Skip to main content
Glama
FixNPE

agentic-financial-advisor

by FixNPE

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 LangGraph create_react_agent, wrapped as an A2A server (agent card + JSON-RPC endpoint) via a2a-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_agentRAG over data/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_KEY

If 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.txt

Verify 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

  1. 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-ingests data/documents/ into Chroma on first startup if the index is empty — no manual step needed (python -m rag.ingest still works directly if you want to force a rebuild after changing the corpus).

  2. Either the chat CLI, in another terminal:

    python main.py

    or 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.py

    The 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 stop

ask_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 cases

Router 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 URL

This 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 (from langgraph.prebuilt) is deprecated as of LangGraph 1.x in favor of langchain.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).