ProjectsStock Advisor Copilot — Multi-Agent Research Assistant
Case Study · FinTech AI · Multi-Agent · RAG

Stock Advisor Copilot — Cited Filing Research Behind an Approval Gate

A multi-agent LangGraph backend that pairs a live quote with a cited summary of a company's latest SEC 10-K — screened by an input guardrail up front and gated by a compliance agent that pauses for advisor sign-off before anything is treated as final.

LangGraphFastAPIClaudePineconeNext.jsLangSmithSSE
Role
Full-stack build, agent orchestration & guardrail design
Domain
FinTech · advisor research
Primary Services
Claude API · LangGraph · Pinecone
01

The problem & requirements

Functional
  • Get an advisor up to speed on a ticker fast: a current quote plus a plain-English summary of what the company's latest 10-K says about its risks and MD&A
  • Route each question to only the specialists it needs — quote-only, filings-only, or both — from a single free-text message
  • Pause every filing summary for advisor sign-off (approve, edit, or reject) before it is treated as final
Non-functional
  • Grounding, not assertion: every summary carries citations back to the filing chunks it was built from
  • Scoped by construction: off-topic requests, prompt injection, and insider-trading solicitations are blocked before any agent or tool runs
  • Compliance-aware output: definitive-advice language forces the approval gate, and every note carries a not-investment-advice disclaimer
02

Scale & constraints

A two-hour prototype with deliberately narrow scope. Mocked quotes and pre-seeded filings keep the demo independent of flaky external APIs, and Pinecone integrated embeddings keep Anthropic as the only paid vendor.

3
tickers with pre-ingested 10-K coverage — AAPL, TSLA, MSFT
8
eval cases through the real graph — 5 research + 3 guardrail triggers
2
guardrail layers in order: deterministic regex fast-path, then an LLM classifier
03

API design

POST /api/query → SSE stream
Runs the graph and streams one `update` event per node, then a final `done` event carrying status — `blocked`, `awaiting_approval`, or `complete` — plus the interrupt payload when a human needs to sign off.
POST /api/approve → {thread_id, status, values}
Resumes the paused graph thread with the advisor's decision — `approve`, `reject`, or `edit` with replacement text — via a LangGraph `Command(resume=...)`.
GET /api/quote/{ticker} → TickerQuote
Exposes the same quote tool the Market Data Agent calls, so the UI can render a quote card directly. Mocked, but mirrors a real provider's response shape.
GET|POST /api/watchlist/{advisor_id}
Reads an advisor's watchlist and recent queries, or adds/removes a ticker.
04

Data model

EntityKey fields
TickerQuotesymbol, company_name, price, change, change_percent, day_high/low, volume, market_cap, as_of.
FilingChunkticker, cik, filing_type, filing_date, section (e.g. Item 1A – Risk Factors), source_url, chunk_index, text.
FilingSummarykey_points, risks, citations, generated_at, and `approval_status: pending|approved|rejected|edited`.
AdvisorProfileadvisor_id, watchlist, recent_queries — a flat JSON file, not a database.
AgentState (LangGraph)TypedDict carrying messages, ticker, quote, retrieved chunks, summary, guardrail flags, `blocked` + `block_category`, and the approval decision.

Thread state lives in LangGraph's in-process MemorySaver, so a paused approval survives across the two HTTP calls but not a backend restart — an accepted MVP cut.

05

Architecture

Guardrail, route, converge, approve

Every request is screened before it can cost anything. Survivors are routed to one or both specialists, which converge on a compliance agent that can pause the whole graph for a human.

🛡
input_guardrail
regex → LLM classifier
supervisor
structured-output routing
💹
market_data_agent
Quote for the routed ticker (mocked, real response shape)
📄
filings_rag_agent
Pinecone retrieval + cited 10-K summary
compliance_agent
flags advice language, interrupts for sign-off
👤
Advisor
approve · edit · reject
The supervisor is a Claude Haiku call with forced structured output — a ticker plus `need_quote` / `need_filing_summary` booleans — and fans out to one or both specialists in parallel. The compliance agent uses LangGraph `interrupt()`, so the run is durably paused mid-graph rather than faked with a UI-only confirm button.

