A lightweight, English-first multi-agent research assistant: give it a question, it drafts a research plan for your approval, searches the web, reflects on what is missing, and writes a cited report — streaming every step to the browser.
Built with LangGraph (agent orchestration) + FastAPI (API and SSE) + React 19 / Vite / Tailwind (UI).
Why "Lite"? This is a deliberately stripped-down edition of a larger enterprise DeepResearch system. Authentication, the PostgreSQL user store and the operational scaffolding were removed so the whole pipeline fits in one repository you can read in an afternoon. See
docs/en/adr/006-lite-edition-scope.mdfor exactly what was kept and what was dropped, and why.
- How it works
- Architecture
- Quick start
- Configuration
- Using the app
- Quality gates
- Design decisions
- Project layout
- Limitations and roadmap
- License and provenance
The pipeline is three composable stages, each an independently testable LangGraph subgraph:
| Stage | Nodes | What happens |
|---|---|---|
| 1. Plan (human-in-the-loop) | generate_plan → evaluate_plan → confirm_plan / replan / awaiting_plan_confirmation |
The agent turns your question into a structured research plan and stops. Nothing is searched until you confirm (Confirm plan). Unconfirmed plans are routed back to replan. |
| 2. Research (subgraph) | generate_queries → web_search (fan-out, one per query) → critique → loop |
Queries are generated, searched in parallel via Send() fan-out, then a reflection step decides whether the evidence is sufficient. If not, it loops with follow-up queries until max_research_loops. |
| 3. Write (subgraph) | outline → draft ⇄ critic_review → cite_and_polish |
An outline is drafted, each section written, a critic scores the draft and can send it back for revision (bounded by max_revisions), then citations are resolved and the report is polished. |
Every node transition and every streamed token is published to a resumable SSE stream, so the browser shows live progress and can reconnect without losing events.
| Diagram | What it shows |
|---|---|
main_graph_overview.png |
All four execution lanes, the HITL pause and the shared state |
research_subgraph.png |
generate_queries → parallel web_search → critique loop |
writer_subgraph_with_review.png |
outline → draft ⇄ critic_review → cite_and_polish |
human_in_the_loop.png |
Run 1 pauses; run 2 resumes the same thread as confirmed |
basic_graph.png |
The straight-through chain with each node's role |
multi_agent_graph.png |
The three agents, their prompts and the shared OverallState |
Runtime sketch
Browser (React 19 + Vite)
│ POST /api/research ─────────────► FastAPI custom app (backend/src/agent/app.py)
│ GET /api/research/{id}/stream ◄── resumable SSE (events carry their Redis stream id)
▼
Redis Streams ──► background worker ──► LangGraph graph (checkpointed in Redis)
(task queue) (single coroutine) │
├─ LLM: any OpenAI-compatible endpoint
├─ Web search: Exa (or DashScope MCP app)
├─ Cache: Redis (per-query search cache)
└─ Knowledge base: Milvus (optional) + embeddings
Prerequisites: Python 3.11+ (3.13 tested) · Node 20+ (24 tested) · a running Redis (task queue, search cache and LangGraph checkpoints).
python3.13 -m venv .venv
cd backend
../.venv/bin/python -m pip install -e ".[dev]" # Windows: ..\.venv\Scripts\python.exe
cp .env.example .env # then fill in your keysStart Redis if you don't have one running:
docker run -d --name deepresearch-redis -p 6379:6379 redis:7-alpineThen launch the backend from backend/:
# Linux / macOS
PYTHONUTF8=1 ../.venv/bin/langgraph dev --no-browser --no-reload --allow-blocking
# Windows (cmd)
set PYTHONUTF8=1
..\.venv\Scripts\langgraph.exe dev --no-browser --no-reload --allow-blockingWindows note —
PYTHONUTF8=1is required.langgraph-apireads its bundled OpenAPI spec with the active code page; on systems using a legacy code page (e.g. cp932) startup dies withUnicodeDecodeErrorbefore the server binds. The repo-root wrappersrun_backend.bat/run_backend.shset it for you.
The backend serves http://127.0.0.1:2024 — the custom API plus LangGraph Studio at
https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024.
cd frontend
npm install
npm run dev # http://localhost:5173//api, /threads, /runs and /assistants are proxied to http://127.0.0.1:2024
(override with BACKEND_URL=... npm run dev). On Windows you can double-click
run_backend.bat and run_frontend.bat instead.
Open http://localhost:5173/, enter a question, pick effort and model, then send. When the plan appears, review it and press Confirm plan to start the research run.
All backend settings live in backend/.env (see .env.example for the annotated template).
| Variable | Required | Purpose |
|---|---|---|
APP_TOKEN |
yes | API key for your LLM endpoint |
LLM_BASE_URL |
yes | Any OpenAI-compatible base URL (default: https://api.deepseek.com) |
AVAILABLE_MODELS |
yes | JSON array of models offered to the UI. Every model_id must exist on your endpoint — the first entry is the default, the middle one is used for reflection |
EXA_API_KEY |
recommended | Web search via Exa. When set, it takes precedence |
MCP_APP_ID |
fallback | DashScope MCP web-search application, used when no Exa key is configured |
REDIS_URL |
yes | Task queue, event streams, search cache and checkpoints |
WEB_SEARCH_MAX_QPS |
no | Client-side search throttle (default 12) |
MILVUS_URI, EMBEDDING_* |
no | Knowledge base. Without it the KB silently degrades and the pipeline still runs |
KB_LIFECYCLE_MODE |
no | off / inform / freshness (default) / lifecycle — freshness filtering and confidence decay |
RERANKER_* |
no | Optional cross-encoder reranking (disabled by default) |
QUERY_GENERATOR_MODEL, REFLECTION_MODEL, ANSWER_MODEL |
no | Pin a model per stage instead of deriving it from AVAILABLE_MODELS |
- Ask — type a research question and choose an effort level
(low/medium/high map to different
initial_search_query_count/max_research_loops). - Review the plan — the plan is not a formality: research does not start until you press Confirm plan, and you can keep the conversation going to refine it.
- Watch it work — the activity timeline shows each node (
Generating plan,Web research,Reflection and analysis,Generating outline,Writing draft,Polishing and adding citations,Finalizing answer) with streamed tokens. - Read the report — the final answer arrives as Markdown with resolved source links.
Every gate below is enforced in .github/workflows/ci.yml:
cd backend
../.venv/bin/python -m ruff check . # lint — currently clean
../.venv/bin/python -m pytest test -q # 296 passed, 4 skipped
../.venv/bin/python ../scripts/check_no_cjk.py # English-only gate
cd ../frontend && npx tsc -b && npm run build # typecheck + production build- Hermetic tests — the suite mocks LLM, search, Redis and Milvus, so it runs with no infrastructure (verified with Redis deliberately unreachable).
- English-only gate —
scripts/check_no_cjk.pywalks every shipped text file and fails if any CJK character survives, withdocs/zh-CN/andREADME.zh-CN.mdas the only bilingual exceptions. - Language contract — prompts are English and carry an explicit "always write your output in English" instruction, so reports stay English regardless of input language.
The reasoning behind the non-obvious choices is recorded as ADRs in docs/en/adr/:
| ADR | Decision |
|---|---|
| 001 | LangGraph + a pluggable tool stack instead of a bespoke agent loop |
| 002 | Pause for plan confirmation before spending search/LLM budget |
| 003 | Split the monolith into independently testable subgraphs |
| 004 | Classify failures (transient vs permanent) and retry only what can succeed |
| 005 | Redis Streams task queue with a resumable SSE contract |
| 006 | What this edition drops (auth, PostgreSQL) and why |
DeepResearchLite/
├── backend/
│ ├── src/agent/
│ │ ├── app.py # FastAPI app: /api/models, /api/research, SSE stream
│ │ ├── graph.py # main graph: plan phase + HITL routing
│ │ ├── sub_agents/ # research_agent (query→search→reflect), writer_agent
│ │ ├── base_agent.py # Agent / JsonAgent / WebSearchAgent / ExaSearchAgent
│ │ ├── task_queue.py # Redis Streams queue, worker, resumable event stream
│ │ ├── prompts.py # all prompt templates (English)
│ │ ├── configuration.py # runtime configuration + model selection
│ │ ├── search_cache.py # per-query Redis cache
│ │ ├── reranker.py # optional cross-encoder reranking
│ │ └── kb/ # optional Milvus knowledge base (facts + lifecycle)
│ ├── eval/ # optional LLM-as-judge benchmark harness
│ ├── test/ # 300 tests
│ └── pyproject.toml # deps + ruff/pytest config
├── frontend/src/ # React UI: plan confirmation, timeline, streamed report
├── docs/
│ ├── diagrams/ # English architecture diagrams (generated by scripts/)
│ ├── en/adr/ # architecture decision records
│ └── zh-CN/ # Chinese long-form docs (bilingual exception)
├── scripts/
│ ├── check_no_cjk.py # English-only gate
│ └── render_graph_diagrams_en.ps1
├── run_backend.bat / .sh # one-command startup
└── run_frontend.bat / .sh
Honest list — these are the things a reviewer should know before running it:
- Single-user by design. No accounts, no per-user isolation; the API is meant to run on localhost. Do not expose it to the internet as-is.
- Redis is required for the task queue, event streams, search cache and checkpoints.
- The knowledge base is optional and off the happy path. Without Milvus the KB layer degrades silently; enabling it also requires an embedding endpoint. It has not been exercised end-to-end in this edition.
- Latency is dominated by the model. A local run of the sample question produced a
~52,000-character report from 40 web sources in about 12 minutes with
initial_search_query_count=1. The writer's critique loop revised the draft four times; loweringmax_revisionsor the effort level shortens runs substantially. - The dev server is not a deployment target.
langgraph devis an in-memory runtime for development.frontend/nginx.confshows the production shape (static SPA + reverse proxy with SSE buffering disabled). - No demo GIF yet. The architecture diagrams under
docs/diagrams/are generated byscripts/render_graph_diagrams_en.ps1; a screen capture of a live run is still to be added.
License has been granted. This codebase was built as a hands-on project from a training course, adapted and re-implemented into an English-first lite edition; This project/re-implemention has been authorized.
