A stateful multi-agent AI system that transforms any research topic into a structured, sourced report — autonomously.
Four specialized agents. One human checkpoint. Zero garbage output.
Most LLM "research" tools are single-prompt wrappers. This is different.
Polyagentic Research Assistant implements a proper multi-agent workflow using LangGraph — a stateful graph engine with real checkpointing. Five agents collaborate in a supervised loop: a Supervisor orchestrates routing, a Researcher queries the live web, a Writer drafts structured reports, and a Critiquer enforces quality through iterative revision.
The critical design choice: a Human-in-the-Loop gate sits at the research boundary. Before any writing begins, you review and optionally edit the raw findings. This single intervention prevents the "garbage in, garbage out" problem that makes fully-automated research tools unreliable.
flowchart TD
START(["User Input"]) --> SV
SV["Supervisor\nRouter"]
SV -->|no research| RS
RS["Researcher\nTavily + LLM"]
RS --> HR
HR{{"HITL Review Gate\nYou review findings"}}
HR -->|approve| SV
HR -->|edit + approve| SV
HR -->|re-search| RS
SV -->|write draft| WR
WR["Writer\nStructured Draft"]
WR --> CR
CR["Critiquer\nQuality Check"]
CR -->|approved| END
CR -->|revisions| SV
SV -->|max revisions| END
END(["Final Report"])
style START fill:#1a1a1a,color:#fff,stroke:#ff4b4b,stroke-width:2px
style END fill:#1a1a1a,color:#fff,stroke:#22c55e,stroke-width:2px
style HR fill:#ff4b4b,color:#fff,stroke:#ff4b4b,stroke-width:2px
style SV fill:#2d2d2d,color:#fff,stroke:#6366f1,stroke-width:2px
style RS fill:#2d2d2d,color:#fff,stroke:#3b82f6,stroke-width:2px
style WR fill:#2d2d2d,color:#fff,stroke:#f59e0b,stroke-width:2px
style CR fill:#2d2d2d,color:#fff,stroke:#8b5cf6,stroke-width:2px
flowchart LR
S1["01 Enter topic"] --> S2
S2["02 Supervisor routes"] --> S3
S3["03 Researcher\nqueries Tavily"] --> S4
S4["04 LLM distills\n5 sourced bullets"] --> S5
S5{{"05 You review\nedit or approve"}} --> S6
S6["06 Supervisor routes"] --> S7
S7["07 Writer drafts\nstructured report"] --> S8
S8["08 Critiquer\nevaluates quality"] --> S9
S9["09 Final Report"]
S8 -->|"revisions"| S6
style S5 fill:#ff4b4b,color:#fff,stroke:#ff4b4b
style S9 fill:#1a1a1a,color:#fff,stroke:#22c55e,stroke-width:2px
style S1 fill:#2d2d2d,color:#fff,stroke:#555
style S2 fill:#2d2d2d,color:#fff,stroke:#6366f1
style S3 fill:#2d2d2d,color:#fff,stroke:#3b82f6
style S4 fill:#2d2d2d,color:#fff,stroke:#3b82f6
style S6 fill:#2d2d2d,color:#fff,stroke:#6366f1
style S7 fill:#2d2d2d,color:#fff,stroke:#f59e0b
style S8 fill:#2d2d2d,color:#fff,stroke:#8b5cf6
| # | Agent | Responsibility | Key Design |
|---|---|---|---|
| 01 | Supervisor | Central router — decides which agent acts next | Deterministic state-based rules first; LLM fallback only when logic is ambiguous. Prevents routing failures. |
| 02 | Researcher | Web search + LLM summarisation | Queries Tavily (live web), distills to exactly 5 sourced bullet points. Source URLs preserved inline. |
| 03 | HITL Review Gate | Human checkpoint — pause, review, edit, or redirect | Implemented as a LangGraph interrupt_before node. State is checkpointed — the graph can resume after human input. |
| 04 | Writer | Structured report generation and revision | Enforces Key Takeaway → Findings → Analysis → Bottom Line schema. Revises against critiquer notes. |
| 05 | Critiquer | Quality gate — approve or return concrete fixes | Evaluates 4 criteria: relevance, source fidelity, substance, structure. Approves at 80% quality. Returns max 3 actionable fixes (not vague advice). |
The Supervisor evaluates workflow state with hardcoded rules before ever calling the LLM. If critique says APPROVED and a draft exists → route to END. If no research exists → route to researcher. This eliminates an entire class of failures caused by LLM JSON parsing errors or hallucinated route decisions.
There is exactly one human checkpoint: after research, before writing. This is the highest-leverage intervention point. Bad source material propagates through every downstream step — writing, critique, and revision can't fix fundamentally wrong facts. One early review prevents wasted compute cycles.
research_findings uses Annotated[List[str], operator.add] in the TypedDict state. Findings accumulate across research cycles rather than being overwritten. Re-searching appends to the pool, preserving prior context.
Maximum 3 critique → writer cycles. The Critiquer prompt is tuned to approve at 80% quality and cap feedback at 3 concrete, scoped instructions — making the automated loop reliable enough to run without further human intervention.
Users switch between Groq (cloud, fast) and Ollama (local, private) at runtime via the sidebar. The _get_llm() factory handles instantiation and falls back gracefully on failure. No API key required in Ollama mode.
| Layer | Technology | Purpose |
|---|---|---|
| Orchestration | LangGraph StateGraph |
Stateful agent workflow with MemorySaver checkpointing |
| LLM Framework | LangChain | Chain construction, prompt templates, LLM abstraction |
| Cloud LLM | Groq | Ultra-fast inference — llama-3.3-70b, mixtral-8x7b, gemma2-9b |
| Local LLM | Ollama | Self-hosted inference, any model |
| Web Search | Tavily Search API | Real-time web research with structured results |
| Frontend | Streamlit | Custom Brutalist UI with CSS design system |
| Package Manager | uv | Fast Python package management |
| Testing | pytest | 55 unit tests, 100% offline (all LLM calls mocked) |
polyagentic-research-assistant/
│
├── app.py # Streamlit entry point — state-machine UI router
├── graph.py # LangGraph StateGraph — nodes, edges, compilation
├── agents.py # Agent factory functions + dynamic LLM provider
├── prompts.py # All prompt templates (supervisor, writer, critiquer)
│
├── ui/
│ ├── __init__.py
│ ├── style.py # Brutalist CSS design system (variables, components)
│ ├── sidebar.py # Sidebar config — LLM provider, model, iterations
│ ├── state.py # Session state initialisation + API key validation
│ └── stream_handler.py # Live agent log, pipeline header, header downgrader
│
├── tests/
│ ├── test_agents.py # 35 tests — all agent chains, LLM factory, error paths
│ ├── test_graph.py # 16 tests — graph nodes, routing, state schema
│ └── test_tools.py # 4 tests — LLM compatibility helper (_call_llm)
│
├── docs/
│ ├── high_level_design.md # Architecture overview and design decisions
│ └── low_level_design.md # Node-by-node implementation details
│
├── .env.example # Environment variable template
├── pyproject.toml # Project config + pytest settings
└── requirements.txt # Pip-installable dependencies
- Python 3.11+
- A Groq API key — free, no credit card required
- A Tavily API key — free tier: 1,000 searches/month
- (Optional) Ollama running locally for private inference
# Clone the repository
git clone https://github.com/virtualvasu/polyagentic-research-assistant.git
cd polyagentic-research-assistant
# Install with uv (recommended — significantly faster than pip)
pip install uv
uv pip install -r requirements.txt
# Or with standard pip
pip install -r requirements.txtcp .env.example .envEdit .env:
# Required
GROQ_API_KEY=gsk_...
TAVILY_API_KEY=tvly-...
# Optional — only needed if using Ollama local inference
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODELS=llama3.1:latest,llama3.1:8b,qwen2.5:7bstreamlit run app.pyOpen http://localhost:8501.
Run research entirely offline — no Groq key required (Tavily key still needed for web search).
# Pull a model
ollama pull llama3.1:latest
# Or smaller, faster option
ollama pull qwen2.5:7bIn the Streamlit sidebar, switch LLM Provider → Ollama and select your pulled model. The _get_llm() factory handles the rest.
All 55 tests run fully offline — every LLM and Tavily call is mocked with unittest.mock.
# Using the pyenv Python that has all dependencies
/home/netweb/.pyenv/versions/3.11.14/bin/python -m pytest tests/ -v
# Or if your env is set up correctly
pytest tests/ -vTest coverage breakdown:
| File | Tests | What's covered |
|---|---|---|
test_agents.py |
35 | _call_llm, _get_llm, Supervisor routing (all branches), Researcher (search, errors, edge cases), Writer (HITL path, error propagation), Critiquer (approve/reject/max-revisions) |
test_graph.py |
16 | Graph compilation, all 5 node functions, state transitions, ResearchState schema validation |
test_tools.py |
4 | LLM compatibility helper (invoke/run/callable fallback chain) |
1. Enter topic → "Post-quantum cryptography adoption timeline"
2. Supervisor → Routes to Researcher (no findings in state)
3. Researcher → Queries Tavily, LLM condenses to 5 sourced bullets
4. [ YOU ] → Review findings. Edit if needed. Approve or re-search.
5. Supervisor → Routes to Writer (findings confirmed by human)
6. Writer → Produces: Key Takeaway / Findings / Analysis / Bottom Line
7. Critiquer → Evaluates 4 quality criteria — approves or returns ≤3 fixes
8. Loop → Writer revises, Critiquer re-evaluates (max 3 cycles)
9. Final Report → Displayed with word count, revision stats, download button
| Setting | Default | Description |
|---|---|---|
| Max Iterations | 15 | LangGraph recursion limit — prevents infinite loops |
| LLM Provider | Groq | Switch between Groq (cloud) and Ollama (local) at runtime |
| Model | llama-3.3-70b-versatile |
Applied to all agent chains simultaneously |
| Ollama Host | http://localhost:11434 |
Only shown when Ollama is selected |
- Persistent checkpoints — replace
MemorySaverwithSqliteSaverfor cross-session history - RAG mode — ChromaDB integration for querying user-uploaded documents alongside web search
- Evaluation agent — automated report scoring on source fidelity, coverage, and conciseness
- FastAPI backend — decouple agent workflow from frontend, expose REST API with Swagger docs
- LangSmith integration — full trace observability, token usage, and latency dashboards
- HuggingFace Spaces deployment — live public demo
MIT — see LICENSE for details.