[LangGraph] 中断相关细节
- 外界获取中断的值
- 用户编辑图状态
- 工具内的中断
- 验证用户输入
外界获取中断的值
result.__interrupt__ 就是中断时,langgraph 暴露给外界的数据。外界(UI / CLI / 后端系统)就是靠它拿到 interrupt 里传出的内容的。
interrupt({
// 配置要传递给外界的数据
})
const result = await graph.invoke();
result.__interrupt__
// 演示外界获取中断的值
import {
Command,
MemorySaver,
START,
END,
StateGraph,
interrupt,
} from "@langchain/langgraph";
import { z } from "zod/v4";
// 图的状态定义
const State = z.object({
actionDetails: z.string().describe("待审批的操作描述"),
status: z
.enum(["pending", "approved", "rejected"])
.nullable()
.describe("当前审批状态"),
__interrupt__: z.any().optional(),
});
const checkpointer = new MemorySaver();
const graph = new StateGraph(State)
// 待审批
.addNode(
"approval",
async (state) => {
// 首先这里就有一个中断,这里question和details就会传递给外界
const decision = interrupt({
question: "是否批准该操作?",
details: state.actionDetails,
});
return new Command({
goto: decision ? "proceed" : "cancel",
});
},
{
ends: ["proceed", "cancel"],
},
) // 审批通过
.addNode("proceed", () => ({
status: "approved",
}))
// 审批不通过
.addNode("cancel", () => ({
status: "rejected",
}))
.addEdge(START, "approval")
.addEdge("proceed", END)
.addEdge("cancel", END)
.compile({
checkpointer,
});
const config = {
configurable: {
thread_id: "approval-123",
},
};
// 第一次执行图,会在中断的地方返回到外界
const result = await graph.invoke(
{
actionDetails: "转账 500 元",
status: "pending",
},
config,
);
console.log(result.__interrupt__);
const finalResult = await graph.invoke(
new Command({
resume: true,
}),
config,
);
console.log(finalResult);
用户编辑图状态
所谓用户编辑状态,就是:
- 先中断
- 外界接收用户的输入
- 恢复图的执行,拿到用户的输入
- 更新图的状态
// 演示用户编辑图状态
import {
Command,
MemorySaver,
START,
END,
StateGraph,
interrupt,
} from "@langchain/langgraph";
import { z } from "zod/v4";
// 图的状态定义
const State = z.object({
generatedText: z.string().describe("当前生成的文本内容"),
__interrupt__: z.any().optional(),
});
const checkpointer = new MemorySaver();
const graph = new StateGraph(State)
.addNode("review", async (state) => {
// 这里会产生一个中断,instruction和content就会传递给外界
// 外界在恢复图的执行的时候,resume的值就会作为interrupt的返回值
const updated = interrupt({
instruction: "请审阅并修改以下内容",
content: state.generatedText,
});
// 使用外界resume的数据来更新图的状态
return {
generatedText: updated,
};
})
.addEdge(START, "review")
.addEdge("review", END)
.compile({
checkpointer,
});
const config = {
configurable: {
thread_id: "review-42",
},
};
await graph.invoke(
{
generatedText: "初稿状态",
},
config,
);
// console.log(result.__interrupt__);
const result = await graph.invoke(
new Command({
resume: "由外界用户输入的数据",
}),
config,
);
console.log(result);
工具内中断
不仅图中的节点可以触发 interrupt,连工具在被调用时也可以主动暂停流程、等待人工审批。
也就是说:工具本身也可以有“人工确认步骤”。
为什么工具需要 interrupt?
因为真实企业场景里,有很多“高风险操作”,必须要人工确认。例如:
- 场景 1:财务扣款工具,LLM 调用了:
deductMoney({ userId: 1001, amount: 20000 });
在工具内部判断:
if (amount > 5000) {
interrupt({ value: "是否确认扣款 20000 元?" });
}
系统暂停 → 人类确认 → resume → 扣款继续执行。
- 场景 2:发版工具 / 发布配置,工具内部:
interrupt({
value: "发布将会影响线上用户,是否继续?"
});
避免 LLM 意外触发危险操作。
-
场景 3:修改数据库、删除文件等不可逆操作,工具内部:
if (operation === "delete") { interrupt({ value: "确认删除文件吗?" }); }
// 演示工具中断
import { tool } from "@langchain/core/tools";
import {
Command,
MemorySaver,
START,
END,
StateGraph,
interrupt,
} from "@langchain/langgraph";
import { z } from "zod/v4";
import "dotenv/config";
// 这是一个发送邮件的工具
const sendEmailTool = tool(
async ({ to, subject, body }) => {
// 在这个工具的工具逻辑内部,首先就产生了一个中断
const response = interrupt({
action: "send_email",
to,
subject,
body,
message: "是否确认发送这封邮件?",
});
if (response?.action === "approve") {
const finalTo = response.to ?? to;
const finalSubject = response.subject ?? subject;
const finalBody = response.body ?? body;
console.log("[sendEmailTool]", finalTo, finalSubject, finalBody);
return `邮件已发送给 ${finalTo}`;
}
return "邮件已被用户取消";
},
{
name: "send_email",
description: "向指定收件人发送一封邮件",
schema: z.object({
to: z.string(),
subject: z.string(),
body: z.string(),
}),
},
);
const Message = z.object({
role: z.enum(["user", "system"]),
content: z.string(),
});
// 图的状态
const State = z.object({
messages: z.array(Message),
__interrupt__: z.any().optional(),
});
const checkpointer = new MemorySaver();
const graph = new StateGraph(State)
.addNode("sendEmail", async (state) => {
// 这里直接调用工具
const result = await sendEmailTool.invoke({
to: "alice@example.com",
subject: "会议通知",
body: "请确认今天下午的会议安排。",
});
return {
messages: [...state.messages, { role: "system", content: result }],
};
})
.addEdge(START, "sendEmail")
.addEdge("sendEmail", END)
.compile({
checkpointer,
});
const config = {
configurable: {
thread_id: "email-workflow",
},
};
// 第一次执行图,会在执行工具的时候,产生中断
const result = await graph.invoke(
{
messages: [
{
role: "user",
content: "发一封会议邮件",
},
],
},
config,
);
console.log(result.__interrupt__);
// 恢复图的执行
const finalResult = await graph.invoke(
new Command({
resume: {
action: "approve",
subject: "【已确认】会议通知",
},
}),
config,
);
console.log(finalResult);
验证用户输入
用户输入信息后,可以验证用户的输入是否符合要求,如果不符合,就反复中断回到外界,让用户重新输入。
// 演示验证用户输入
import {
Command,
MemorySaver,
START,
END,
StateGraph,
interrupt,
} from "@langchain/langgraph";
import { z } from "zod/v4";
// 图的状态定义
const State = z.object({
age: z.number().nullable().describe("用户年龄"),
});
const checkpointer = new MemorySaver();
const graph = new StateGraph(State)
.addNode("collectAge", () => {
let prompt = "请输入你的年龄";
while (true) {
// 先产生中断
const answer = interrupt(prompt);
if (typeof answer === "number" && answer > 0) {
// 如果符合要求,才结束这个节点
return { age: answer };
}
prompt = `“${answer}”不是有效的年龄,请输入一个大于 0 的数字`;
}
})
.addEdge(START, "collectAge")
.addEdge("collectAge", END)
.compile({
checkpointer,
});
const config = { configurable: { thread_id: "form-1" } };
const first = await graph.invoke({}, config);
console.log("first>>>", first);
const second = await graph.invoke(
new Command({
resume: "二十",
}),
config,
);
console.log("second>>>", second);
// 再次恢复图的执行
const third = await graph.invoke(
new Command({
resume: 20,
}),
config,
);
console.log("third>>>", third);

浙公网安备 33010602011771号