Skip to content
anubhavlal07Public

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Cerebro

CI Python 3.12 Node 24 LTS LangGraph offline

A personal deep-research agent built on LangGraph — and a teaching reference for Multi-Agent Architectures & Human-in-the-Loop with LangGraph. Give Cerebro a research question; it plans the work, dispatches a hierarchical team of specialist agents to gather evidence (web, academic, and your own documents), drafts a cited, research-paper-style report, self-critiques in a bounded loop, and pauses at three approval gates for your call. Every run is checkpointed, so you can inspect history, edit past state, and fork a new branch from any point.

Cerebro — a full research run, from question to cited report

It is designed to make four LangGraph concepts individually visible:

  1. Supervisor orchestrating sub-agents — a top-level planner decomposes the question and routes work.
  2. Hierarchical / nested teams — research_team is a compiled subgraph whose lead fans sub-questions out to specialists in parallel via the Send API.
  3. HITL approval gates — real interrupt() / Command(resume=...) pauses with editable state at three points (plan, draft, final).
  4. Time-travel / checkpointing — inspect the checkpoint history, edit a past checkpoint, and fork a divergent branch.

Runs fully offline with no API keys — a deterministic mock model + mock search/embeddings drive the entire loop (including real token streaming), so you can demo it anywhere. Add a provider key to switch to live models, web search, and vector retrieval, with zero code changes.

Capabilities

  • Multi-agent research graph — the StateGraph is the single source of truth: planner → plan_gate → research_team (subgraph) → synthesizer → draft_gate → critic (bounded revise loop) → finalize → END. The FastAPI layer only streams graph events; React only renders them.
  • Document RAG over your own corpus — upload PDF / DOCX / TXT / MD → async ingestion (extract → chunk → embed → store) → hybrid retrieval (pgvector HNSW + Postgres full-text search, fused with Reciprocal Rank Fusion) exposed as a third corpus specialist. Findings carry a page/section locator, so the report can cite "your document · p.N". Works in-memory with a deterministic mock embedder; pgvector activates when DATABASE_URL is set.
  • Research-paper-style reports — abstract, background, thematic synthesis, discussion, limitations, and conclusion, with inline [n] citations. Export to PDF or Word, sources included.
  • Evaluation harness (cerebro-eval) — structural metrics (citation validity, grounding coverage, plan quality, retrieval recall@k) + optional LLM-judge faithfulness. Runs hermetically offline and gates CI.
  • Per-run observability — token / cost / latency streamed over SSE and shown in a live meter, cumulative across gates. Optional LangSmith tracing via LANGCHAIN_*.
  • Local run history — every run autosaves to the browser; reopen, review, or clear past runs from a history drawer, even after a reload.
  • Typed end-to-end contract — one source of truth: Pydantic → OpenAPI → generated TypeScript (frontend/lib/api-types.ts); a CI check guards drift.

Architecture

