Skip to content

ki11e6/langgraph-multi-agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧠 Multi-Agent Research Assistant

A multi-agent AI system built with LangGraph that turns a single question into a polished, well-researched report. A team of specialized agents — a Researcher, a Writer, and a Reviewer — collaborate under a central Supervisor that orchestrates the workflow as a stateful graph.

It demonstrates the supervisor / orchestrator-worker pattern for agentic systems, dynamic LLM-based routing via conditional edges, an iterative writer↔reviewer refinement loop, tool use for live web search, and a fully provider-agnostic LLM layer (Groq, Gemini, OpenAI, or Anthropic — switchable with one environment variable).


✨ Features

  • Multi-agent orchestration — cooperating agents modeled as nodes in a LangGraph StateGraph.
  • Dynamic supervisor routing — a coordinator node decides which agent runs next based on shared state, instead of a brittle hard-coded pipeline.
  • Grounded, cite-or-refuse output — the Writer must cite retrieved sources with footnote markers ([^n]); a deterministic validate node refuses to ship a draft that has no sources, no citations, or citations pointing at sources that don't exist (or that were retrieved empty). See the incident write-up for why.
  • Iterative refinement — the Writer and Reviewer loop until the draft is accepted or MAX_REVISIONS is reached; termination is decided deterministically, so the loop provably halts.
  • Live web research — the Researcher retrieves keyless DuckDuckGo results and fetches the page text, carrying structured sources (title, URL, content) through the graph.
  • Provider-agnostic LLM layer — run on Groq (free, default), Gemini, OpenAI, or Anthropic by changing LLM_PROVIDER.
  • Streaming output — node-by-node progress is streamed to the terminal as the graph executes.
  • Human-in-the-loop (opt-in)--human-review pauses the graph (LangGraph interrupt + a MemorySaver checkpointer) so a human makes the final accept/revise call, overriding the model's verdict. Because a human bounds the loop, a "revise" is honored even past MAX_REVISIONS. Resumes via Command(resume=...).
  • Observability — LangSmith tracing auto-instruments every LLM/graph step when env vars are set; startup reports whether it's on.
  • Zero-cost by default — Groq's free tier + keyless search means it runs without any paid API or credit card.

🏗️ Architecture

graph TD;
    __start__([start]) --> supervisor
    supervisor -.->|no sources| researcher
    supervisor -.->|sources, no draft| writer
    researcher --> supervisor
    writer --> validate
    validate -.->|grounded| reviewer
    validate -.->|ungrounded → refuse| __end__([END])
    reviewer -.->|REVISE| writer
    reviewer -.->|ACCEPT / max revisions| __end__
Loading

This diagram is generated from the compiled graph itself: build_graph().get_graph().draw_mermaid().

Two design choices worth calling out:

  • The Supervisor sequences the early work (research → first draft), but it does not own termination. Once a draft exists, control flows writer → validate → reviewer, and the Reviewer's verdict deterministically ends the loop (via route_after_review) rather than asking the LLM Supervisor to notice "ACCEPT." Delegating termination to the LLM was the original cause of a near-infinite loop.
  • validate is a deterministic gate, not an agent. The LLM proposes a draft; plain Python disposes on whether it's grounded enough to ship. An agent graph shouldn't be all model calls — some edges are boring, testable code. This is what turns "confident hallucination" into "grounded answer or honest refusal."

Agent roles

Agent Responsibility Tools
Supervisor Inspects shared state and routes the early work (research → first draft). LLM-based routing
Researcher Retrieves web results and fetches page text, keeping structured sources (title, URL, content) in state — provenance is preserved, not summarised away. web_search (DuckDuckGo + page fetch)
Writer Transforms sources into a markdown report, citing each claim [^n]; incorporates reviewer feedback on revisions. LLM generation
Validate Deterministic (no LLM). Refuses drafts with no sources, no citations, dangling citations, or citations to empty-content sources. plain Python
Reviewer Critiques the draft for accuracy, clarity, and completeness; issues an ACCEPT or REVISE verdict that deterministically ends or continues the loop. LLM evaluation

⚙️ How it works

The system is a state machine. A single typed AgentState object flows through the graph, and each node returns a partial update that LangGraph merges into it.

Shared state (AgentState):

