当前位置: 首页 > news >正文

[LangGraph] Command指令

有些时候,可能会出现这么一种场景:

在节点内部,根据计算结果,动态决定要跳到哪个下一节点,而不是提前把所有边写死。

  • 节点:负责做事情
  • 边:决定走哪一条路线

传统方案节点只能做:

return { ...state update... }

而流程走向由 graph 的 .addEdge() 固定决定的。也就是说,在构建图的时候,流程走向就确定下来了,无法动态跳转。

因此,Langgraph 中提供了一个Command的特性,让更新节点和跳转能够合并到一起。

基础语法

// Command是一个类,对外返回一个Command的实例对象
new Command({update: ...,  goto: ...,  
})

配置对象包含两项:

  • update:更新的状态
  • goto:要跳转的节点

例如:

import { Command } from "@langchain/langgraph";graph.addNode("myNode", (state) => {// 返回一个Command实例对象return new Command({update: { foo: "bar" },  // 更新状态goto: "myOtherNode",     // 要跳转到哪一个节点 });
});

在上面的myNode节点中:

  1. 更新当前状态的字段 foo
  2. 执行完后直接跳转到 myOtherNode

允许列表

当一个节点使用Command来做更新跳转时,需要指定一个允许列表(数组),这相当于是一个路径的白名单,列出了所有可能的去向。

例如:

