[LangGraph] 阻塞式中断
中断指的就是允许图在执行的过程中暂停,等待外部输入。这种机制提供了一种“人类参与”的交互模式,某些场景下某些决策非常重要,需要人类来做出最终抉择,这个时候就可以使用中断。
场景案例
下面我们先来看一下,如果不使用中断,是否能够实现下面的场景:
需求:让大模型帮我们书写邮件,人类给出反馈
- approve:邮件内容没问题,直接发送邮件
- 其它内容:作为邮件内容的修改建议
import { StateGraph, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import readlineSync from "readline-sync";
import { z } from "zod/v4";
import "dotenv/config";
// 图的状态
const Schema = z.object({
subject: z.string().default("").describe("邮件主题"),
message: z.string().default("").describe("邮件内容"),
feedback: z.string().default("").describe("反馈"),
});
// 根据Schema生成的ts类型
type TState = z.infer<typeof Schema>;
const model = new ChatOpenAI({
model: "gpt-4o-mini",
});
// 节点:1. 写邮件的节点 2. 用户审查节点 3. 发送邮件
async function writeEmail(state: TState) {
console.log("AI: 正在生成/修改邮件内容...");
// 提示词
const lines: string[] = [
"你是一个专业的中文邮件撰写助手。",
"请用自然、礼貌、简洁的中文撰写邮件正文。",
"只输出邮件正文内容本身,不要输出额外的解释说明。",
];
if (!state.message || !state.feedback) {
// 说明是初次生成邮件内容,当前只有主题
lines.push(`邮件主题:${state.subject}`);
lines.push("请根据以上主题撰写第一版邮件正文。");
} else if (state.feedback !== "approve") {
// 说明有修改意见,需要根据上一版的正文 + 用户的反馈意见来进行修改
lines.push("下面是上一版邮件正文:");
lines.push(state.message);
lines.push("下面是用户给出的修改意见:");
lines.push(state.feedback);
lines.push(
"请严格根据修改意见,在保留合理内容的前提下,重写一封新的邮件正文,只输出修改后的完整邮件内容。",
);
}
const pt = lines.join("\n");
const result = await model.invoke(pt);
const content =
typeof result.content === "string"
? result.content
: JSON.stringify(result.content);
return {
message: content,
};
}
async function humanReview(state: TState) {
// 先将模型生成的邮件内容显示出来
console.log("\n===== 当前 AI 生成的邮件内容 =====\n");
console.log(state.message);
console.log("\n系统: 等待人类审核...\n");
const input = readlineSync.question(
"是否发送?请输入 'approve' 表示发送,或输入你的修改意见:",
);
console.log(`\n\n用户的反馈为:${input}`);
return {
feedback: input,
};
}
function sendEmail(state: TState) {
console.log("\n===== 模拟发送邮件 =====");
const to = "demo@example.com";
console.log(`收件人: ${to}`);
console.log(`主题: ${state.subject}`);
console.log("正文:\n");
console.log(state.message);
console.log("\n[模拟] 邮件已发送!");
return {};
}
// 构建图
const graph = new StateGraph(Schema)
.addNode("writeEmail", writeEmail)
.addNode("humanReview", humanReview)
.addNode("sendEmail", sendEmail)
.addEdge(START, "writeEmail")
.addEdge("writeEmail", "humanReview")
.addConditionalEdges("humanReview", (state: TState) => {
if (state.feedback === "approve") return "sendEmail";
return "writeEmail";
})
.addEdge("sendEmail", END)
.compile();
async function main() {
const subject = readlineSync.question("请输入邮件的主题:");
console.log(
"\n===== 开始:大模型根据主题生成邮件,并支持多次人工修改 =====\n",
);
const stream = await graph.stream({ subject });
for await (const _e of stream) {
}
console.log("\n===== 流程结束 =====");
}
main();
存在的问题
目前来看,我们的代码好像也实现了中断的需求,并且没有用到langgraph里面的interrupt.
🙋那是不是官方提供的interrupt是多此一举呢?
上面的案例,在实现中断,等待用户输入的时候:
const input = readlineSync.question(
"是否发送?请输入 'approve' 表示发送,或输入你的修改意见:"
);
这是一个同步阻塞式的操作。
这也就意味着程序执行到这里的时候,后面的步骤被卡死,等待用户的输入。只要用户不输入,无法进入到后面的流程。
企业审批系统不能这么做,因为:
- 审批者不可能立刻在线
- 后端不可能阻塞线程等待审批
- 流程不能暂停并等待下一次调用
- 多个审批人不可能同时处理
- 审批A没回消息,整个系统卡死
总结起来就一句:阻塞式的逻辑会让后端挂起线程,企业级系统绝对不会这么做。
-EOF-

浙公网安备 33010602011771号