Field Purpose
messages Running conversation log (uses the add_messages reducer to append).
sources Structured {title, url, snippet, content} retrieved by the Researcher — the evidence every claim must trace back to.
research_notes Numbered, URL-preserving view of the sources handed to the Writer.
draft Current report produced by the Writer, with footnote [^n] citations.
review_feedback Reviewer's critique; consumed and cleared by the Writer on each revision.
verdict Reviewer's ACCEPT/REVISE decision — persisted so routing is deterministic.
validation_error Set by the validate node when a draft isn't grounded; triggers refusal.
next_agent The Supervisor's routing decision.
revision_count Counts revision cycles; caps the loop at MAX_REVISIONS (3).

Execution flow for one query:

  1. Supervisor sees no sources → routes to Researcher.
  2. Researcher searches + fetches pages → writes sources and research_notes.
  3. Supervisor sees sources but no draft → routes to Writer.
  4. Writer drafts a report citing [^n] → writes draft.
  5. Validate (deterministic) checks grounding → ungrounded ends the run with a refusal; groundedReviewer.
  6. Reviewer critiques → ACCEPT or REVISE (verdict persisted to state).
  7. REVISE → back to Writer (loop); ACCEPT or MAX_REVISIONSEND.

🧩 Tech stack

Component Technology
Orchestration LangGraph (StateGraph, conditional edges)
LLM abstraction LangChain (langchain-core)
LLM — Groq (default) Llama 3.3 70B via langchain-groq
LLM — Gemini gemini-2.5-flash-lite via langchain-google-genai
LLM — OpenAI GPT-4o via langchain-openai
LLM — Anthropic Claude Sonnet 4.5 via langchain-anthropic
Web search DuckDuckGo (ddgs) via langchain-community — no API key
Page fetching httpx + stdlib HTML parsing (best-effort, falls back to snippets)
State & validation Pydantic v2
Configuration python-dotenv
Build backend Hatchling
Package manager uv
Linting Ruff
Containerization Docker (Python 3.12 slim)

🚀 Getting started

