惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Palo Alto Networks Blog
大猫的无限游戏
大猫的无限游戏
Martin Fowler
Martin Fowler
GbyAI
GbyAI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
量子位
T
The Blog of Author Tim Ferriss
Y
Y Combinator Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
CERT Recently Published Vulnerability Notes
Recent Announcements
Recent Announcements
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
P
Privacy International News Feed
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
博客园 - 叶小钗
L
Lohrmann on Cybersecurity
G
GRAHAM CLULEY
T
The Exploit Database - CXSecurity.com
Hugging Face - Blog
Hugging Face - Blog
P
Proofpoint News Feed
NISL@THU
NISL@THU
博客园 - Franky
C
Cybersecurity and Infrastructure Security Agency CISA
The Register - Security
The Register - Security
M
MIT News - Artificial intelligence
Know Your Adversary
Know Your Adversary
A
Arctic Wolf
F
Full Disclosure
T
Threat Research - Cisco Blogs
P
Privacy & Cybersecurity Law Blog
The Hacker News
The Hacker News
博客园 - 【当耐特】
D
Docker
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Jina AI
Jina AI
Help Net Security
Help Net Security
V
Visual Studio Blog
小众软件
小众软件
B
Blog
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
Forbes - Security
Forbes - Security
N
Netflix TechBlog - Medium
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic

博客园 - Zhentiw

[GenAI] Migration Plan: Flat API → Layered Architecture [Skill] Frontend Design Skill Claude Code Hooks: Complete Guide [GenAI] Pre-retieval overview [GenAI] About Indexing [GenAI] Indexing overview [Vibe Coding] 降低大模型幻觉 - 重试机制 [Vibe coding] 降低大模型幻觉 - JSON 安全输出提示词 [Node.js] WebSocket基础知识 [LangGraph] 应用结构 [LangGrpah] Unit testing [LangGraph] Functional API [LangGraph] 中断注意事项 [LangGrpah] 静态断点 [LangGrpah] 非阻塞式中断 [LangGraph] 阻塞式中断 [LangGraph] 语义搜索 [LangGraph] 长期记忆 [LangGraph] 管理短期记忆 [LangGraph] 自定义checkpointer [LangGraph] 短期记忆 [LangGraph] 时间旅行 [LangGraph] checkpoint常用API [LangGraph] 元数据标记 [LangGraph] 流 [Vitest] mockClear, mockReset, mockRestore [LangGraph] 将子图添加为节点
[LangGraph] 中断相关细节
Zhentiw · 2026-03-19 · via 博客园 - Zhentiw
  1. 外界获取中断的值
  2. 用户编辑图状态
  3. 工具内的中断
  4. 验证用户输入

外界获取中断的值

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);

用户编辑图状态

所谓用户编辑状态,就是:

  1. 先中断
  2. 外界接收用户的输入
  3. 恢复图的执行,拿到用户的输入
  4. 更新图的状态
// 演示用户编辑图状态
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. 场景 1:财务扣款工具,LLM 调用了:
deductMoney({ userId: 1001, amount: 20000 });

在工具内部判断:

if (amount > 5000) {
       interrupt({ value: "是否确认扣款 20000 元?" });
   }

系统暂停 → 人类确认 → resume → 扣款继续执行。

  1. 场景 2:发版工具 / 发布配置,工具内部:
interrupt({
       value: "发布将会影响线上用户,是否继续?"
   });

避免 LLM 意外触发危险操作。

  1. 场景 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);