agentic-financial-advisor
Agentic Financial Advisor
A multi-agent financial advisor built with LangChain/LangGraph agents, exposed to each other over the A2A protocol, pulling live market data through an MCP (Alpha Vantage) server, and grounding answers in a local RAG (Chroma) knowledge base. Usable via a chat CLI, a Streamlit UI with a live view of the routing/agent-call flow (plus a RAG Explorer page), or directly from Claude Code — the app's own operations are exposed as an MCP server too.
Informational/educational only — nothing here is personalized financial advice.
Architecture
Specialist agents (
agents/) — each is a standalone LangGraphcreate_react_agent, wrapped as an A2A server (agent card + JSON-RPC endpoint) viaa2a-sdk:market_research_agent— live quotes/fundamentals/technicals/macro data, via the Alpha Vantage MCP server (mcp_integration/).portfolio_analyst_agent— allocation weighting and concentration risk, via local calculation tools.financial_planning_agent— savings/retirement projections, via local calculation tools.document_research_agent— RAG overdata/documents/using Chroma + local HuggingFace embeddings (rag/).
Orchestrator (
orchestrator/) — a LangGraph supervisor graph that routes a user query to the relevant specialist agent(s), calls them in parallel over A2A (orchestrator/a2a_client.py), and synthesizes one final answer.main.py— interactive chat CLI that talks to the orchestrator.
User -> main.py -> supervisor graph (route -> fan-out -> synthesize)
| | | |
market_research portfolio planning document_research
(MCP: Alpha Vantage) (local tools) (RAG: Chroma)Setup
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # fill in ANTHROPIC_API_KEY and ALPHAVANTAGE_API_KEYIf python -m venv fails with ensurepip is not available (this system had
no python3-venv/pip installed at all and no passwordless sudo), bootstrap
pip inside the venv directly instead of installing the apt package:
python3 -m venv --without-pip .venv
curl -sS https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py
.venv/bin/python /tmp/get-pip.py
.venv/bin/python -m pip install -r requirements.txtVerify ALPHAVANTAGE_MCP_URL in .env against Alpha Vantage's current MCP
documentation — hosted MCP endpoints can change.
a2a-sdk is pinned to ==0.3.26 in requirements.txt: newer 1.x releases
restructured the SDK around gRPC/protobuf and dropped a2a.server.apps
entirely, which this project's Starlette-based agent servers depend on. This
pin was verified against the actual installed package, not assumed.
Running
Start the four specialist agent servers:
python scripts/run_all_agents.py(or run each individually in its own terminal, e.g.
python -m agents.market_research_agent). The Document Research Agent auto-ingestsdata/documents/into Chroma on first startup if the index is empty — no manual step needed (python -m rag.ingeststill works directly if you want to force a rebuild after changing the corpus).Either the chat CLI, in another terminal:
python main.pyor the Streamlit UI, which also shows the routing decision and each agent's call live as it happens rather than only the final answer:
streamlit run app/streamlit_app.pyThe Streamlit app has a second page, RAG Explorer (in its sidebar), for browsing the Chroma collection — chunks, metadata, a chunk's raw embedding vector, and a live semantic-search test against the same index the Document Research Agent retrieves from.
Corpus
data/documents/ holds the RAG source material: two markdown reference docs
(investment glossary, model risk-profile allocations) plus two public
government-published PDFs under data/documents/pdfs/ — a FINRA guide to
spotting investment scams and a CFPB home-loan toolkit. Ingestion
(rag/ingest.py) handles .md, .txt, and .pdf (text-extracted
page-by-page via pypdf) uniformly. Drop more files of any of those types
into data/documents/ (subdirectories are fine) and either restart the
Document Research Agent (auto-ingests if the index was empty) or run
python -m rag.ingest to force a full rebuild.
MCP server (use these tools from Claude Code)
mcp_server.py exposes this app's own operations — portfolio allocation,
concentration risk, retirement/savings projections, RAG document search, and
a full multi-agent advisor query — as MCP tools, the same way the Alpha
Vantage MCP server exposes its functions to the Market Research Agent. Every
tool is a thin wrapper reusing the actual app logic (the same
@tool-decorated calculators the agents use, the same Chroma retriever, the
same supervisor graph), not a reimplementation.
It's registered in this project's .mcp.json for Claude Code to pick up as a
local (stdio) MCP server. Restart your Claude Code session for a newly
added .mcp.json entry to take effect — like skills, MCP servers are loaded
at session start, not picked up mid-session.
To test it manually without Claude Code:
python mcp_server.py # runs the stdio server; Ctrl+C to stopask_financial_advisor (the full-orchestrator tool) requires the four
specialist agent servers to already be running — it calls out to them over
A2A exactly like main.py/the Streamlit UI do. The other five tools
(calculators + RAG search) are self-contained and work with just this one
process.
Workflow testing
tests/scenarios.json has ~19 test queries covering each specialist agent
individually, multi-agent combinations, and edge cases (ambiguous/gibberish
input, a zero-value portfolio). tests/run_scenarios.py runs them against the
live supervisor graph (requires the four agent servers running and real API
keys — these are real LLM/MCP calls, not mocked):
python -m tests.run_scenarios # all scenarios
python -m tests.run_scenarios --category market_research # one category
python -m tests.run_scenarios --id multi-01 planning-02 # specific casesRouter agent-selection mismatches print as warnings (routing is LLM-based and won't always pick the exact expected set); the runner only exits non-zero on a genuine agent error or unhandled exception.
LLM response caching
On by default: a persistent SQLite cache (data/llm_cache.sqlite, gitignored)
so re-running the same query/scenario doesn't re-spend on the Anthropic API —
useful since tests/scenarios.json gets run repeatedly during development.
Measured on a real re-run: ~2x faster, with a genuine (partial, not 100%)
reduction in API calls — LangGraph embeds a random tool_call_id into each
agent's own tool-calling turns, so those specific turns miss the cache even
on an identical repeat query, while the router/synthesizer calls and each
agent's first turn hit it reliably.
Set LLM_CACHE_ENABLED=false in .env, or delete data/llm_cache.sqlite,
whenever you need a guaranteed-fresh answer — this matters most for market
data queries, where an exact-wording repeat would otherwise replay a stale
quote instead of fetching a current one.
Observability (LangSmith)
Set LANGSMITH_API_KEY in .env (get one at https://smith.langchain.com/ ->
Settings -> API Keys) to turn on full tracing across the whole system —
every LLM call, tool call (MCP/RAG/calculators), and LangGraph node in every
process shows up in the LangSmith UI under your LANGSMITH_PROJECT. This is
zero-code auto-instrumentation from LangChain/LangGraph once the env vars are
set (agents/common/observability.py::enable_tracing(), called at the top of
every entrypoint). Leave LANGSMITH_API_KEY blank to run with tracing off.
Each user query gets a request_id, generated in main.py/tests/run_scenarios.py
and propagated through the supervisor graph and over A2A (as message metadata)
to every specialist agent it calls. The orchestrator's own run (route -> fan-out
-> synthesize) appears as one nested trace; each specialist agent runs in its
own process so it appears as a separate trace, but every trace involved in one
user query is tagged with the same request_id — filter on metadata.request_id
in the LangSmith UI to reconstruct the full "360 view" of one query across all
four agents plus the orchestrator. (Traces aren't stitched into a single
literal parent-child tree across the A2A/process boundary — that would need
distributed trace-context propagation, which isn't implemented here.)
For a testing view in the LangSmith UI (rather than only console output):
python -m tests.upload_dataset # push tests/scenarios.json as a LangSmith dataset (idempotent)
python -m tests.langsmith_eval # run it as an experiment; prints a results URLThis runs the same scenarios as tests/run_scenarios.py but records them as a
LangSmith experiment against the financial-advisor-workflow-scenarios
dataset — viewable under Datasets & Testing as a results table (routing-match
and no-agent-errors scores per row) with a full trace attached to every row.
Notes
Embeddings are local (
sentence-transformers/all-MiniLM-L6-v2) so RAG works without an extra API key.create_react_agent(fromlanggraph.prebuilt) is deprecated as of LangGraph 1.x in favor oflangchain.agents.create_agent, but still works — every agent in this repo has been smoke-tested against the installed version. Migrate when LangGraph actually removes it (planned for 2.0).