中断与人机交互
让人类介入 AI 的决策过程
interrupt() 暂停 → 人类审批 → Command 恢复没有刹车
智能体在无人监督的情况下自动执行了所有操作——包括那些不该自动执行的操作。
发了不该发的邮件
智能体自动给客户发了一封通知邮件,措辞不当引发了投诉。如果发之前有人审核一下就好了。
执行了危险操作
智能体调用 DELETE FROM users 清空了测试数据库。删除操作应该有人确认。
缺少关键信息
智能体处理退款请求时,需要用户确认退款金额和收款账户。但图已经跑完了,没机会问。
核心机制
interrupt() 暂停,Command({ resume }) 恢复

图:中断与恢复的完整流程
【AI 生图提示词】请生成一张中文技术流程图,风格简洁、配色柔和、适合教学: - 画面主体:一个从左到右的流程图,中间有一个暂停环节 - 节点 1:"节点 A 执行"(蓝色) - → 节点 2:"interrupt() 暂停"(橙色,大号突出显示) - → 状态保存到 checkpointer(灰色圆柱体),标注"状态完整保存" - → 一个人形图标:"人类审批"(绿色) - → 节点 3:"Command({ resume }) 恢复"(蓝色) - → 节点 4:"继续执行"(蓝色) - 底部标注:"interrupt() 的返回值 = Command 传入的 resume 值" - 各元素关系:节点A→interrupt 是线性执行,interrupt 后图暂停并将状态保存到 checkpointer,等待人类审批后通过 Command resume 恢复,从中断点继续执行后续节点 - 关键标注:interrupt 节点标注"暂停并传出问题",checkpointer 标注"完整保存当前 state",人形图标标注"人类输入审批结果",Command 标注"resume 值成为 interrupt 返回值" - 配色建议:浅灰背景,节点柔和蓝/绿,interrupt 节点橙色高亮 - 画面比例:16:9 - 避免:过度装饰、3D 效果、杂乱的颜色
import { interrupt } from "@langchain/langgraph";
async function approvalNode(state) {
// 暂停并请求审批
const approved = interrupt("Do you approve this action?");
// Command({ resume: true }) 传入的值成为 approved
return { approved };
}
interrupt() 暂停执行,传入提示信息 → ② 状态保存到 checkpointer → ③ 用 Command({ resume: value }) 恢复,value 成为 interrupt() 的返回值。
暂停与恢复
完整的中断-恢复代码流程
import { Command } from "@langchain/langgraph";
const config = { configurable: { thread_id: "thread-1" } };
// 第一次运行 — 遇到中断暂停
const result = await graph.invoke({ input: "data" }, config);
// 查看中断内容
console.log(result.__interrupt__);
// [{ value: 'Do you approve this action?', ... }]
// 用人类的响应恢复
await graph.invoke(new Command({ resume: true }), config);
三个关键点
- 同一个 thread_id:恢复时必须用和暂停时相同的线程
- resume 值 = 返回值:
Command({ resume: true })中的true成为 interrupt() 的返回值 - 节点从头执行:恢复后节点重新执行,不是从中断行继续
审批模式
interrupt 返回布尔值,决定走哪条路
const approvalNode: typeof State.Node = (state) => {
const isApproved = interrupt({
question: "Do you want to proceed?",
details: state.actionDetails,
});
if (isApproved) {
return new Command({ goto: "proceed" });
} else {
return new Command({ goto: "cancel" });
}
};
// 恢复时传入 true 审批或 false 拒绝
await graph.invoke(new Command({ resume: true }), config); // 审批
await graph.invoke(new Command({ resume: false }), config); // 拒绝
审查与编辑
人类修改智能体生成的内容后再继续
const reviewNode: typeof State.Node = (state) => {
const editedContent = interrupt({
instruction: "Review and edit this content",
content: state.generatedText,
});
return { generatedText: editedContent };
};
// 恢复时提供编辑后的内容
await graph.invoke(
new Command({ resume: "The edited and improved text" }),
config
);
工具中的中断
interrupt 不只能在节点里用——工具函数里也能用
import { tool } from "@langchain/core/tools";
const sendEmailTool = tool(
async ({ to, subject, body }) => {
const response = interrupt({
action: "send_email",
to, subject, body,
message: "Approve sending this email?",
});
if (response?.action === "approve") {
return `Email sent to ${response.to ?? to}`;
}
return "Email cancelled by user";
},
{
name: "send_email",
description: "Send an email",
schema: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
}
);
输入验证
用 while 循环反复询问,直到获得合法输入
const getAgeNode: typeof State.Node = (state) => {
let prompt = "What is your age?";
while (true) {
const answer = interrupt(prompt);
if (typeof answer === "number" && answer > 0) {
return { age: answer };
} else {
prompt = `'${answer}' is not a valid age. Please enter a positive number.`;
}
}
};

