用 Command 同时更新 State 与控制路由

LangGraph 入门第九课:用 Command 同时更新 State 与控制路由

配套示例:command_routing_demo.py。本课使用本地评分数据,不依赖模型或外部服务。

一个节点算完结果后,常见做法是先返回 State 更新,再由条件边读取更新后的 State,决定下一步去哪里。如果这两个动作紧密相关,也可以直接从节点返回 Command,把状态更新和路由目标放在同一个结果里。

业务逻辑相同,可以用两种方式组织:返回字典配合条件边,或者返回 Command(update=..., goto=...)。

写法一:节点返回 State 更新,条件边负责选路

python 复制代码
def review(state):
    return {
        "approved": state["score"] >= 80,
        "status": "reviewed",
    }


def route_after_review(state):
    return "accept" if state["approved"] else "revise"


builder.add_node("review", review)
builder.add_conditional_edges("review", route_after_review)

review 是节点函数,负责产生本次状态更新;route_after_review 是普通路由函数,由图在节点完成后调用。处理逻辑和流程选择分开写,结构比较清楚,也便于复用同一个节点逻辑。

写法二:节点返回 Command

python 复制代码
from typing import Literal
from langgraph.types import Command


def review(state) -> Command[Literal["accept", "revise"]]:
    approved = state["score"] >= 80
    destination = "accept" if approved else "revise"

    return Command(
        update={"approved": approved, "status": "reviewed"},
        goto=destination,
    )

这里 update 提交 State 更新,goto 指定后续执行目标。LangGraph 会在节点完成后安排目标节点;goto 不是在 Python 函数里直接调用另一个函数。官方 Command 文档

返回类型 Command[Literal["accept", "revise"]] 声明这个节点可能导航到哪些目标,便于图结构分析和可视化。它不会自动创建 accept、revise 节点,也不会替代评分判断;这些节点仍要注册到图里。官方 update 与 goto 说明

Command.update 仍然是 State 更新

本例还有一个 events 字段,用 operator.add 累积事件:

python 复制代码
events: Annotated[list[str], add]

review 返回 {"events": ["review"]},通过分支再返回本次新增事件。Command.update 遵循字段原本配置的 reducer 规则,不会绕过 State 的合并逻辑。使用累加字段时,返回本次新增项即可,不要把完整历史列表再次提交。

关键实验:Command 不会取消普通静态边

假设节点返回:

python 复制代码
return Command(
    update={"approved": True},
    goto="accept",
)

同时建图时还写了:

python 复制代码
builder.add_edge("review", "static_notice")

那么 accept 和 static_notice 都会被安排执行。Command(goto=...) 增加动态跳转目标,不会覆盖节点上已有的普通边。LangGraph 官方也建议对一个节点选择一种路由方式,避免同时使用 Command 路由和普通边制造意外分支。官方 Command 路由说明

这也会带来并发 State 更新问题:本例中 accept 会更新普通字段 status,所以 static_notice 只更新配置了 add reducer 的 events。如果两个并行节点都写入没有 reducer 的 status,就会遇到并发写冲突。并行事件的列表顺序也不应当被用作业务顺序;需要稳定顺序时,应在数据里保留标识并显式排序。

Command 的两种使用位置不要混淆

Command 既可以从节点返回,也可以作为恢复中断时的调用输入,但用途不同:

用法 放在哪里 作用
Command(update=..., goto=...) 节点函数的返回值 更新 State,并导航到后续节点
Command(resume=value) 调用方再次调用 invoke 时 把值传给等待中的 interrupt()

如果要在已完成的线程中提交下一轮用户输入,仍然传普通输入字典。不要把单独的 Command(update=...) 当作新一轮输入;它表达的是基于现有检查点继续执行的控制指令。官方 invoke 输入说明

Send 又是另一种工具:它可以给每个动态任务指定独立输入;第八课中用它按文本列表生成多项 worker 任务。本课的 Command 则主要表达"这个节点更新了什么,并决定走哪条控制路径"。

什么时候选哪一种?

场景 合适的写法
固定顺序执行 普通边
只根据 State 决定下一节点 条件边
一个节点产出更新,并基于该结果决定下一步 Command(update=..., goto=...)
运行时才知道要派发多少项工作,且每项输入不同 Send

没有必要为了使用新 API 而改写所有条件边。将路由留在独立函数,还是放在节点的返回值里,取决于哪种方式能让业务流程更容易读懂。

运行与观察

powershell 复制代码
python -B -X utf8 command_routing_demo.py
场景 预期路径
score=90 review → accept → END
score=50 review → revise → END
score=90,且额外添加静态边 accept 与 static_notice 都会执行

示例还记录 events,用来观察静态边与动态目标同时存在时产生的两个后续任务。由于它们处于并行执行阶段,不应依赖两个事件在列表中的固定先后顺序。

本课小结

  1. Command(update=..., goto=...) 将 State 更新和节点路由放在同一个返回值中。
  2. Command.update 仍遵守对应 State 字段的 reducer 规则。
  3. Command[Literal[...]] 声明可能的导航目标,有助于图的分析和渲染;目标节点仍需自行注册。
  4. goto 不会取消节点上已有的普通静态边;混用时可能形成并行分支。
  5. Command(resume=...) 是调用方恢复 interrupt() 的用法,与节点返回的 update/goto 不同。

下一课会把本地规则分类替换为真实模型请求,并学习怎样校验模型输出,再让程序根据结构化结果选择 LangGraph 分支。

相关推荐
智驭未来掌门人1 小时前
Qt开发者不是只能选Qt creator
后端
薛定谔的悦1 小时前
从一次逆流事故说起:储能 EMS 里的 System V 信号量与跨模块快速响应机制
后端
站大爷IP1 小时前
Python的序列化把我坑惨了,原来json和pickle的区别这么大
后端
没事写点啥1 小时前
WeKnora 回答溯源
前端·后端·ai编程
智驭未来掌门人1 小时前
在 CLion 里创建 Qt 项目,真正需要想清楚的是什么
后端
数据库小学妹1 小时前
MySQL MDL锁等待排查:DDL卡住与长事务定位
数据库·后端·mysql
全栈弄潮儿1 小时前
Python实战第13期:模块与包
后端·python·编程语言
Thneonl1 小时前
ClickHouse UPDATE:一秒返回,三小时才生效
数据库·后端
铁皮饭盒2 小时前
这6个小模型各有绝活, 且都可cpu/浏览器端运行
前端·后端