Prerequisites

  • Python 3.12+
  • uv (curl -LsSf https://astral.sh/uv/install.sh | sh)
  • A free LLM API key (Groq recommended — no credit card)

Install & run

# 1. Clone
git clone https://github.com/<your-username>/langgraph-multi-agent.git
cd langgraph-multi-agent

# 2. Configure environment
cp .env-template .env
# Edit .env and add your GROQ_API_KEY (free at https://console.groq.com/keys)

# 3. Install dependencies (creates .venv)
uv sync

# 4. Run a research query
uv run python main.py "What are the latest breakthroughs in quantum computing?"

# Verbose mode prints the full output of every agent
uv run python main.py --verbose "Explain the current state of nuclear fusion energy"

# Human-in-the-loop: pause for your approve/revise decision before finishing
uv run python main.py --human-review "How do I connect Langfuse to LangGraph?"

🔧 Configuration

Set the provider with LLM_PROVIDER in .env and supply the matching key:

Variable Required Description
LLM_PROVIDER No groq (default), gemini, openai, or anthropic
GROQ_API_KEY If groq Groq — free tier, no credit card
GOOGLE_API_KEY If gemini Google AI Studio
OPENAI_API_KEY If openai OpenAI
ANTHROPIC_API_KEY If anthropic Anthropic
LLM_MODEL No Override the provider's default model
LANGSMITH_TRACING No Set to true (with LANGSMITH_API_KEY) to trace every step in LangSmith

Web search uses DuckDuckGo and requires no API key.


🔭 Tracing (LangSmith)

Every LLM call and graph transition can be traced to LangSmith — the full supervisor → researcher → writer → validate → reviewer tree, token counts, latencies, and each node's inputs/outputs. It's auto-instrumented: no code wires it up, you just set env vars.

Setup (~2 min, free tier — no card):

  1. Create a key at smith.langchain.comSettings → API Keys → Create API Key (lsv2_...).
  2. Add to your .env:
    LANGSMITH_TRACING=true
    LANGSMITH_API_KEY=lsv2_your_key_here
    LANGSMITH_PROJECT=langgraph-multi-agent   # optional; defaults to "default"
  3. Run normally — the startup banner confirms it:
    LangSmith tracing: ON (project='langgraph-multi-agent')
    
    Traces then appear in the LangSmith UI under that project.

How it works: .env is loaded (load_dotenv() in agents/config.py) before the first LLM call, so LangChain/LangGraph pick up the vars and stream traces automatically. The only project code involved is the startup status line in main.py — the instrumentation is the framework's.

Notes:

  • langsmith ships as a LangChain dependency, so nothing extra to install.
  • Leave LANGSMITH_TRACING unset/false until the key is in — enabling it without a valid key just fails to upload (and adds slight latency).
  • Older LANGCHAIN_TRACING_V2 / LANGCHAIN_API_KEY names also work; the startup check honors both.

📤 Example output

$ uv run python main.py "How do I connect Langfuse to LangGraph?"

============================================================
  Research query: How do I connect Langfuse to LangGraph?
  LangSmith tracing: off (set LANGSMITH_TRACING=true + LANGSMITH_API_KEY to enable)
============================================================

--- [SUPERVISOR] ---   [Supervisor] Routing to: researcher
--- [RESEARCHER] ---   [Researcher] Retrieved 5 source(s).
--- [SUPERVISOR] ---   [Supervisor] Routing to: writer
--- [WRITER] ---       [Writer] Draft produced (2740 chars)
--- [VALIDATE] ---     [Validate] grounded
--- [REVIEWER] ---     [Reviewer] Verdict: REVISE
--- [WRITER] ---       [Writer] Draft produced (5606 chars)
--- [VALIDATE] ---     [Validate] grounded
--- [REVIEWER] ---     [Reviewer] Verdict: ACCEPT

============================================================
  FINAL REPORT
============================================================

## Overview
Langfuse is an open-source LLM-engineering platform that traces API calls,
manages prompts, and runs evaluations [^4][^5]. ...

## Sources
[1] Open Source Observability for LangGraph - Langfuse — https://langfuse.com/guides/cookbook/integration_langgraph
[4] Langfuse integrations - Docs by LangChain — https://docs.langchain.com/oss/python/integrations/providers/langfuse
[5] Get Started - Langfuse — https://langfuse.com/docs/observability/get-started

Every claim carries a [^n] citation and the run ends with a ## Sources list. If nothing can be grounded, the tool prints an INSUFFICIENT GROUNDING notice and exits non-zero instead of inventing an answer.


🐳 Docker

docker build -t research-assistant .
docker run --env-file .env research-assistant "Your research query here"

The image uses a multi-stage build with uv for fast, reproducible dependency installation on a python:3.12-slim base.


📁 Project structure

langgraph-multi-agent/
├── agents/
│   ├── __init__.py
│   ├── config.py      # Provider-agnostic LLM factory (Groq/Gemini/OpenAI/Anthropic)
│   ├── graph.py       # AgentState, agent nodes, routing, and StateGraph assembly
│   └── tools.py       # Custom tools: web_search (DuckDuckGo) + summarize
├── main.py            # CLI entry point — builds and streams the graph
├── pyproject.toml     # Project metadata, dependencies, tooling config
├── Dockerfile         # Multi-stage container build
├── .env-template      # Environment variable template
└── README.md

🧠 Design notes

  • Supervisor / orchestrator-worker pattern — a central node routes to specialist workers, keeping the workflow flexible and extensible (adding an agent = add a node + a routing branch).
  • Conditional edges — the Supervisor uses add_conditional_edges so the next step is decided at runtime from state, rather than fixed at graph-build time.
  • Reducersmessages uses LangGraph's add_messages reducer to append history; other fields overwrite by default.
  • Loop safetyrevision_count + MAX_REVISIONS guarantees termination of the writer↔reviewer loop.
  • Factory + Strategyget_llm() constructs the configured provider behind a single interface, with lazy imports so only the chosen SDK is required.

🗺️ Possible extensions

  • Semantic grounding — the validate gate checks that citations resolve, not that the source text actually supports the claim; an entailment / LLM-as-judge check would close that gap.
  • Structured routing — replace prose-based verdict parsing with structured output (function calling / Pydantic schemas) for more reliable control flow.
  • Durable persistence — swap the in-memory MemorySaver for a SQLite/Postgres checkpointer to pause/resume across restarts and support multi-turn sessions.
  • Concurrent research — fan out searches and page fetches concurrently (currently sequential).
  • Bounded supervisor — add an explicit hop counter (today only the writer↔reviewer loop is capped by MAX_REVISIONS).

📄 License

MIT

About

Multi-agent research assistant built with LangGraph — a supervisor orchestrates researcher/writer/reviewer agents that produce grounded, cite-or-refuse reports (structured web-search sources + a deterministic validation gate), with optional human-in-the-loop review and a provider-agnostic LLM layer (Groq/Gemini/OpenAI/Anthropic).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors