Command and Send: dynamic routing

LangGraph · Practical · 13 min

Many workflows decide their next step inside a node, or need a variable number of parallel workers.

Conditional edges route after a node. Two tools give more flexibility.

`Command`. A node can return Command(update={...}, goto="node_name"): update the state and choose the next node in one go. Annotate the return type, such as -> Command[Literal["approve", "reject"]], so LangGraph knows the possible targets for drawing and validation. Use it when the routing decision is made by the node's own logic (for example from a model's structured output) and you don't want to store a temporary flag in state just for an edge to read. Command is also how agents hand control to each other, and Command(resume=...) resumes interrupts (later lessons).

`Send`. Sometimes the number of parallel tasks is only known at run time: summarize each of N documents, check each of N claims. Make a conditional edge's routing function return a list of Send("node", payload) objects. LangGraph runs that node once per Send, in parallel, each with its own payload as input (which can differ from the graph state). Results flow back through a reducer, such as operator.add, so they accumulate. This is the map-reduce pattern: map work out, reduce results back.

Tips: keep payloads small; cap parallelism with the max_concurrency config key; give each worker's output a reducer or parallel writes will clash.

Example

import operator
from typing import Annotated, Literal, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, Send


class State(TypedDict):
    docs: list[str]
    summaries: Annotated[list[str], operator.add]


def plan(state: State) -> Command[Literal["review"]]:
    return Command(update={"docs": [d.strip() for d in state["docs"]]}, goto="review")


def fan_out(state: State) -> list[Send]:
    return [Send("summarize", {"doc": d}) for d in state["docs"]]   # one task per doc


def summarize(payload: dict) -> dict:
    return {"summaries": [payload["doc"][:10]]}


b = StateGraph(State)
b.add_node(plan)
b.add_node("review", lambda s: {})
b.add_node(summarize)
b.add_edge(START, "plan")
b.add_conditional_edges("review", fan_out, ["summarize"])
b.add_edge("summarize", END)
print(b.compile().invoke({"docs": [" alpha report ", "beta notes"], "summaries": []}))
# {'docs': ['alpha report', 'beta notes'], 'summaries': ['alpha repo', 'beta notes']}

Remember

  • Command(update=..., goto=...) updates state and routes in one return
  • Type the return as Command[Literal[...]] to declare targets
  • Returning [Send(node, payload), ...] runs a node once per item in parallel
  • Collect Send results with a reducer (map-reduce)

Check yourself

  1. You must grade a list of essays whose length varies per run, in parallel. What do you use?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.langchain.com/oss/python/langgraph/graph-api