01 - Routing and subgraphs¶
A question-answering assistant. Classify the question, then either give a one-shot quick answer or run a multi-step research sub-pipeline, then lightly copy-edit the result.
Overview¶
You ask a question. A classifier LLM decides whether it can be
answered in one or two sentences ("quick") or whether it benefits
from considering multiple angles ("research"). Quick questions go
through a single quick_answer node. Research questions descend
into a subgraph that plans three angles, gathers a short note for
each, and synthesizes them into a paragraph. Either way, a final
format_final node copy-edits the answer.
Demo questions: "what year did the moon landing happen" (usually routes to quick) and "why is the lunar south pole strategically important?" (usually routes to research).
What it teaches¶
- Conditional edges routing on a state
field.
classifywritesstate.route; the conditional edge function reads it and returns the next node's name. - Subgraph composition. The research
pipeline is itself a compiled graph, wrapped as a single node in
the outer graph via
add_subgraph_node. - A custom
ProjectionStrategy. The defaultFieldNameMatchingonly carries fields back out of a subgraph; carrying the parent's question in requires writing a smallProjectionStrategyclass. TheQuestionProjectionhere is the canonical pattern for non-trivial subgraph boundaries. - The
mergereducer for dict accumulation. Every node returns a smalltalliesfragment; the reducer accumulates them into one dict on the final state.
How to run¶
uv sync --group examples
LLM_API_KEY=sk-... uv run python examples/01-routing-and-subgraphs/main.py \
"why is the lunar south pole strategically important?"
The first positional arg becomes the question. With no arg, it falls back to the lunar-south-pole question above.
The graph¶
flowchart TD
start([start])
classify[classify]
quick_answer[quick_answer]
format_final[format_final]
stop([end])
subgraph research [research subgraph]
direction TB
plan_research[plan_research]
gather[gather]
synthesize[synthesize]
plan_research --> gather --> synthesize
end
start --> classify
classify -->|"route == 'quick'"| quick_answer
classify -->|"route == 'research'"| research
quick_answer --> format_final
research --> format_final
format_final --> stop
The research box is a separate compiled graph with its own state
schema (ResearchState). The QuestionProjection carries
parent.question in as subgraph.question, and brings
subgraph.answer plus subgraph.trace back out.
Reading the output¶
For a research-route run, expect:
question: why is the lunar south pole strategically important?
route: research
answer:
<paragraph synthesized from three angles>
trace: ['classify', 'plan_research', 'gather', 'synthesize', 'format_final']
tallies: {'classify_calls': 1, 'research_runs': 1, 'formatted': 1}
routeis the fieldclassifywrote that the conditional edge read.tracelists nodes in invocation order. Subgraph nodes appear inline; that's the projection'stracefield flowing back out through the parent'sappendreducer.tallieshas one entry per node that contributed:classifysetclassify_calls, the subgraph projection'sproject_outsetresearch_runs,format_finalsetformatted.quick_answerwould have contributedquick_answers: 1if the run had gone the other way.
For a quick-route run, trace drops to ['classify',
'quick_answer', 'format_final'] and tallies has
quick_answers: 1 in place of research_runs: 1.