import { Command } from "@langchain/langgraph";// 1. 节点名称
// 2. 节点对应的回调函数:返回一个Command的实例对象
// 3. 配置对象:ends(允许列表)
graph.addNode("myNode", (state) => {return new Command({update: { foo: "bar" },  // 更新状态goto: "B",     					 // 跳转下一个节点});
},{ ends: ["B", "C"] 					 // 允许列表
});

快速上手

接下来我们来看一个快速上手的案例。

流程:

  1. 用户提交订单
  2. A 节点做基础校验
  3. 根据订单类型跳转到 B 或 C → D 做最终汇总
// 使用Command
import { StateGraph, START, END, Command } from "@langchain/langgraph";
import * as z from "zod";// 1. 状态Schema
const Schema = z.object({orderType: z.enum(["digital", "physical"]).optional().describe("订单的类型"),basicInfo: z.string().optional().describe("基础校验"),digitalInfo: z.string().optional().describe("数码产品校验"),physicalInfo: z.string().optional().describe("非数码产品校验"),logs: z.array(z.string()).default([]),
});// 根据Schema生成ts类型
type TState = z.infer<typeof Schema>;// 2. 构建图
const app = new StateGraph(Schema).addNode("A",(state: TState) => {console.log("运行A节点的基础校验");// 判断下一个跳转节点const next = state.orderType === "digital" ? "B" : "C";// 关键点:对外返回一个Commandreturn new Command({// 更新状态update: {basicInfo: "A节点基础校验已完成",logs: [...state.logs,`A节点校验已完成,订单类型为${state.orderType},前往${next}`,],},// 指定跳转节点goto: next,});},{ends: ["B", "C"],}).addNode("B",(state: TState) => {console.log("运行B节点的校验");return new Command({update: {digitalInfo: `B节点已经对数码产品进行了校验`,logs: [...state.logs, "数码产品订单校验完毕"],},goto: "D",});},{ends: ["D"],}).addNode("C",(state: TState) => {console.log("运行C节点的校验");return new Command({update: {digitalInfo: `C节点已经对非数码产品进行了校验`,logs: [...state.logs, "非数码产品订单校验完毕"],},goto: "D",});},{ ends: ["D"] }).addNode("D", (state: TState) => {console.log("运行D节点");const summary =state.orderType === "digital"? `D:数码商品流程完成:${state.digitalInfo}`: `D:非数码商品流程完成:${state.physicalInfo}`;return {logs: [...state.logs, summary, "处理完成"],};}).addEdge(START, "A").addEdge("D", END).compile();// 3. 测试用例
const result = await app.invoke({logs: [],orderType: "digital",
});console.log("\n最终输出:");
console.log(JSON.stringify(result, null, 2));

条件分支

🙋:这个Command不就是和前面的条件分支一样的么?

重构快速上手示例

// 使用条件分支
import { StateGraph, START, END } from "@langchain/langgraph";
import * as z from "zod";// 1. 状态Schema
const Schema = z.object({orderType: z.enum(["digital", "physical"]).optional().describe("订单的类型"),basicInfo: z.string().optional().describe("基础校验"),digitalInfo: z.string().optional().describe("数码产品校验"),physicalInfo: z.string().optional().describe("非数码产品校验"),logs: z.array(z.string()).default([]),
});// 根据Schema生成ts类型
type TState = z.infer<typeof Schema>;// 2. 构建图
const app = new StateGraph(Schema).addNode("A", (state: TState) => {console.log("运行A节点的基础校验");// 判断下一个跳转节点const next = state.orderType === "digital" ? "B" : "C";return {basicInfo: "A节点基础校验已完成",logs: [...state.logs,`A节点校验已完成,订单类型为${state.orderType},前往${next}`,],};}).addNode("B", (state: TState) => {console.log("运行B节点的校验");return {digitalInfo: `B节点已经对数码产品进行了校验`,logs: [...state.logs, "数码产品订单校验完毕"],};}).addNode("C", (state: TState) => {console.log("运行C节点的校验");return {digitalInfo: `C节点已经对非数码产品进行了校验`,logs: [...state.logs, "非数码产品订单校验完毕"],};}).addNode("D", (state: TState) => {console.log("运行D节点");const summary =state.orderType === "digital"? `D:数码商品流程完成:${state.digitalInfo}`: `D:非数码商品流程完成:${state.physicalInfo}`;return {logs: [...state.logs, summary, "处理完成"],};}).addEdge(START, "A").addConditionalEdges("A", (state: TState) => {if (state.orderType === "digital") return "B";else return "C";}).addEdge("B", "D").addEdge("C", "D").addEdge("D", END).compile();// 3. 测试用例
const result = await app.invoke({logs: [],orderType: "physical",
});console.log("\n最终输出:");
console.log(JSON.stringify(result, null, 2));

和条件分支区别

相同点

条件分支通过路由函数决定接下来走哪条边,Command 通过 goto 字段决定下一个节点是谁。

二者都能实现类似于:

if (xxx) 去 B;
else 去 C;

的分支逻辑。

不同点

条件边写的路由逻辑是图结构层面的:

graph.addConditionalEdges("A", (state) => {if (state.foo === "bar") return "B";else return "C";
});

流程像是:

A执行完 → 图外:下一步去哪? → 路由函数返回结果 → 图外按照流程走

这里 A 的函数自己不知道下一个是谁,是“外部控制器”决定的。

而 Command 的逻辑是节点自己就决定跳哪:

graph.addNode("A", (state) => {if (state.foo === "bar") {return new Command({update: { foo: "baz" },goto: "B",});}
});

流程像是:

A 执行时 → 自己计算出结果 + 自己决定去哪 → 直接返回 Command

A 自己完成了“计算 + 更新 + 跳转”三件事,图外不再需要再问一次“去哪”。

总结:

  1. Command:在节点里面计算,计算完成后进行跳转
  2. 条件分支:在节点外编排跳转逻辑

两者的具体区别如下表:

特性 条件分支 Command
定义位置 图结构层(Graph 设计阶段) 节点函数内部(运行阶段)
逻辑归属 结构控制(流程图层) 业务逻辑(节点层)
能否更新 state 不能更新,只决定方向 可以更新并跳转
调用时机 节点执行完之后(外部控制流) 节点执行过程中(内部决定)
使用场景 固定的流程路由、if/else 分支 动态决策、代理交接、HITL 恢复等

应用场景

由于执行机制的不同,Command常用于以下两类场景:

  1. 跨子图跳转
  2. 人工介入、人工干预、中断

跨子图跳转

由于条件边的跳转范围仅限于当前图内,无法直接跨出子图跳到父图或兄弟子图。比如:

父图:├── 子图A(负责分析)├── 子图B(负责生成)

如果子图A分析完想直接跳到父图中的子图B,这时条件边就做不到,因为它只识别当前子图的节点。

而通过Command就能够很好的解决这个问题:

return new Command({update: { result: "analysis done" },goto: "SubgraphB",graph: Command.PARENT, // 指定跳转到父图
});

人工介入

在 LangGraph 的执行流程中,有些节点可能需要等待人工输入或外部事件的反馈才能继续执行,比如:

  • 等待用户确认某个决策;
  • 等待客服或审核人员填写信息;
  • 等待外部系统的异步回调。

这类场景下,普通的节点返回值或条件边无法暂停图的运行,而Command可以做到这一点。

例如一个客服系统流程:

AI回复 → 等待用户确认 → 继续执行

执行到“等待用户输入”时,流程需要暂停。这时系统会:

// 暂停:在节点里
await interrupt({ reason: "需要人工确认报价" });

用户输入后,系统会再恢复。这个恢复动作就是用Command完成的:

// 恢复:拿到外部输入后(继续同一节点的后续逻辑)
return new Command({resume: { approved: true, note: "人工已确认" },goto: "NextNode",
});

-EOF-

http://www.jsqmd.com/news/387338/

相关文章:

  • 好用还专业!9个降AI率工具测评:本科生必看的降AI率神器
  • 别再瞎找了!10个降AIGC工具测评:自考降AI率全攻略
  • 柑橘叶子病害检测数据集VOC+YOLO格式1102张4类别
  • 07]Delphi10.3中Richedit中的链接可以点击
  • 2026船用减压阀选购指南:这些品牌备受好评,船用减压阀/船用防浪阀/船用疏水阀/船用安全阀,船用减压阀企业推荐 - 品牌推荐师
  • tj
  • 看完就会:10个AI论文软件测评!专科生毕业论文+开题报告神器推荐
  • 研究生必看!全网爆红的AI论文工具 —— 千笔·专业学术智能体
  • 深度测评 8个AI论文写作软件:研究生毕业论文与科研写作必备工具全解析
  • 06]delphi10.3中richedit中文本背景颜色
  • 2026别错过!AI论文平台 千笔·专业学术智能体 VS 学术猹,本科生写作神器!
  • 【机器学习】深度学习推荐框架(三十):X 推荐算法Phoenix rerank机制
  • 一文讲透|专科生必备的降AIGC网站 —— 千笔·降AI率助手
  • 【节点】[CustomSpecular节点]原理解析与实际应用
  • 【节点】[CustomSpecular节点]原理解析与实际应用
  • 改稿速度拉满!专科生专属降AI率网站 —— 千笔·专业降AIGC智能体
  • 深度解析防火涂料:2026年工程应用与选型,饰面型防火涂料/钢结构防火涂料/薄型钢结构防火涂料,防火涂料订制厂家联系方式 - 品牌推荐师
  • 全屋智能家居的最强大脑!极空间部署全屋AI自动化方案『Miloco』
  • 《实时渲染》第3章-图形处理单元-3.6曲面细分阶段
  • 使用 ArcPy 自动化导出并合并 ArcGIS 地图系列为 PDF 地图册
  • 2026年2月,这些靠谱异宠医院值得关注排行,宠物内科专家/腹腔镜绝育/宠物医院/宠物骨科专家,异宠专家哪家最好 - 品牌推荐师
  • 生产环境自go-zero走进微服务最佳实践与性能优化
  • 深度测评!研究生专属AI论文写作软件 —— 千笔ai写作
  • 完整教程:微信小程序WXSS 模板样式
  • 格子玻尔兹曼方法(LBM)中的MRT作用力模型
  • 互联网大厂Java求职面试实录:技术栈深度问答与业务场景解析
  • OpenClaw终于有了图形界面,一键安装使用你的24小时AI 研究助手!
  • 初学者福音!2026年值得入手的入门古筝盘点,瑶鸾古筝Y103系列(梦蝶),古筝生产厂家找哪家 - 品牌推荐师
  • 使用 ArcPy 快速统计 OD 线在网格中的起点-终点流量
  • 2025-2026年公寓装修、酒店翻新、商业办公装配式公司怎么选?这份选购指南帮你搞定 - 匠言榜单