Conditional routing
Conditional edges in LangGraph are just functions. They read the current state and return the string name of the next node. Our router is short, but every branch is deliberate.
MAX_DEPTH = 5
def _route(state: RetrievalState) -> str:
if state["confidence"] < 0.3:
logger.info("Low confidence, stopping traversal")
return "end"
if state["depth"] >= MAX_DEPTH:
logger.info("Max depth reached, retrieving current node")
return "retrieve"
if state["should_descend"] and state["current_node"].children:
return "descend"
return "retrieve"Four guard rails in order: low confidence stops the agent, max depth forces retrieval, the descend flag plus available children triggers descent, otherwise retrieve what we have.
Max depth is the circuit breaker. Without it, a buggy tree or a confused LLM could keep descending forever. Five is generous for most technical documents: chapter, section, subsection, sub-subsection, and one extra level of slack. Past that, we just retrieve what we have.
Ordering exercise: Put the routing guards in the correct order
Loading practice…
def _make_descend():
def descend(state: RetrievalState) -> dict:
current: TreeNode = state["current_node"]
target_id = state.get("target_child_id")
# Find the chosen child by id, or fall back to the first
target = next(
(c for c in current.children if c.id == target_id),
current.children[0],
)
return {"current_node": target}
return descendThe descend node is a pure state update. It swaps current_node for the chosen child, then the graph routes back to analyze. The closure factory pattern lets us share client and model across nodes cleanly.
Quiz: Quiz
Loading practice…