Drift uses Google’s Gemini AI to parse natural language financial goals into structured targets. Instead of forcing users to specify exact dollar amounts and timelines, you can describe goals conversationally:
“I want to retire comfortably in 15 years”
“Save for a house down payment”
“Build a 6-month emergency fund”
“Pay off my student loans”
Gemini extracts the goal type, target amount, and timeline—or asks clarifying questions if the goal is unclear.
const prompt = `You are a financial goal parser. Given a user's natural language financial goal, extract structured parameters.User's goal: "${goal}"Extract the following (use null if not determinable):1. goal_type: One of: - "retirement" (retiring, stop working, financial independence) - "major_purchase" (house, car, boat, etc.) - "emergency_fund" (rainy day, safety net, 6 months expenses) - "debt_payoff" (pay off loans, become debt free) - "travel" (vacation, trip, sabbatical) - "education" (college, degree, certification) - "investment" (grow wealth, portfolio target) - "custom" (anything else)2. target_amount: Number in USD. If vague: - "comfortable retirement" → 1000000 - "house down payment" → 60000 - "emergency fund" → 15000 (estimate) - "pay off debt" → null (will be filled from data) - IMPORTANT: Flag as unrealistic if target seems way too low for the goal type (e.g., $3 for a car)3. timeline_months: Number of months. If vague: - "in a few years" → 36 - "by retirement" → use 65 - 30 = 35 years = 420 months - "soon" → 12 - "long term" → 1204. constraints: Array of any constraints mentioned (empty array if none)5. needsClarification: Boolean. Set to true if: - Target amount is unrealistically low for the goal type - Timeline is missing for a time-sensitive goal - Goal description is too vague to estimate amounts6. clarifyingQuestions: If needsClarification is true, provide questions. Otherwise null.Respond in JSON only, no explanation.
From goal_parser.py:117-260, the Python implementation uses OpenAI’s GPT-4o-mini with similar prompting:
prompt = f"""You are a financial advisor. Parse the following savings goal and extract:1. Type of goal (retirement, house, emergency_fund, vacation, college, car, custom)2. Target amount in USD3. Timeline in months4. Whether the goal description contains enough information to parse (realistic and clear)User context:- Monthly income: ${monthly_income:,.0f}- Risk tolerance: {risk_tolerance}- Goal description: "{goal_text}"Important: If the goal seems unrealistic or lacks critical information, flag it and provide clarifying questions needed.Respond as JSON with these fields:{{ "goal_type": "string", "target_amount": number (USD) or null if unclear, "timeline_months": number or null if unclear, "description": "string describing the goal", "confidence": number (0-1, how confident are you in this parsing), "needs_clarification": boolean, "clarifying_questions": ["string"] or []}}"""response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "You are a helpful financial planning assistant. Always respond with valid JSON."}, {"role": "user", "content": prompt} ], temperature=0.3, max_tokens=300)
Template matching works well for common goals, but lacks the nuance of LLM parsing. For example, it can’t distinguish between “buy a used Honda” (15K)and"buyaPorsche"(80K).
When a goal needs clarification, the parser returns low confidence with questions (geminiService.ts:99-106):
// Ensure clarifying questions exist if neededif (parsed.needsClarification && !parsed.clarifyingQuestions) { parsed.clarifyingQuestions = [ 'Please provide more specific details about your goal amount and timeline.' ]}
Example from mock parser (geminiService.ts:285-291):
if (extractedAmount !== null && extractedAmount < 100) { if (goalType === 'major_purchase' || goalType === 'retirement') { needsClarification = true clarifyingQuestions = [ `The amount $${extractedAmount} seems very low for a ${goalType}. Did you mean $${extractedAmount * 1000}?`, 'Could you clarify the exact amount you need?' ] }}
If you say “buy a corvette for 3",theparserwillflagthisandask:"Acorvettetypicallycosts50,000-100,000.Didyoumean3,000 or $30,000?”
Drift also supports multi-turn conversations for goal setting (geminiService.ts:407-517):
class GeminiGoalConversation { async processUserInput( userMessage: string, conversationHistory: ConversationMessage[] ): Promise<GoalConversationResponse> { const systemPrompt = `You are Drift, a friendly financial goal assistant.RULES:1. Be conversational and warm - like a smart friend who's good with money2. Keep responses SHORT (1-2 sentences max)3. Ask ONE clarifying question at a time if needed4. Extract: goal type, target amount, timelineWHEN YOU HAVE ALL THREE (amount, timeline, goal type):- Confirm what you understood- End your response with [GOAL_COMPLETE] on its own line- Include a JSON block with the parsed goal` const result = await this.model.generateContent(fullPrompt) const responseText = result.response.text().trim() // Check if goal is complete const isComplete = responseText.includes('[GOAL_COMPLETE]') // Extract parsed goal if complete if (isComplete) { const jsonMatch = responseText.match(/```json\s*([\s\S]*?)```/) if (jsonMatch) { parsedGoal = JSON.parse(jsonMatch[1].trim()) } } return { response: cleanResponse, isComplete, parsedGoal } }}
User: "I want to save for a house"Drift: "Nice! How much are you thinking for the down payment?"User: "Like 50 thousand"Drift: "Got it, $50K for a house down payment. What's your timeline - when are you hoping to buy?"User: "Maybe 3 years"Drift: "Perfect - $50,000 for a house down payment in 3 years. Let's see what your odds look like![GOAL_COMPLETE]```json{"targetAmount": 50000, "timelineMonths": 36, "goalType": "major_purchase"}
## Income Estimation from TransactionsWhen parsing goals, Drift can estimate income from deposit patterns (goal_parser.py:318-379):```pythondef extract_salary_from_deposits( deposits_by_account: Dict[str, list], purchases_data: Optional[Dict[str, list]] = None, return_details: bool = False) -> Tuple[float, Optional[Dict[str, Any]]]: # Collect all deposits all_deposits = [] for account_id, deposits in deposits_by_account.items(): for deposit in deposits: amount = deposit.get('amount', 0) if amount > 0: all_deposits.append({ 'amount': amount, 'date': deposit.get('date', ''), 'description': deposit.get('description', '') }) # Look for large recurring deposits (likely salary) amounts = [d['amount'] for d in all_deposits] q75 = np.percentile(amounts, 75) large_deposits = [a for a in amounts if a >= q75] if large_deposits: salary = np.mean(large_deposits) else: salary = np.median(amounts) return round(salary, 2)
This finds recurring large deposits (paychecks) to estimate monthly income, which informs retirement goals (25x annual income) and emergency funds (6 months).
response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "You are a helpful financial planning assistant. Always respond with valid JSON."}, {"role": "user", "content": prompt} ], temperature=0.3, max_tokens=300)
Uses GPT-4o-mini with low temperature (0.3) for deterministic parsing.
Both models are instruction-tuned and excel at structured JSON extraction. Gemini 2.0 Flash is faster and cheaper, while GPT-4o-mini is slightly more reliable for edge cases.