Estimated time: 5–8 hours · Portfolio-grade · Deployable
This is the Chapter 2 capstone. It integrates every pattern from the chapter into a single, deployable system that is demonstrably more powerful than a single LLM call:
The finished system accepts a research question via CLI, runs a multi-step pipeline to gather and synthesise information, and produces a structured research report in both Markdown and JSON.
The system has four stages:
User query
│
▼
[1. Router] — classifies query as "factual", "analytical", or "opinion"
│
▼
[2. Research Agent] — parallel searches, summarisation, fact-checking loop
│ (runs until evidence quality threshold is met)
▼
[3. Synthesis] — drafts a structured report from gathered evidence
│
▼
[4. Evaluator] — scores the report; requests improvement if score < 8/10
│
▼
Structured ResearchReport (JSON + Markdown)
Classify the incoming query into one of three types. Each type receives a different system prompt in Stage 2:
Run an agentic loop with these tools (same as P005, with one addition):
search_web(query)summarise_content(content, focus)check_fact(claim, context)get_current_date()store_evidence(key, content) — new: stores a piece of evidence in a shared dictionary that the synthesis stage reads. This is the agent’s “notepad”.The system prompt for the research agent instructs it to:
After the agent loop exits, run a dedicated synthesis prompt that reads all stored evidence and produces a ResearchReport:
from pydantic import BaseModel
class Source(BaseModel):
title: str
url: str | None
relevance: str # one sentence
class ResearchReport(BaseModel):
query: str
query_type: str # factual | analytical | opinion
summary: str # 2–3 paragraph executive summary
key_findings: list[str] # bullet points, 5–10 items
sources: list[Source]
confidence: str # "high" | "medium" | "low"
limitations: str # what this report does not cover
generated_at: str # ISO datetime
Score the report on four criteria (each 1–10), producing an overall average:
If the average score is below 8, send the report back to Stage 3 (synthesis) with the evaluator’s feedback. Maximum 3 synthesis-evaluation iterations.
# Single research question
uv run research.py "What are the main differences between PydanticAI and LangGraph?"
# Save report to files
uv run research.py "How does vector search work?" --output reports/
# Verbose mode (shows all agent iterations)
uv run research.py "Who are the main players in the AI chip market?" --verbose
# Use a specific orchestration backend
uv run research.py "..." --backend langgraph # or --backend pydantic-ai
Sample output:
[Router] Query type: analytical (confidence: high)
[Research] Starting information gathering...
Iter 1: search_web("PydanticAI vs LangGraph comparison") → 2,341 chars
Iter 2: summarise_content(...) → 312 chars → stored as "pydanticai_overview"
Iter 3: search_web("LangGraph features 2026") → 1,847 chars
Iter 4: check_fact("LangGraph supports human-in-the-loop", ...) → supported
Iter 5: store_evidence("langgraph_hitl", ...) → stored
Iter 6: RESEARCH COMPLETE — gathered 5 evidence items
[Synthesis] Drafting report...
[Evaluator] Scores: completeness=7, accuracy=9, clarity=8, sourcing=6 → avg 7.5
[Synthesis] Retry with feedback: "Add more specific source citations"
[Evaluator] Scores: completeness=8, accuracy=9, clarity=8, sourcing=8 → avg 8.25 ✓
[Report] Saved to reports/2026-06-24-pydanticai-vs-langgraph.{md,json}
══════════════════════════════════════════════
RESEARCH REPORT
══════════════════════════════════════════════
Query: What are the main differences between PydanticAI and LangGraph?
Type: analytical | Confidence: medium
SUMMARY
PydanticAI and LangGraph represent different philosophies in AI workflow
orchestration. PydanticAI prioritises type safety and testability through
Python's native type system...
KEY FINDINGS
• PydanticAI enforces typed result models — agents cannot return
unvalidated output
• LangGraph models workflows as explicit state graphs with visible
nodes and edges
• PydanticAI offers superior dependency injection; LangGraph has
native human-in-the-loop via interrupt()
...
SOURCES
1. PydanticAI Agents documentation (ai.pydantic.dev/agents/)
2. LangGraph: Multi-Agent Workflows (blog.langchain.dev/langgraph/)
...
LIMITATIONS
This report covers the libraries as of 2026-06-24. Both are actively
developed and APIs may change.
══════════════════════════════════════════════
The outer pipeline (router → research → synthesis → evaluator loop) can be built with either LangGraph or PydanticAI. You must implement one of them — not raw API — for the outer shell. The research agent loop inside Stage 2 must remain raw API (no framework).
Model the four stages as a state graph. Use add_conditional_edges for the evaluator-optimizer loop. State should include the research evidence dict, the current report, and the iteration count.
Build the outer system as a PydanticAI agent whose result type is ResearchReport. The router, research, and evaluator are tools registered on this outer agent. Dependencies include a shared evidence store (dataclass).
.md (Markdown) and a .json (ResearchReport JSON) fileResearchReport, RouterDecision, EvaluationResult).ResearchReport.ResearchReport object.md and .json files--verbose shows all research agent iterations--backend langgraph and --backend pydantic-ai, then add a compare command that diffs the two reports side by side.Finished this project? Share your GitHub repo link in the discussion below. Browse what others built to get inspiration and give feedback.