Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/api_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 12 additions & 14 deletions server/fastapi_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
AgentType,
Portfolio,
Message,
Suggestion,
TokenMetadata,
SolanaVerifyRequest,
Context,
Expand Down Expand Up @@ -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
Expand All @@ -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 []

Expand Down
26 changes: 15 additions & 11 deletions templates/suggestions.jinja2
Original file line number Diff line number Diff line change
@@ -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 }}
Expand All @@ -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 }}

Expand Down