Fig. 1a — A blocked request short-circuits at the guardrail: no supervisor call, no retrieval, no quote lookup.

Deploy topology

The backend is the only component that talks to Anthropic, Pinecone, and LangSmith. The Next.js frontend never holds a third-party credential.

Next.js
chat · quote card · approvals queue
SSE + REST
FastAPI + LangGraph
🧠
Claude
routing, guardrail, summarization
🔎
Pinecone
integrated embeddings
🛰
LangSmith
eval scoring + traces
Filings are ingested once, ahead of time, by `app/rag/ingest.py` — 10-Ks for AAPL, TSLA, and MSFT split by section — rather than fetched live per query, so latency and rate limits stay off the request path.

Fig. 1b — The frontend runs on Vercel; the backend ships without deployment config, so the live demo depends on where it is hosted.

06

Key decisions & trade-offs

Invariant. Guardrails run before the supervisor, not after. A bad request never reaches a tool call or spends an LLM call on real work. Refusals are category-specific — off-topic, prompt injection, insider trading — so the advisor learns what the assistant is scoped to.
Trade-off. Regex first, then an LLM classifier. The regex layer catches the obvious cases for free and does not depend on a model call succeeding; the Haiku classifier handles subtler drift and softly-worded insider-trading asks. Regex alone is brittle, the LLM alone is slower and can fail open.
Constraint. Filing summaries always require sign-off. This models the real compliance requirement for advisor-facing research notes. A recommendation-language flag forces the same gate even for quote-only answers.
Bootstrap-first. Mocked quotes and pre-seeded filings. The quote tool mirrors a real API's response shape, so swapping in a provider like Finnhub is a one-file change. Pinecone's integrated embeddings avoid adding a second paid vendor.
Trade-off. Streamed node updates over a single blocking response. SSE lets the UI show which agent is running as it happens — the agent status stepper — and a done event reports whether the run finished, was blocked, or is waiting on the advisor.
Verification. An eval that includes attacks, not just happy paths. Of the 8 cases, 3 are guardrail triggers — off-topic, prompt injection, insider trading — so a change that weakens the input layer shows up as a score drop in LangSmith.
07

Guardrails

Input screened before any agent runs

Regex fast-path plus a temperature-0 Claude Haiku classifier blocks off-topic requests, prompt injection, and insider-trading solicitations.

Human sign-off on every filing summary

The compliance agent calls interrupt(); the advisor's approve / edit / reject decision is recorded as the summary's approval_status.

Recommendation-language check

Patterns like "you should buy" and "guaranteed return" are flagged and force the approval gate regardless of the summary path.

Only public data, stated plainly

The insider-trading refusal says the assistant works only from market data and SEC filings, and cannot be used to source or act on material non-public information.

Disclaimer on every note

Each final output carries an informational-purposes-only, not-investment-advice disclaimer.

08

Lessons learned

💡 What actually held up
  • Putting the guardrail node ahead of the supervisor meant blocked requests cost nothing downstream and the supervisor's routing prompt never sees an injection attempt.
  • Using `interrupt()` for approval rather than a UI-only confirm made the human gate a property of the graph itself — the summary cannot become final without a resume call.
  • Mirroring a real quote API's shape in the mock kept the demo reliable without making the eventual provider swap a rewrite.
What I'd do differently
  • Compliance checks are regex patterns, so advice phrased outside those patterns slips past the extra flag. The always-on summary sign-off is the real backstop; a classifier would be the better long-term check.
  • State is in-memory and the watchlist is a JSON file — fine for a prototype, but an approval pending at restart is lost. A Postgres-backed LangGraph checkpointer is the obvious next step.
  • The eval is a manual harness, not a CI gate, and covers only 8 cases across 3 tickers. It shows the shape of the loop but would not yet catch a subtle regression on its own.
🔁If I started this over: stand up the persistent checkpointer and a CI-gated eval on day one, so the approval flow and the guardrail regression tests are durable and repeatable instead of manual.
System
Stock Advisor Copilot — Multi-Agent Research Assistant
Primary services
Claude API · LangGraph · Pinecone
Status
Live frontend demo · quotes mocked, 3 tickers, in-memory state
Type
FinTech AI · multi-agent orchestration

Interested in the architecture behind this or another project?