Browse Source

fix: improve routing robustness — better keyword coverage for stock/property/retirement queries, improved LLM fallback prompt with examples and clearer market vs performance distinction

Made-with: Cursor
pull/6453/head
Priyanka Punukollu 1 month ago
parent
commit
b749638fb8
  1. 125
      agent/graph.py

125
agent/graph.py

@ -108,28 +108,89 @@ def llm_classify_intent(query: str) -> str:
Returns a valid query_type string."""
client = anthropic.Anthropic()
prompt = f"""You are a routing classifier for a finance AI agent.
Given a user query, return ONLY one of these exact labels nothing else:
market - user wants stock price, market data, or info about a specific ticker
performance - user wants their portfolio summary, holdings, returns, or allocation
compliance - user asks about risk, diversification, concentration, or regulatory concerns
tax - user asks about taxes, capital gains, tax-loss harvesting, wash sales
activity - user asks about trades, buys, sells, transaction history
property_list - user wants to add, view, update, or remove a property they own
property_net_worth - user asks about total net worth including real estate
equity_unlock - user asks about home equity, cash-out refinance, HELOC options
relocation_runway - user asks about moving cities, cost of living comparison, runway
wealth_gap - user asks about retirement, savings rate, financial benchmarks, age
life_decision - user asks about job offers, major life decisions, affordability, buying multiple properties, rental strategy
family_planner - user asks about kids, childcare, family planning costs
market_overview - user asks about general market trends, not a specific stock
unknown - cannot determine intent
prompt = f"""You are a routing classifier for a personal finance AI agent.
The user has a portfolio of stocks and may own properties.
Classify the user query into EXACTLY ONE label.
Respond with only the label no explanation, no punctuation, just the label.
LABELS AND WHEN TO USE THEM:
market
User wants current stock price or market data for a specific company or ticker symbol.
Examples:
- "check apple for me"
- "what is AAPL at"
- "give me the nvidia number"
- "apple stock check"
- "what about my nvidia" (asking about price)
- "how is tesla doing" (price/market performance)
- "what is AAPL worth today"
KEY SIGNAL: company name or ticker + price/check/at/worth
performance
User wants to see their own portfolio holdings, returns, allocation, or overall financial picture.
Examples:
- "show me my money"
- "how am I doing financially"
- "what does my investment look like"
- "pull up my stocks" (their holdings)
- "run the numbers on my portfolio"
KEY SIGNAL: "my" + portfolio/investments/stocks/money
property_list
User wants info about real estate they own.
Examples:
- "tell me about my house"
- "what is my home worth"
- "my property value"
KEY SIGNAL: my house, my home, my property
wealth_gap
User asks about retirement readiness, savings rate, or financial benchmarks.
Examples:
- "can I afford to retire"
- "am I on track financially"
- "when can I retire"
KEY SIGNAL: retire, retirement, savings rate
life_decision
User asks about major life decisions like job offers, moving cities, buying a home.
Examples:
- "should I take this job offer"
- "can I afford to move to Seattle"
KEY SIGNAL: should I, can I afford, job offer
compliance
User asks about portfolio risk or diversification.
Examples:
- "am I too concentrated"
- "how diversified am I"
KEY SIGNAL: diversified, concentrated, risk
tax
User asks about taxes or capital gains.
Examples:
- "what are my capital gains"
- "do I owe taxes"
KEY SIGNAL: tax, capital gains, wash sale
activity
User asks about trade history.
Examples:
- "show my recent trades"
- "what did I buy this year"
KEY SIGNAL: trades, bought, sold, transactions
IMPORTANT DISTINCTION:
"what about my nvidia" = market (price question)
"my nvidia position" = performance (holding question)
"check apple" = market (price check)
"my apple shares" = performance (holding question)
User query: {query}
Respond with exactly one label from the list above.
No explanation. No punctuation. Just the label."""
Respond with exactly one label."""
valid_types = {
"market", "performance", "compliance", "tax", "activity",
@ -672,6 +733,9 @@ async def classify_node(state: AgentState) -> AgentState:
"wealth percentile", "net worth percentile",
"federal reserve", "median wealth", "peer comparison",
"how does my net worth compare", "retirement projection",
"can i afford to retire", "afford to retire", "retirement plan",
"on track to retire", "retirement savings", "retire early",
"when can i retire",
]
if any(kw in query for kw in wealth_gap_kws):
return {**state, "query_type": "wealth_gap"}
@ -754,6 +818,17 @@ async def classify_node(state: AgentState) -> AgentState:
if any(kw in query for kw in wealth_net_worth_kws):
return {**state, "query_type": "wealth_portfolio_summary"}
# --- Property queries (run regardless of feature flag for correct routing) ---
property_general_kws = [
"my house", "my home", "my property", "my real estate",
"about my house", "about my home", "about my property",
"my home value", "my house value", "my property value",
]
if any(kw in query for kw in property_general_kws):
if any(v in query for v in ["value", "worth"]):
return {**state, "query_type": "property_net_worth"}
return {**state, "query_type": "property_list"}
# --- Property Tracker (feature-flagged) — checked BEFORE general real estate
# so "add my property" doesn't fall through to real_estate_snapshot ---
if is_property_tracking_enabled():
@ -767,6 +842,8 @@ async def classify_node(state: AgentState) -> AgentState:
"my properties", "list my properties", "show my properties",
"my real estate holdings", "properties i own", "my property portfolio",
"what properties", "show my homes",
"my house", "my home", "my property", "my real estate",
"about my house", "about my home", "about my property",
]
property_net_worth_kws = [
"net worth including", "net worth with real estate",
@ -775,6 +852,7 @@ async def classify_node(state: AgentState) -> AgentState:
"everything i own", "show my total net worth",
"complete financial picture", "net worth including my home",
"net worth including my investment",
"my home value", "my house value", "my property value",
]
property_update_kws = [
"update my home", "update my property", "update my house",
@ -927,6 +1005,13 @@ async def classify_node(state: AgentState) -> AgentState:
"what is googl", "what is amzn", "what is meta", "what is vti",
"trading at", "price today", "how much is", "ticker", "quote",
"what's the stock price", "whats the stock price",
"check apple", "check aapl", "check msft", "check nvda", "check tsla",
"check googl", "check amzn", "check meta",
"apple stock", "msft stock", "nvda stock", "tsla stock", "aapl stock",
"apple price", "nvidia price", "microsoft price", "tesla price", "amazon price",
"what about aapl", "what about msft", "what about nvda", "what about tsla",
"what about apple", "what about nvidia", "what about tesla", "what about microsoft",
"aapl number", "msft number", "nvda number", "stock check",
]
if any(kw in query for kw in stock_price_kws) and _extract_ticker(query):
return {**state, "query_type": "market"}
@ -935,7 +1020,7 @@ async def classify_node(state: AgentState) -> AgentState:
# These are common phrasings that don't match the terse keyword lists above.
natural_performance_kws = [
"how am i doing", "how have i done", "how is my money",
"how are my investments", "how are my stocks",
"show me my money", "how are my investments", "how are my stocks",
"am i making money", "am i losing money",
"what is my portfolio worth", "what's my portfolio worth",
"show me my portfolio", "give me a summary",

Loading…
Cancel
Save