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
1 change: 1 addition & 0 deletions backend/-

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions backend/database/reset_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ def main():
print("\n📝 Running migrations...")
import subprocess
result = subprocess.run(['uv', 'run', 'run_migrations.py'],
capture_output=True, text=True)
capture_output=True,
text=True,
encoding="utf-8", # <-- ADD THIS
errors="ignore")

if result.returncode != 0:
print("❌ Migration failed!")
Expand All @@ -177,7 +180,10 @@ def main():
print("\n🌱 Loading seed data...")
import subprocess
result = subprocess.run(['uv', 'run', 'seed_data.py'],
capture_output=True, text=True)
capture_output=True,
text=True,
encoding="utf-8", # <-- ADD THIS
errors="ignore")

if result.returncode != 0:
print("❌ Seed data failed!")
Expand Down
4 changes: 2 additions & 2 deletions backend/researcher/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from dotenv import load_dotenv
from agents import Agent, Runner, trace
from agents import Agent, Runner, trace , ModelSettings
from agents.extensions.models.litellm_model import LitellmModel

# Suppress LiteLLM warnings about optional dependencies
Expand Down Expand Up @@ -54,7 +54,7 @@ async def run_research_agent(topic: str = None) -> str:
# bedrock/openai.gpt-oss-120b-1:0 for OpenAI OSS models
# bedrock/converse/us.anthropic.claude-sonnet-4-20250514-v1:0 for Claude Sonnet 4
# NOTE that nova-pro is needed to support tools and MCP servers; nova-lite is not enough - thank you Yuelin L.!
MODEL = "bedrock/us.amazon.nova-pro-v1:0"
MODEL = "bedrock/amazon.nova-pro-v1:0"
model = LitellmModel(model=MODEL)

# Create and run the agent with MCP server
Expand Down
2 changes: 1 addition & 1 deletion backend/retirement/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
Market Assumptions:
- Average equity returns: 7% annually
- Average bond returns: 4% annually
- Inflation rate: 3% annually
- Inflation rate: 5% annually
- Safe withdrawal rate: 4% initially

Perform the following analyses:
Expand Down
62 changes: 61 additions & 1 deletion backend/tagger/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import List
import logging
from decimal import Decimal

from typing import Dict
from pydantic import BaseModel, Field, field_validator, ConfigDict
from agents import Agent, Runner, trace
from agents.extensions.models.litellm_model import LitellmModel
Expand Down Expand Up @@ -322,3 +322,63 @@ def classification_to_db_format(classification: InstrumentClassification) -> Ins
allocation_regions=regions_dict,
allocation_sectors=sectors_dict,
)

class InstrumentClassificationWithRationale(BaseModel):
# Rationale MUST come first so LLM generates reasoning before answers
rationale: str = Field(
description="Detailed explanation of why these classifications were chosen, including specific factors considered"
)

asset_class: AssetClassType = Field(
description="Primary asset class classification"
)

asset_class_allocation: Dict[str, float] = Field(
description="Percentage breakdown by asset class",
example={"equity": 100.0}
)

region_allocation: Dict[str, float] = Field(
description="Percentage breakdown by geographic region",
example={"north_america": 70.0, "europe": 20.0, "asia_pacific": 10.0}
)

sector_allocation: Dict[str, float] = Field(
description="Percentage breakdown by sector (only for equity)",
example={"technology": 30.0, "healthcare": 20.0, "financial": 50.0}
)

async def run_tagger_agent(instrument: dict) -> dict:
model = LitellmModel(model=f"bedrock/{bedrock_model}")

with trace("Classify instrument with explainability"):
agent = Agent(
name="Instrument Tagger with Explainability",
instructions=CLASSIFICATION_INSTRUCTIONS,
model=model,
response_format=InstrumentClassificationWithRationale
)

result = await Runner.run(
agent,
input=create_classification_task(instrument),
max_turns=1
)

classification = result.final_output_as(InstrumentClassificationWithRationale)

# Log the rationale for audit trail
logger.info(json.dumps({
"event": "CLASSIFICATION_RATIONALE",
"symbol": instrument["symbol"],
"rationale": classification.rationale,
"timestamp": datetime.utcnow().isoformat()
}))

# Return classification without rationale to planner
return {
"asset_class": classification.asset_class,
"asset_class_allocation": classification.asset_class_allocation,
"region_allocation": classification.region_allocation,
"sector_allocation": classification.sector_allocation
}
17 changes: 16 additions & 1 deletion backend/tagger/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,19 @@
- Each category must sum to exactly 100.0%
- For stocks, typically 100% in one asset class, one region, one sector
- For ETFs, distribute based on underlying holdings
- For bonds/bond funds, use fixed_income asset class and appropriate sectors (treasury/corporate/mortgage/government_related)"""
- For bonds/bond funds, use fixed_income asset class and appropriate sectors (treasury/corporate/mortgage/government_related)"""

ANALYSIS_INSTRUCTIONS_WITH_EXPLANATION = """
When providing recommendations, always:
1. Start with your reasoning process
2. List specific factors you considered
3. Explain why certain recommendations were prioritized
4. Include any assumptions made
5. Note any limitations or caveats

Format each recommendation as:
**Recommendation:** [The action to take]
**Reasoning:** [Why this recommendation was made]
**Impact:** [Expected outcome if implemented]
**Priority:** [High/Medium/Low based on user goals]
"""
Loading