在 LangGraph 中完成基础条件分支后,真正复杂的工作流往往还需要节点根据执行结果动态选择下一步,或把文档分片与多路查询批量派发出去。要实现这些控制方式,需要理解 Command、Send 以及状态合并策略之间的关系,否则并行执行时很容易遇到状态写入冲突。
初学 LangGraph 时,最先接触的是静态条件分支 add_conditional_edges。这种方式在编译期定义图的分支规则,ReAct Agent 循环就用它,图结构清晰、Mermaid 可视化友好。
但遇到更复杂的需求:
这时就需要 Command 和 Send。 第一次上手会踩:并行更新 state 报 InvalidUpdateError,分不清 Command(goto=[nodeA, nodeB]) 和 [Send()] 的并行差异。
Command 是 LangGraph 提供的特殊返回对象,在 Node 函数 return 时返回。它可以一次性完成两件事:
update:更新状态 State(等价于直接返回字典更新 state)goto:运行时指定下一跳,可以是单个节点、节点数组、Send 对象数组核心区别:
add_conditional_edges:node 执行完成 → 执行独立路由函数判断跳转(编译期就注册分支规则)Command:在当前节点内部,业务逻辑跑完同时决定跳转目标,不需要提前注册条件边。
# 1. 单个节点,串行跳转
Command(goto="even_node")
# 2. 多个节点名字数组:并行执行【不同节点】,共享同一份State
Command(goto=["search_node", "calc_node"])
# 3. Send对象数组:并行执行【同一个节点多份独立State副本】,Map场景
Command(goto=[Send("summarize_chunk", {"chunk": "片段1"}), Send("summarize_chunk", {"chunk": "片段2"})])
重要提醒:
Command的动态跳转不会体现在 Mermaid 图中,可视化调试是短板,简单分支优先选择add_conditional_edges。
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command
class State(TypedDict):
num: int
def check_node(state: State) -> Command:
if state["num"] % 2 == 0:
return Command(goto="even_node", update={"tip": "偶数"})
else:
return Command(goto="odd_node", update={"tip": "奇数"})
def even_node(state):
print(f"{state['num']} 是偶数")
def odd_node(state):
print(f"{state['num']} 是奇数")
builder = StateGraph(State)
builder.add_node("check_node", check_node)
builder.add_node("even_node", even_node)
builder.add_node("odd_node", odd_node)
builder.add_edge(START, "check_node")
builder.add_edge("even_node", END)
builder.add_edge("odd_node", END)
graph = builder.compile()
graph.invoke({"num": 8})
Send(node, arg) 返回Send 调度指令对象,不是直接调用节点! 它只是一个数据结构,告诉 Pregel 调度器:
创建一份全新的 State 副本,把
arg字典合并进副本,在这个副本上执行目标节点。
Send 类简化定义:
@dataclass
class Send:
node: str # 目标节点名称
arg: dict | None # 合并进子state的字典
表格
| 写法 | 并行类型 | State 特点 | 使用场景 |
|---|---|---|---|
Command(goto=["A", "B"]) | 多个不同节点并行 | 共享同一份 state | 多个独立工具同时执行,互不依赖 |
Command(goto=[Send("A", s1), Send("A", s2)]) | 同一个节点多次并行 | 每个 Send 拥有独立 state 副本 | 文档分片摘要、多 query 并行检索(Map-Reduce) |
重点:
- 节点名字数组并行:共用 state,多个节点同时写同一个 key,会冲突;
- Send 数组并行:每个任务 state 隔离,执行完成后统一合并回主 state。
重点:并行写入 state 必须用
Annotated + reducer,否则直接抛出InvalidUpdateError
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, Send
import operator
class State(TypedDict):
docs: list[str]
# operator.add 作为reducer,并行多个返回值自动列表追加
summaries: Annotated[list[str], operator.add]
def split_docs(state: State) -> Command:
sends = []
for doc in state["docs"]:
sends.append(Send("summarize_chunk", {"chunk": doc}))
return Command(goto=sends)
def summarize_chunk(state):
chunk = state["chunk"]
return {"summaries": [f"文档摘要:{chunk[:15]}..."]}
def merge_summary(state):
print("汇总所有分片摘要:", state["summaries"])
builder = StateGraph(State)
builder.add_node("split_docs", split_docs)
builder.add_node("summarize_chunk", summarize_chunk)
builder.add_node("merge_summary", merge_summary)
builder.add_edge(START, "split_docs")
builder.add_edge("summarize_chunk", "merge_summary")
builder.add_edge("merge_summary", END)
graph = builder.compile()
res = graph.invoke({
"docs": ["大模型Agent基础原理", "LangGraph状态图介绍", "Send并行任务讲解"]
})
print(res)
InvalidUpdateError: At key 'summaries': Can receive only one value per step. Use an Annotated key to handle multiple values.
LangGraph State 默认字段的合并策略是覆盖。 在同一个 superstep(Pregel 的一步),多个并行节点同时更新同一个 state key,引擎不知道怎么合并,直接报错。
使用 Annotated[类型, reducer] 显式定义合并策略:
operator.add:列表追加(Map-Reduce 最常用);自定义 reducer 示例:
def list_merge_reducer(old: list, new: list) -> list:
# 简单去重合并
return list(set(old + new))
class State(TypedDict):
docs: list[str]
summaries: Annotated[list[str], list_merge_reducer]
注意:并行节点返回值必须是列表,
operator.add做列表拼接;直接返回字符串会报错! ✅return {"summaries": ["摘要文本"]}❌return {"summaries": "摘要文本"}
Command(goto=END)add_conditional_edges,图可视化清晰,便于调试;Annotated + reducer;stream逐 step 打印 state 排查问题。add_conditional_edges:编译期静态分支,适合常规 Agent 循环,可视化友好;Command:Node 内部运行时动态路由,支持跳转、状态更新、批量并行指令;Send:Map 并行任务分发,创建隔离的 State 副本,是分片处理的核心;Annotated + reducer,解决InvalidUpdateError。把穿搭镜装进眼镜:用 AIUI 为 Rokid Glasses 打造会说话的「镜感 VIBE」
用Word Copilot法律专员高效制作农药企业合规培训材料
先跑通最小 RAG,再理解 LangGraph 与 Agentic RAG
PB 0.31背后的清算定价:多智能体如何将价值陷阱转成分步减仓方案
CSS Hack大全教你如何区分出IE6IE10、FireFox、Chrome、Opera
LangGraph 深入实践:用 Command 与 Send 实现动态流转和并行 Map-Reduce