diff --git a/api/api_types.py b/api/api_types.py index 503dda0..c13c4e7 100644 --- a/api/api_types.py +++ b/api/api_types.py @@ -99,6 +99,11 @@ class WalletPoolPosition(BaseModel): depositedTokens: Dict[str, float] # address to token amount +class Suggestion(BaseModel): + display: str # short label shown in the UI (2-5 words) + prompt: str # full detailed message sent to the agent when clicked + + class UserMessage(BaseModel): type: Literal["user"] = "user" message: str diff --git a/server/fastapi_server.py b/server/fastapi_server.py index 6d01da8..392bac1 100644 --- a/server/fastapi_server.py +++ b/server/fastapi_server.py @@ -25,6 +25,7 @@ AgentType, Portfolio, Message, + Suggestion, TokenMetadata, SolanaVerifyRequest, Context, @@ -595,7 +596,7 @@ async def handle_suggestions_request( portfolio: Portfolio, token_metadata_repo: TokenMetadataRepo, suggestions_model: ChatOpenAI, -) -> List[str]: +) -> List[Suggestion]: # Get tools from agent config and format them tools = create_investor_agent_toolkit() + create_analytics_agent_toolkit( token_metadata_repo @@ -621,21 +622,18 @@ async def handle_suggestions_request( content = content.strip() try: - # First try parsing as JSON - suggestions = json.loads(content) - if isinstance(suggestions, list): + parsed = json.loads(content) + if isinstance(parsed, list): + suggestions = [] + for item in parsed: + if isinstance(item, dict) and "display" in item and "prompt" in item: + suggestions.append(Suggestion(display=item["display"], prompt=item["prompt"])) + elif isinstance(item, str): + # Graceful fallback: treat legacy string as both display and prompt + suggestions.append(Suggestion(display=item, prompt=item)) return suggestions except json.JSONDecodeError: - # If JSON parsing fails, try parsing as string array - try: - # Remove any JSON-like syntax and split by comma - cleaned = content.strip("[]") - # Split by comma and remove quotes - suggestions = [item.strip().strip("'\"") for item in cleaned.split(",")] - return suggestions - except Exception as e: - logging.error(f"Error parsing suggestions string: {e}") - return [] + logging.error(f"Error parsing suggestions JSON: {content}") return [] diff --git a/templates/suggestions.jinja2 b/templates/suggestions.jinja2 index b3d6ed3..ab12d43 100644 --- a/templates/suggestions.jinja2 +++ b/templates/suggestions.jinja2 @@ -1,13 +1,18 @@ You are a context-aware suggestion system for OpenGradient's DeFi platform. Based on conversation history and user wallet data, generate 3-4 highly relevant next-step suggestions displayed as clickable buttons. +Each suggestion has two parts: +- "display": short label shown on the button (2-5 words, action-oriented) +- "prompt": the full, detailed message sent to the agent when the button is clicked (1-3 sentences with specific context, tokens, or chains) + CRITICAL RULES: -1. Return ONLY a valid JSON array of strings: ["Suggestion 1", "Suggestion 2", "Suggestion 3", "Suggestion 4"] +1. Return ONLY a valid JSON array of objects: [{"display": "...", "prompt": "..."}, ...] 2. Don't wrap your response in quotes or anything else, just return the array -3. Each suggestion must be 2-5 words, action-oriented, and specific -4. NO explanations, preambles, or additional text -5. ONLY suggest actions supported by the available tools listed below -6. DON'T suggest an action that has already been taken -7. Don't recommend analyze_price_trend() or get_coingecko_current_price() on stablecoins like USDC, USDT, etc +3. "display" must be 2-5 words, action-oriented, and specific +4. "prompt" must be a complete, detailed question or instruction the agent can act on immediately +5. NO explanations, preambles, or additional text outside the JSON array +6. ONLY suggest actions supported by the available tools listed below +7. DON'T suggest an action that has already been taken +8. Don't recommend analyze_price_trend() or get_coingecko_current_price() on stablecoins like USDC, USDT, etc AVAILABLE TOOLS: {{ tools }} @@ -24,11 +29,10 @@ Always consider: - For tokens that the user is interested in, suggest buying only if the token is on solana. e.g. id is "solana:TOKEN_B" EXAMPLE RESPONSES: -- After yield discussion: ["Yield on USDC", "Yield on SOL", "Yield on BONK", "Get price history for SOL", etc] -- After token mention: ["Evaluate BONK risk", "Top holders of BONK", "Buy TOKEN_B", etc] -- After market question: ["Analyze ETH trend", "Get Ethereum TVL", "Portfolio volatility", "TVL trends on Ethereum", "Portfolio summary", etc] -- After risk concern: ["Analyze BONK risk", "Analyze BONK price trend", etc] -- After trending tokens on CHAIN_A: ["Evaluate TOKEN_A risk", "Top holders of TOKEN_A", "Evaluate TOKEN_B risk", etc] +- After yield discussion: [{"display": "Yield on SOL", "prompt": "What are the best current yield opportunities for my SOL holdings, including APR and risk level?"}, {"display": "Yield on BONK", "prompt": "Show me yield options for BONK including staking and liquidity pool APRs."}, ...] +- After token mention: [{"display": "Evaluate BONK risk", "prompt": "Run a full risk evaluation on BONK, covering contract security, liquidity depth, and holder concentration."}, {"display": "Top BONK holders", "prompt": "Who are the top holders of BONK and what percentage of supply do they control?"}, ...] +- After market question: [{"display": "Analyze ETH trend", "prompt": "Analyze Ethereum's price trend over the past 30 days, including volume, momentum, and key support and resistance levels."}, {"display": "Get Ethereum TVL", "prompt": "Show me the historical TVL trend for Ethereum DeFi over the past 90 days."}, ...] +- After risk concern: [{"display": "Analyze BONK risk", "prompt": "Evaluate BONK for smart contract risks, rug pull indicators, and unusual whale activity."}, ...] User's Solana wallet tokens: {{ tokens }}