Unit 11.01: Building the conditional and error paths
The routing is where the design becomes a graph, and it is the part with four distinct outcomes to get right.
One router, four destinations
after_review handles everything: error, approval, revise, and exhaustion. The ordering of its checks is the specification of what takes precedence.
The code builds the full graph and runs it.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
MAX_ATTEMPTS = 3
class State(TypedDict):
findings: list
draft: str
approved: bool
attempts: int
error: str
outcome: str
def research(state: State):
if not state["findings"] and state["attempts"] > 0:
return {"error": "no sources found"}
return {"findings": ["s1", "s2"]}
def draft(state: State):
return {"draft": f"draft from {len(state['findings'])} sources",
"attempts": state["attempts"] + 1}
def review(state: State):
return {"approved": state["attempts"] >= 2}
def after_review(state: State):
if state["error"]:
return "handle_error"
if state["approved"]:
return "publish"
return "draft" if state["attempts"] < MAX_ATTEMPTS else "give_up"
g = StateGraph(State)
for name, fn in [("research", research), ("draft", draft), ("review", review)]:
g.add_node(name, fn)
for name, text in [("publish", "published"), ("give_up", "gave up"),
("handle_error", "errored")]:
g.add_node(name, (lambda t: (lambda s: {"outcome": t}))(text))
g.add_edge(name, END)
g.add_edge(START, "research")
g.add_edge("research", "draft")
g.add_edge("draft", "review")
g.add_conditional_edges("review", after_review, {
"draft": "draft", "publish": "publish",
"give_up": "give_up", "handle_error": "handle_error"})
print(g.compile().invoke({"findings": [], "draft": "", "approved": False,
"attempts": 0, "error": "", "outcome": ""}))
Error is checked first, so a run that errored and was somehow also approved takes the error path. Approval is checked before the attempt ceiling, so a run approved on its final permitted attempt publishes rather than giving up.
Both orderings are decisions. Written as a sequence of early returns they are three lines and unambiguous; expressed as nested conditionals or spread across multiple routers, the same decisions become something you have to reconstruct.
The mistake this prevents
The mistake is one router per condition - a separate conditional edge for the error check and another for the approval check. Precedence is then implicit in the graph's shape rather than explicit in one function, and changing it means rewiring edges rather than reordering three lines.
Takeaway
Put the whole routing decision in one function as ordered early returns. The order encodes precedence, and precedence is a decision that should be readable in one place.
