Agentic Portfolio Copilot
A LangGraph agent that routes to deeper risk analysis when it detects a concentration flag — running fully local, no API key
Project information
- Category: Data Science / Agentic AI
- Data: Synthetic portfolio (14 US large-caps, 6 sectors)
- Stack: Python, LangGraph, Ollama (Llama 3.1 8B), Streamlit
- Link to github: GitHub repo →
- Live app: Live demo →
An agent whose path changes, not just its answer
A single LLM call with tools bolted on can call a function and describe the result — that's a wrapper. What makes this agentic is that the graph's path changes based on what a tool returns, not just what the user typed: a risk question that trips a concentration threshold is routed to a second reasoning step before the agent answers, while a plain lookup goes straight through. Built on LangGraph as a genuine state machine, running on a local Llama 3.1 model via Ollama — reproducible by anyone who clones the repo, no API key required.
Project deep dive
Problem statement
Build an agent that reasons over a portfolio's holdings, sector concentration, VaR, and drawdowns — and instead of just reporting a number, automatically routes to a deeper explanation whenever a tool result crosses a risk threshold. The goal was to demonstrate genuine conditional planning (the graph's path depends on tool output, not just the question), full grounding (every number in an answer traceable to a tool call), and zero-cost reproducibility for anyone reviewing the code.
Architecture
A LangGraph state machine, not a single tool-calling LLM call:
START → router → tool_exec → risk_check ─┬─ (flag raised) → deep_analysis → END
└─ (no flag) → respond → END
What the agent can do
- get_holdings — current positions, market values, and weights.
- sector_exposure — weight by sector, with concentration flags above a configurable threshold.
- compute_var — historical-simulation Value-at-Risk, parameterized by confidence level and horizon.
- compute_drawdown — max drawdown, portfolio-wide or per ticker.
- get_price_history — recent price series for a single ticker.
- risk_check + deep_analysis — the conditional edge: when a tool result crosses a threshold (e.g. sector weight > 25%), the graph routes to a second reasoning step that explains the risk and suggests a mitigation, instead of just stating the number.
Eval results
| Metric | Score |
|---|---|
| Tool selection accuracy | 0.917 |
| Grounding rate | 1.0 |
| Errors | 0 |
- Grounding rate (1.0): every number in every answer, across 18 test questions, traces back to an actual tool result — checked by a custom harness, not eyeballed. Getting this proxy right (list-numbering artifacts, sign mismatches, legitimate rounding of summed figures) took several debugging passes, documented in
eval/eval_harness.py. - Tool selection accuracy (0.917): the gap comes from two deliberately ambiguous questions ("How risky is my portfolio right now?" and "Has NVDA dropped a lot recently?"), where the local 8B router sometimes picks a plausible-but-incomplete tool rather than the full expected set — a documented, honest limitation of running the router on a free local model instead of a hosted frontier one.
Tech stack
Full eval harness, test suite, and reasoning-trace UI are in the GitHub repo → — clone it and run python eval/eval_harness.py to reproduce the table above with no API key.