Course Content
LangGraph Agents
7 sections · 49 lessons
What is the Send API used for (map-reduce / fan-out fan-in patterns)?
What you need to know
Python
1import operator2from typing import Annotated, TypedDict3from langgraph.graph import StateGraph, START, END4from langgraph.types import Send56class State(TypedDict):7 topics: list[str]8 findings: Annotated[list[str], operator.add]9 report: str1011class WorkerInput(TypedDict):12 topic: str1314def plan(state: State) -> dict:15 return {}1617def fan_out(state: State) -> list[Send]:18 return [Send("research", {"topic": t}) for t in state["topics"]]1920def research(state: WorkerInput) -> dict:21 return {"findings": [f"notes on {state['topic']}"]}2223def synthesise(state: State) -> dict:24 return {"report": "\n".join(sorted(state["findings"]))}2526builder = StateGraph(State)27builder.add_node("plan", plan)28builder.add_node("research", research)29builder.add_node("synthesise", synthesise)30builder.add_edge(START, "plan")31builder.add_conditional_edges("plan", fan_out, ["research"])32builder.add_edge("research", "synthesise")33builder.add_edge("synthesise", END)34graph = builder.compile()35print(graph.invoke({"topics": ["pricing", "competitors", "regulation"], "findings": []}))The two rules
- Input is private. The dict in
Sendis the worker's whole input. It does not seetopicsor other workers. - Output is shared. Every worker's update merges into global state, so
findingsneedsoperator.addor a similar reducer.
Fan-in
All Send workers here run in the same super-step, so synthesise runs once, after all of them. If workers have different path lengths (for example, a worker is itself a two-node path), mark the join defer=True.
Costs and limits
- Every worker is a model call: 50 topics means 50 calls. Cap the list length.
- Use
max_concurrencyin the run config to avoid provider rate limits. - Catch expected errors inside the worker and return a partial result, so one bad item does not fail the step.
A real-life example
An insurance company receives claim files with 5 to 40 attached documents. A split node lists the documents; Send starts one extract worker per document; each writes {"doc_id": ..., "fields": {...}} into extracted. With max_concurrency: 8, a 40-document file finishes in about 25 seconds instead of 3 minutes sequentially. A merge node sorts by doc_id, reconciles conflicting dates, and flags two documents where OCR failed for a human.
Follow-up questions to expect
- "Static fan-out versus
Send?" — Several fixed edges when the branches are known at build time (search news and filings);Sendwhen the count depends on data. - "Can
Sendtarget a subgraph?" — Yes; the target can be any node, including a compiled subgraph. - "Can you use
Sendfor self-consistency?" — Yes: send the same question five times with different seeds and vote in the join node.