Backend (Python 3.12, FastAPI, LangGraph, SSE)          Frontend (Next.js App Router, React Flow)
────────────────────────────────────────────           ──────────────────────────────────────────
graph/            the StateGraph (source of truth)       app/            the research workspace
  planner ─▶ plan_gate ─▶ research_team (subgraph)       components/
             (HITL #1)      lead ─Send▶ web_search          graph/       live React Flow topology
                                       academic             conversation/ question + streamed report
                                       corpus (RAG)         hitl/        editable approval cards
                            reduce                          inspector/   live state + metrics meter
  ─▶ synthesizer ─▶ draft_gate (HITL #2)                    timeline/    checkpoints + "fork from here"
  ─▶ critic ⇄ (bounded revise loop)                         documents/   corpus upload + sources drawer
  ─▶ finalize (HITL #3) ─▶ END                            lib/            typed client + SSE + generated types

Runtime / system view — how a request flows across the stack:

flowchart LR
  subgraph Client["Browser · Next.js SPA (Cloudflare Pages)"]
    UI["React Flow topology<br/>HITL cards · streamed report"]
  end
  subgraph Server["FastAPI (Render container)"]
    SSE["SSE stream<br/>/threads · /runs · /resume · /fork"]
  end
  subgraph Graph["LangGraph StateGraph — single source of truth"]
    direction TB
    P[planner] --> PG{{plan_gate · HITL}}
    PG --> RT["research_team subgraph<br/>lead —Send▶ web · academic · corpus"]
    RT --> SY[synthesizer] --> DG{{draft_gate · HITL}}
    DG --> CR[critic]
    CR -->|revise ≤ bound| SY
    CR --> FZ{{finalize · HITL}}
  end
  Models["get_model / get_embedder<br/>Gemini · Claude · mock"]
  Search["SearchTool<br/>Tavily · arXiv · mock"]
  Corpus[("Corpus<br/>pgvector HNSW + FTS · RRF")]
  Ckpt[("Checkpointer<br/>SQLite / Postgres")]

  UI <-->|"HTTPS + SSE (fetch/ReadableStream)"| SSE
  SSE <--> Graph
  P -.-> Models
  SY -.-> Models
  CR -.-> Models
  RT -.-> Search
  RT -.-> Corpus
  Graph -.->|checkpoint every step| Ckpt
Loading

The compiled graph topology (also rendered to backend/docs/graph.png):

graph TD;
  __start__ --> planner;
  planner --> plan_gate;
  plan_gate -. reject .-> planner;
  plan_gate -. approve .-> research_team;
  research_team --> synthesizer;
  synthesizer -. first draft .-> draft_gate;
  synthesizer -. revision .-> critic;
  draft_gate -. reject .-> research_team;
  draft_gate -. approve .-> critic;
  critic -. revise (≤ bound) .-> synthesizer;
  critic -. pass .-> finalize;
  finalize -. request changes .-> synthesizer;
  finalize -. approve .-> __end__;
Loading

The graph is the single source of truth for orchestration — the FastAPI layer only streams graph events and relays human decisions; the React layer only renders them.

Tech stack

Backend Python 3.12, FastAPI (async), LangGraph, LangChain, SSE via sse-starlette, AsyncSqliteSaver checkpointer (Postgres-swappable), pgvector for the corpus, structlog, Pydantic
Frontend Next.js 16 (App Router), React 19, TypeScript (strict), TanStack Query, React Flow (@xyflow/react), Tailwind CSS v4 (tokenized theme), next-themes
Models Provider-agnostic get_model(role) / get_embedder() — Google Gemini or Anthropic Claude by env key, else a deterministic offline mock. Stronger tier for planner/critic, faster tier for workers.
Search / retrieval SearchTool interface — Tavily (web) + arXiv (academic) + corpus retrieval (pgvector HNSW + Postgres FTS, fused via RRF), each with a no-network mock fallback.
Evaluation cerebro-eval — offline structural metrics + optional LLM judge, gating CI.
Contract Pydantic models → OpenAPI → generated TypeScript (frontend/lib/api-types.ts).

Quick start

First time here? Follow the step-by-step Getting Started guide — install, run, and exactly how to approve / edit / reject at each human-in-the-loop gate. For a non-technical overview, open the intro slide deck.

Option A — Docker (one command)

docker compose up --build
# frontend → http://localhost:3000   backend → http://localhost:8000

Runs in offline mock mode by default. To use live models / search, create a root .env with any of GOOGLE_API_KEY, ANTHROPIC_API_KEY, TAVILY_API_KEY (and DATABASE_URL for pgvector) first.

Option B — Local dev

One command (both services): from the project root, run python dev.py — it starts the backend and frontend together, prefixes each one's logs, and stops both on Ctrl+C. Prefer separate terminals? Use the two blocks below.

Backend (needs uv):

cd backend
cp .env.example .env            # optional; the app runs with zero keys
uv sync                          # install deps
uv run cerebro-api               # serve at http://127.0.0.1:8000

Frontend (needs Node 20+):

cd frontend
npm install
npm run dev                      # http://localhost:3000

The frontend reads the backend URL from NEXT_PUBLIC_API_BASE_URL (default http://127.0.0.1:8000).

Adding keys

Whichever key is present is used, in priority Google (Gemini) → Anthropic (Claude) → mock:

# backend/.env
GOOGLE_API_KEY=...        # gemini-2.5-pro (planner/critic) + gemini-2.5-flash (workers) + embeddings
# or
ANTHROPIC_API_KEY=...     # claude-sonnet-5 / claude-haiku-4-5
TAVILY_API_KEY=...        # live web search (else mock)
DATABASE_URL=...          # pgvector Postgres for the corpus (else in-memory + mock embedder)

Using it

Ask a question → pause at the plan gate and edit the sub-questions → watch the hierarchical team fan out on the live graph → review / edit the draft → see the critic loop → approve the final report → export it to PDF / Word (sources included), or open the checkpoint timeline and fork a different branch from an earlier state.

Plan gate (HITL) Streaming report + graph
Plan gate Draft report

The CLI (no web layer)

cd backend
uv run cerebro "Is intermittent fasting actually good for you?"        # answer gates from stdin
uv run cerebro --auto "Do electric cars really help the environment?"   # auto-approve every gate
uv run cerebro-eval                                                     # run the eval harness

The HTTP API (SSE)

Method Path Purpose
POST /threads create a research session → {thread_id}
POST /threads/{id}/runs start a run — streams node transitions, findings, report tokens, metrics, and interrupt events
POST /threads/{id}/resume resume from an interrupt with a decision (+ edited plan/draft)
GET /threads/{id}/state current graph state
GET /threads/{id}/history checkpoint history (time-travel timeline)
POST /threads/{id}/fork edit a past checkpoint and branch a new run
GET /threads/{id}/report.pdf · report.docx download the report (sources included)
GET, POST, DELETE /documents … upload / list / delete corpus documents
GET /meta/graph the real node/edge topology (for the graph view)
GET /meta/examples a fresh, randomly-sampled set of starter questions

Interactive docs at http://127.0.0.1:8000/docs.

Development

# Backend
cd backend
uv run ruff check src && uv run ruff format --check src   # lint + format
uv run mypy src                                           # strict type check
uv run pytest                                             # tests (interrupt→resume, fork, RAG, export, evals)
uv run cerebro-eval                                       # eval harness (gates CI)
uv run python scripts/dump_openapi.py                     # regenerate OpenAPI after any Pydantic change
uv run python scripts/render_graph.py                     # regenerate backend/docs/graph.png + .mmd

# Frontend
cd frontend
npm run gen:api      # regenerate lib/api-types.ts from the backend OpenAPI
npx tsc --noEmit     # type check
npm run build        # production build
npx jest             # component + storage tests

Project layout

backend/
  src/cerebro/
    config.py         typed settings singleton (pydantic-settings)
    models.py         get_model(role) + offline FakeStreamingChatModel
    embeddings.py     get_embedder() + deterministic MockEmbedder
    observability.py  per-run token/cost/latency accounting (SSE run_metrics)
    graph/            state, nodes, gates, research_team subgraph, reasoning, build, checkpointer
    corpus/           CorpusStore (in-memory + pgvector), hybrid retrieval + RRF, chunker, schema.sql
    ingestion/        extract / chunk / embed pipeline + in-process queue worker
    tools/            SearchTool (Tavily + mock), arXiv, corpus retrieval
    schemas/          Pydantic API + SSE-event + document models (the wire contract)
    api/              FastAPI app, SSE mapping, routes (threads / runs / documents), export
    evals/            cerebro-eval harness: dataset, metrics, judge, runner, CI report
    cli.py            end-to-end CLI driver
  scripts/            dump_openapi.py, render_graph.py
  tests/              pytest suite
frontend/
  app/                workspace, layout, providers, globals
  components/         graph / conversation / hitl / inspector / timeline / documents / history / ui
  lib/                typed client, SSE parser, query hooks, localStorage run store, generated types
  design-system/      DESIGN.md + tokens.css
docker-compose.yml        zero-key local demo
docker-compose.prod.yml   hardened production stack (pgvector + Postgres checkpointer)

Deployment

Full walkthrough: docs/DEPLOYMENT.md. Nothing deploys automatically — every step needs your own cloud accounts and is confirmation-gated.

Recommended topology

Browser --HTTPS/SSE--> Cloudflare Pages (static Next.js export, client SPA)
                              |
                     cross-origin HTTPS (CORS)
                              v
               FastAPI + LangGraph container (Render, free)  <-- UptimeRobot pings
                              |                                   /health every 5 min
                              v                                   (no cold starts)
                 pgvector Postgres  (checkpointer + corpus vectors)
  • Frontend → Cloudflare Pages. The web app is a pure client SPA, so it ships as a static export (output: export → ./out). No Cloudflare adapter needed.
  • Backend → a container host, not a Worker. FastAPI + LangGraph + long-lived SSE + a Postgres pool is a container workload. Recommended default: Render (render.yaml, free tier); Fly.io (backend/fly.toml) and Railway work too. The same backend/Dockerfile runs everywhere.
  • Keep-warm on free hosting: Render's free instance sleeps after ~15 min idle, so an UptimeRobot monitor pings /health every 5 minutes to spare visitors the ~1 min cold start.
  • Database: one pgvector Postgres serves both the LangGraph checkpointer (POSTGRES_DSN) and the corpus vector store (DATABASE_URL).
  • Offline demo survives deployment: with no provider keys the live site still runs the deterministic mock model + mock search/embeddings end to end.

Build/deploy artifacts: hardened multi-stage backend/Dockerfile and frontend/Dockerfile; docker-compose.prod.yml; frontend/wrangler.toml for Pages; render.yaml for the backend service; and .github/workflows/deploy.yml, which deploys both on every push to main (Pages via Wrangler, Render via its API, waiting for the deploy to go live). Credentials, free-tier caveats, the UptimeRobot keep-warm setup, and the go-live checklist are in docs/DEPLOYMENT.md.

Notes

  • Persistence: SQLite by default (AsyncSqliteSaver). Set CHECKPOINTER=postgres + POSTGRES_DSN (install the postgres extra: uv sync --extra postgres) to swap in Postgres without touching graph code. CHECKPOINTER=memory is used for tests.
  • Corpus: in-memory store + deterministic mock embedder with zero keys; set DATABASE_URL to activate pgvector (HNSW index) with Postgres full-text search fused via RRF.
  • Streaming: SSE is consumed with fetch + a ReadableStream reader (POST streams). Report tokens, findings, and metrics stream over LangGraph's custom channel; node transitions and interrupts over updates.
  • Observability: structured logs (human-readable locally, JSON in prod), a live per-run token/cost/latency meter, and optional LangSmith tracing via the standard LANGCHAIN_* variables.

Theme

Light and dark are both hand-tuned.

Dark mode

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages