[LangGrpah] 静态断点

基本介绍

一句话定义:静态断点,是在编译图时就确定好的中断点,用于保证某个节点在执行前或执行后一定会暂停。

静态断点的两种形式

LangGraph 提供了两种静态断点配置方式:

  1. interrupt_before:在指定节点 开始执行之前 暂停。

    interruptBefore: ["node_name"]
    
  2. interrupt_after:在指定节点 执行完成之后 暂停。

    interruptAfter: ["node_name"]
    

静态断点定义在哪里

静态断点不是写在节点函数里的。它定义在:

  • 图的结构上
  • 节点与节点之间的执行关系中
  • 通常配置在 graph.compile() 阶段

换句话说:静态断点是图的一部分,不是代码逻辑的一部分。

例如:

const graph = new StateGraph(State)
  .addNode("nodeA", nodeA)
  .addNode("nodeB", nodeB)
  .addEdge(START, "nodeA")
  .addEdge("nodeA", "nodeB")
  .addEdge("nodeB", END);

const compiledGraph = graph.compile({
  interruptBefore: ["nodeB"], // 在 nodeB 执行前暂停
});

静态断点的控制粒度

静态断点的控制粒度是:节点级

这意味着,一个节点:

  • 要么完全不执行(interrupt_before)

  • 要么完整执行完(interrupt_after)

  • 不能在节点函数的某一行中断,或者在节点内部写条件判断决定是否中断

如果你有这样的需求:“执行到一半,根据 state 决定要不要停”

那它就不属于静态断点的范畴了。

静态断点与动态断点核心对比

如下表所示:

对比维度 静态断点 动态断点
何时确定 编译图时 运行时
定义位置 图结构上 节点函数内部
是否依赖条件
是否依赖 state
控制粒度 节点级 语句级
常见用途 流程控制 决策

快速入门

假设我们在做一个 Agent 流程,逻辑是:

  1. 解析用户输入
  2. 整理出要调用外部系统的参数
  3. 调用外部工具(下单 / 发邮件 / 写数据库)

在真实业务中,第 3 步往往是有风险的

  • 真的要下单吗?
  • 参数对不对?
  • 要不要人工确认?

静态断点非常适合卡在这里。

整体流程结构:

START
  ↓
parseInput        // 解析用户意图
  ↓
prepareAction     // 生成“即将执行的操作”
  ↓
executeAction     // 真正产生副作用
  ↓
 END

我们要做的事:在 executeAction 执行之前,强制暂停流程。

import { z } from "zod/v4";
import readline from "readline-sync";
import { StateGraph, START, END, MemorySaver } from "@langchain/langgraph";

const StateSchema = z.object({
  action: z.string().optional().describe("要执行的行为"),
  to: z.email().optional().describe("要对哪一个对象执行这个行为"),
  content: z.string().optional().describe("该行为对应的具体内容"),
});

type State = z.infer<typeof StateSchema>;

// 1. 解析用户意图节点
async function parseInput(): Promise<State> {
  return {
    action: "send_email", // 意图:发邮件
    to: "test@example.com", // 收件人
    content: "Hello, this is a test email", // 邮件内容
  };
}

// 2. 准备即将执行的操作
async function prepareAction(state: State): Promise<State> {
  console.log("\n【准备执行的操作】");
  console.log({
    action: state.action,
    to: state.to,
    content: state.content,
  });
  return state;
}

// 3. 执行具体的操作
async function executeAction(state: State): Promise<State> {
  console.log("\n🚨 正在执行真实操作!");
  console.log(`邮件已发送给 ${state.to}`);
  return state;
}

const checkpointer = new MemorySaver();

// 4. 编排图
const graph = new StateGraph(StateSchema)
  .addNode("parseInput", parseInput)
  .addNode("prepareAction", prepareAction)
  .addNode("executeAction", executeAction)
  .addEdge(START, "parseInput")
  .addEdge("parseInput", "prepareAction")
  .addEdge("prepareAction", "executeAction")
  .addEdge("executeAction", END)
  .compile({
    checkpointer,
    interruptBefore: ["executeAction"], // 在具体执行动作之前,需要中断
  });

async function main() {
  const config = {
    configurable: {
      thread_id: "static-interrupt-demo",
    },
  };

  // 执行整个图
  console.log("\n=== 第一次执行:跑到静态断点 ===");
  await graph.invoke({}, config);

  // 第一次执行的时候,跑到静态断点,就会从图里面出来,回到主逻辑
  console.log("\n流程已在 executeAction 前暂停");
  const input = readline.question("是否确认执行真实操作?(y / n): ");

  if (input.toLowerCase() !== "y") {
    console.log("\n❌ 操作已取消,流程结束");
    return;
  }
  // 说明用户输入的是 y 或者 Y
  console.log("\n✅ 已确认,继续执行流程");
  // 恢复图的执行,会从中断的节点开始执行
  // 1. 第一个参数传递null:因为这一次是从中断点开始执行,null表示使用checkpointer保存的状态继续
  // 2. 第二个参数config
  await graph.invoke(null, config);
}

main();
posted @ 2026-03-17 14:18  Zhentiw  阅读(2)  评论(0)    收藏  举报