图:输入验证的循环模式
【AI 生图提示词】请生成一张中文技术流程图,风格简洁、配色柔和、适合教学: - 画面主体:一个验证循环流程图 - 节点 1:"interrupt() 询问年龄"(蓝色圆角矩形) - → 菱形判断:"输入合法?"(橙色菱形) - "是" → 节点 2:"返回年龄,继续执行"(绿色) - "否" → 回到节点 1,标注"修改提示词,重新询问"(红色虚线回边) - 底部示例:"第1次: 'abc' → 不合法 → 第2次: -5 → 不合法 → 第3次: 25 → 合法 → 继续" - 各元素关系:interrupt 询问→判断是线性串联,判断"合法"则继续执行后续节点,判断"不合法"则回到 interrupt 形成循环,每次循环修改提示词重新询问 - 关键标注:interrupt 节点标注"暂停等待用户输入",判断菱形标注"验证逻辑",回边标注"修改提示词重新询问" - 配色建议:浅灰背景,合法路径绿色,不合法路径红色 - 画面比例:16:9 - 避免:过度装饰、3D 效果、杂乱的颜色
多个中断
并行分支各有中断时,用 ID 映射恢复值
import { isInterrupted, INTERRUPT } from "@langchain/langgraph";
// 两个并行节点各有一个 interrupt
const interruptedResult = await graph.invoke({ vals: [] }, config);
// { __interrupt__: [{ id: '...', value: 'question_a' }, { id: '...', value: 'question_b' }] }
// 一次恢复所有中断
const resumeMap: Record<string, string> = {};
if (isInterrupted(interruptedResult)) {
for (const i of interruptedResult[INTERRUPT]) {
if (i.id != null) {
resumeMap[i.id] = `answer for ${i.value}`;
}
}
}
const result = await graph.invoke(new Command({ resume: resumeMap }), config);
五条铁律
违反任何一条都会导致中断行为异常
interrupt 通过抛出异常来暂停执行。try/catch 会捕获这个异常,导致图无法正确暂停。
interrupt 的匹配是基于索引的。如果节点内有多个 interrupt,恢复时按顺序匹配。调换顺序会导致值错位。
不要传函数、Symbol、循环引用等复杂对象。interrupt 的值会被序列化保存。
恢复时节点会从头重新执行。interrupt 之前的代码会重新运行。如果之前发了 HTTP 请求,恢复时会再发一次。
interrupt 依赖持久化层保存状态、等待恢复。没有 checkpointer 就无法暂停;恢复时若不用和暂停时相同的
thread_id,将读不到中断现场。
排错指南
interrupt 没暂停
症状:调用了 interrupt() 但图继续执行了。
修复:检查是否被 try/catch 包裹了。另外确认编译时传了 checkpointer——没有 checkpointer 就没有持久化,中断无法工作。
恢复后拿到错误的值
症状:resume 传入 true,但 interrupt() 返回的不是 true。
修复:确认使用同一个 thread_id。不同线程的中断互不影响。
中断后无法恢复
症状:invoke(new Command({ resume })) 报错。
修复:必须用相同的 thread_id 和配置。中断信息绑定在特定线程上。
动手练习
构建一个带审批门的文档发布流程
- 节点 1:生成文档内容
- 节点 2:interrupt() 暂停,展示文档给人类审批
- 如果审批通过 → 节点 3:发布文档
- 如果审批拒绝 → 回到节点 1,附带拒绝理由重新生成
参考答案
const State = new StateSchema({
topic: z.string(),
content: z.string(),
feedback: z.string().optional(),
});
// 节点 1:生成文档
const generateDoc: GraphNode<typeof State> = async (state) => {
const prompt = state.feedback
? `Rewrite doc about ${state.topic}. Feedback: ${state.feedback}`
: `Write a doc about ${state.topic}`;
const msg = await model.invoke(prompt);
return { content: msg.content };
};
// 节点 2:审批
const review: GraphNode<typeof State> = (state) => {
const decision = interrupt({ content: state.content, question: "Approve?" });
if (decision.approved) return new Command({ goto: "publish" });
return { feedback: decision.feedback };
};