一句话结论:Send 更像“把一份特定输入派发给某个节点”,常用于动态 fan-out;Command 更像“节点返回的控制指令”,可以同时更新状态并决定下一步去哪里。
为什么这两个概念容易混
在 LangGraph 里,图的执行不是只有线性调用。节点可以根据运行时数据决定下一步,也可以把一个任务拆成多份并行处理。Send 和 Command 都和“控制流”有关,所以初学者很容易把它们都理解成“跳转到某个节点”。但它们真正解决的问题不一样:Send 偏向分发工作单元,Command 偏向让节点把状态更新和路由决策一起返回。
换句话说,Send 是路由函数发出的“派单”;Command 是节点函数发出的“执行结果加下一步安排”。
核心区别
|
维度 |
Send |
Command |
|
关注点 |
把指定参数发送给目标节点。 |
让节点同时表达状态更新和下一步路由。 |
|
典型位置 |
通常从条件边的路由函数返回。 |
通常从节点函数返回。 |
|
状态语义 |
给目标节点传入定制化的 arg;适合每个分支拿到不同输入。 |
通过 update 修改图状态,再通过 goto 控制后续节点。 |
|
适合场景 |
map-reduce、批量任务拆分、动态数量的并行分支。 |
节点内部完成判断后,需要更新状态并跳转到不同节点。 |
|
思考方式 |
“把这些子任务分别派给某节点。” |
“我处理完了,状态这样改,下一步去这里。” |
Send:适合动态分发
当你在运行时才知道要启动多少个分支时,Send 很顺手。比如用户给出一组主题,你希望每个主题都交给同一个 writer 节点生成草稿。这里每个分支的输入并不完全一样,Send 就负责把不同的 arg 发送给同一个或不同的目标节点。
from langgraph.types import Send
def route_to_writers(state):
return [Send("writer", {"topic": topic}) for topic in state["topics"]]
这段逻辑的重点不是“writer 之后去哪里”,而是“我要把多少份工作派出去,每份工作带什么输入”。所以 Send 非常适合动态并行、批处理、map-reduce 这类模式。
Command:适合节点内决策
Command 的优势在于把节点的业务结果和控制流放在同一个返回值里。比如一个审核节点读完输入后,既要把审核结论写入状态,又要根据结论跳到 approve 或 revise。
需要注意的是,Command 在官方文档里是一个更通用的控制原语;除了节点返回 update 和 goto,它还可以用于中断恢复等场景。本文为了回答 Send 和 Command 的区别,主要聚焦最常见的节点路由语境。
from typing import Literal
from langgraph.types import Command
def review(state) -> Command[Literal["approve", "revise"]]:
passed = check_quality(state["draft"])
return Command(
update={"review_passed": passed},
goto="approve" if passed else "revise",
)
这时如果只用普通条件边,你可能需要先在节点里写状态,再在另一段路由函数里重复判断。Command 把这两件事放在一起,尤其适合“判断逻辑本来就属于这个节点”的情况。
怎么选
常见误区
- 误区一:Send 和 Command 都是“跳转”。更准确地说,Send 是派发带参数的执行分支,Command 是节点返回控制指令。
- 误区二:Command 可以替代所有条件边。并不是;简单路由继续用条件边会更清晰。
- 误区三:Send 只适合发给同一个节点。它常见于同一节点的多分支并行,但本质上表达的是“把 arg 发送到目标节点”。
- 误区四:为了高级而使用 Command。只有当状态更新和路由决策强相关时,Command 才真正降低认知成本。
一个更直观的类比
可以把 Send 想成派单系统:你有很多张工单,每张工单都带着自己的上下文,被派到某个处理节点。Command 更像处理人写在工单上的处置意见:处理结果是什么、系统状态怎么改、下一步流转到哪个环节。
博客可用结尾
真正写 LangGraph 时,不必先问“哪个更高级”,而要先问“我现在是在分发工作,还是在表达节点处理后的下一步”。如果是在动态创建多个执行分支,Send 是自然选择;如果是在节点内部做业务判断,并且判断结果会同时影响状态和后续路径,Command 会更贴近代码意图。把这个边界想清楚,图的结构会清爽很多。



