Skip to content

LangGraph 学习笔记(三):interrupt 暂停机制

衔接上篇:已理解 State / Node / Edge / Checkpointer / MessagesAnnotation 本篇目标:掌握 interrupt 暂停机制——图的「暂停键」

一、使用场景

图执行到某个节点时,需要等待人类输入才能继续。 典型场景:支付确认、危险操作二次确认、人工审核。

二、完整示例

js
import {
  Annotation, Command, END, MemorySaver,
  START, StateGraph, interrupt,
} from "@langchain/langgraph"

const StateAnnotation = Annotation.Root({
  actionSummary: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
  userInput:     Annotation({ reducer: (_prev, next) => next, default: () => "" }),
})

/** 普通节点:准备数据 */
const showTransfer = () => ({
  actionSummary: "向张三转账 ¥100(模拟,不会真扣款)",
})

/** 暂停节点:等待人类输入 */
const waitConfirm = (state) => {
  const text = interrupt({
    hint: "输入「确认」或备注后回车,图才会继续",
    actionSummary: state.actionSummary,
  })
  return { userInput: String(text) }
}

const graph = new StateGraph(StateAnnotation)
  .addNode("showTransfer", showTransfer)
  .addNode("waitConfirm",  waitConfirm)
  .addEdge(START, "showTransfer")
  .addEdge("showTransfer", "waitConfirm")
  .addEdge("waitConfirm", END)
  .compile({ checkpointer: new MemorySaver() })  // ⚠️ interrupt 必须有 checkpointer

const config = { configurable: { thread_id: "interrupt-demo" } }

// 第一次 invoke:图跑到 interrupt() 处暂停
const paused = await graph.invoke({}, config)
console.log("待确认:", paused.__interrupt__?.[0]?.value)

// ... 等待用户输入 ...

// 第二次 invoke:传入 resume,从断点继续
const done = await graph.invoke(new Command({ resume: userInput }), config)

三、执行流程图

第一次 invoke({}, config)

[showTransfer] → 写入 actionSummary

[waitConfirm] → 执行到 interrupt() → ⏸️ 冻结!

返回 { __interrupt__: [{ value: { hint, actionSummary } }] }

(程序等待用户输入...)

第二次 invoke(new Command({ resume: "确认" }), config)

从 waitConfirm 断点处恢复

interrupt() 返回值 = "确认"

return { userInput: "确认" }

END ✅

四、关键概念解析

概念说明
interrupt(value)暂停图执行,value 作为暂停信息返回给调用方
__interrupt__第一次 invoke 返回值里的暂停信息
Command({ resume })第二次 invoke 的参数,resume 值会成为 interrupt() 的返回值
checkpointer必须有,用于保存冻结的 state,resume 时恢复
thread_id两次 invoke 必须相同,才能找到同一个断点

五、前端类比

js
// interrupt 就像 await 一个外部 Promise:
const waitForUser = () => new Promise(resolve => {
  button.onclick = () => resolve(input.value)
})

const text = await waitForUser()  // ← 对应 interrupt()
//                                    resume 对应 resolve(用户输入)

六、为什么必须有 checkpointer?

interrupt 暂停时,图的完整 state 被「冻结」存入 checkpointer。第二次 invoke 时,通过同一 thread_id 找回冻结 state,从断点继续。没有 checkpointer → 无法保存断点 → interrupt 根本无法工作。

七、实战进阶:多 interrupt 转账流程

js
// 完整流程图:
// START → [askAmount] → ◇ valid? → [showTransfer] → [waitConfirm] → ◇ confirmed? → [doTransfer] → END
//                               └ invalid → 回到 askAmount ↩️                 └ cancelled → [cancelTransfer] → END

四个关键节点:

askAmount:第一个 interrupt,询问转账金额。非法输入时通过回边重新询问。

js
const askAmount = (state) => {
  const input = interrupt({
    hint: `请输入转账金额($1 ~ $${state.balance})`,
    currentBalance: state.balance,
  });
  const amount = Number(String(input).trim());
  if (isNaN(amount) || amount <= 0 || amount > state.balance) {
    return { amount: -1 };  // 非法值 → 触发回边重问
  }
  return { amount };
};

showTransfer:展示转账信息预览。

waitConfirm:第二个 interrupt,等待用户确认。

js
const waitConfirm = (state) => {
  const text = interrupt({
    hint: '输入「确认」执行转账,输入其他内容取消',
    actionSummary: state.actionSummary,
    afterBalance: state.balance - state.amount,
  });
  return { userInput: String(text).trim() };
};

doTransfer / cancelTransfer:执行扣款或取消。

组装图:

js
const graph = new StateGraph(StateAnnotation)
  .addNode("askAmount",      askAmount)
  .addNode("showTransfer",   showTransfer)
  .addNode("waitConfirm",    waitConfirm)
  .addNode("doTransfer",     doTransfer)
  .addNode("cancelTransfer", cancelTransfer)
  .addEdge(START, "askAmount")
  .addConditionalEdges("askAmount", (state) => state.amount > 0 ? "valid" : "invalid", {
    valid:   "showTransfer",
    invalid: "askAmount",       // 回边:非法 → 重新问
  })
  .addEdge("showTransfer", "waitConfirm")
  .addConditionalEdges("waitConfirm", (state) => state.userInput === "确认" ? "confirmed" : "cancelled", {
    confirmed: "doTransfer",
    cancelled: "cancelTransfer",
  })
  .addEdge("doTransfer",     END)
  .addEdge("cancelTransfer", END)
  .compile({ checkpointer: new MemorySaver() });

图结构:

START

[askAmount] ⏸️(等待输入金额)

◇ valid?
  ├── valid   → [showTransfer] → [waitConfirm] ⏸️(等待确认)
  │                                 ↓
  │                             ◇ confirmed?
  │                               ├── confirmed → [doTransfer] → END ✅
  │                               └── cancelled → [cancelTransfer] → END ❌
  └── invalid → 回到 askAmount ↩️

图中包含:

  • ✅ 两个 interrupt(金额输入 + 转账确认)
  • ✅ 回边(非法金额重问)
  • ✅ 条件边(金额验证 + 确认判断)
  • ✅ 多个 END 出口(成功 / 取消)

八、两种图模式总结

模式一:单 interrupt(简单线性 + 暂停)
START → [准备数据] → [interrupt ⏸️] → END

模式二:多 interrupt + 回边(复杂流程)
START → [interrupt ⏸️] → ◇ valid? → [展示] → [interrupt ⏸️] → ◇ confirmed?
                         └ invalid ↩️                                 ├ confirmed → END
                                                                      └ cancelled → END