以编程方式使用文档

中断允许您在特定点暂停图执行并等待外部输入后再继续。这实现了人机协作模式,在此模式下您需要外部输入才能继续。当触发中断时,LangGraph 使用其 持久化 层保存图状态,并无限期等待直到您恢复执行。

中断通过调用 interrupt() 函数来实现,您可以在图的任意节点中调用它。该函数接受任何可序列化为 JSON 的值,并将其暴露给调用者。当您准备继续时,使用 Command重新调用图来恢复执行,这将成为 interrupt() 从节点内部调用的返回值。

与静态断点(在特定节点之前或之后暂停)不同,中断是 **动态的**:它们可以放置在代码的任何位置,并且可以根据应用逻辑有条件地触发。

  • 检查点保存您的位置: 检查点写入器会精确保存图的状态,以便您稍后可以恢复,即使处于错误状态。
  • - **thread_id 是指针:** 使用 { configurable: { thread_id: ... } } 作为 invoke 方法的选项来指定检查点要加载的状态。
  • - **中断负载作为 __interrupt__:** 传递给 interrupt() 的值通过 __interrupt__ 字段返回给调用者,以便您知道图正在等待什么。

您选择的 thread_id 实际上就是您的持久游标。重用它会从同一检查点恢复;使用新值会启动一个具有空状态的新线程。

使用以下方式暂停 interrupt

interrupt 函数会暂停图执行并向调用者返回值。当您调用 interrupt 时,LangGraph 会保存当前图状态并等待您恢复执行并提供输入。

要使用 interrupt,您需要: 1. A **检查点** 持久保存图状态(生产环境中使用持久化检查点) 2. A **线程 ID** 在配置中,以便运行时知道从哪个状态恢复 3. 在您想暂停的位置调用 interrupt() (负载必须可序列化为 JSON)

async function approvalNode(state: State) {
    // Pause and ask for approval
    const approved = interrupt("Do you approve this action?");

    // Command({ resume: ... }) provides the value returned into this variable
    return { approved };
}

当您调用 interrupt, 发生的事情如下:

  1. **图执行被挂起** 在恰好调用 interrupt 的位置
  2. **状态被保存** 使用检查点器,以便稍后可以恢复执行,在生产环境中,这应该是一个持久化检查点器(例如,由数据库支持)
  1. **值被返回** 给调用方, __interrupt__;它可以是任何 JSON 可序列化值(字符串、对象、数组等)
  1. **图无限期等待** 直到你使用响应恢复执行
  2. **响应在恢复时被传递回** 节点,成为 interrupt() 调用的返回值

恢复会中断

在中断暂停执行后,你可以通过使用包含恢复值的 Command 再次调用图来恢复它。恢复值被传递回 interrupt 调用,使节点能够使用外部输入继续执行。

// Initial run - hits the interrupt and pauses
// thread_id is the durable pointer back to the saved checkpoint
const config = { configurable: { thread_id: "thread-1" } };
const result = await graph.invoke({ input: "data" }, config);

// Check what was interrupted
// __interrupt__ mirrors every payload you passed to interrupt()
console.log(result.__interrupt__);
// [{ value: 'Do you approve this action?', ... }]

// Resume with the human's response
// Command({ resume }) returns that value from interrupt() in the node
await graph.invoke(new Command({ resume: true }), config);

关于恢复的关键要点:

  • - 你必须使用 **相同的线程 ID** 恢复时必须使用中断发生时所使用的
  • - 传递给 new Command({ resume: ... }) 的值将成为 interrupt 调用的返回值
  • - 节点从 interrupt 被调用的节点开始处重新启动,因此 interrupt 之前的任何代码都会再次运行
  • - 你可以传递任何可 JSON 序列化的值作为恢复值

常见模式

中断解锁的关键在于能够暂停执行并等待外部输入。这对于多种用例非常有用,包括:

  • - 审批工作流:在执行关键操作(API 调用、数据库更改、金融交易)之前暂停
  • - 处理多个中断:在单次调用中恢复多个中断时,将中断 ID 与恢复值配对
  • - 审查和编辑:让人类在继续之前审查和修改 LLM 输出或工具调用
  • - 中断工具调用:在执行工具调用之前暂停,以审查和编辑工具调用
  • - 验证人工输入:在继续下一步之前暂停以验证人工输入

使用人在回路 (HITL) 中断进行流式传输

在构建具有人在回路工作流的交互式代理时,你可以使用 事件流式传输 在处理中断的同时并发消费消息块和状态快照。

在循环中使用 graph.stream_events(..., version="v3") 返回的类型化投影,直到运行完成: - 通过 stream.messages - 按 token 流式传输 AI 响应 stream.values - 通过 stream.interrupted 观察每步状态快照 stream.interrupts - 通过 stream_events 检测中断 Command(resume=...) 并从 stream.interrupted 读取其负载

  • - **stream.messages**: 聊天模型输出为内容块;迭代 message.text 用于令牌增量。对于嵌套子图,从...读取消息块 stream.subgraphs[*].messages.
  • - **stream.values**: 每步后的完整状态快照
  • - **stream.interrupted / stream.interrupts**: 每次运行后,检查图是否暂停;从...读取负载 stream.interrupts
  • - **Command(resume=...)**: 传递为下一个 streamEvents 输入以恢复;循环直到运行完成而不中断

处理多个中断

当并行分支同时中断时(例如,扇出到多个节点,每个节点调用 interrupt()),您可能需要在一次调用中恢复多个中断。 使用单次调用恢复多个中断时,请将每个中断ID映射到其恢复值。 这确保了每个响应在运行时与正确的中断配对。

  Annotation,
  Command,
  END,
  INTERRUPT,
  MemorySaver,
  START,
  StateGraph,
  interrupt,
  isInterrupted,
} from "@langchain/langgraph";

const State = Annotation.Root({
  vals: Annotation<string[]>({
    reducer: (left, right) =>
      left.concat(Array.isArray(right) ? right : [right]),
    default: () => [],
  }),
});

function nodeA(_state: typeof State.State) {
  const answer = interrupt("question_a") as string;
  return { vals: [`a:${answer}`] };
}

function nodeB(_state: typeof State.State) {
  const answer = interrupt("question_b") as string;
  return { vals: [`b:${answer}`] };
}

const graph = new StateGraph(State)
  .addNode("a", nodeA)
  .addNode("b", nodeB)
  .addEdge(START, "a")
  .addEdge(START, "b")
  .addEdge("a", END)
  .addEdge("b", END)
  .compile({ checkpointer: new MemorySaver() });

const config = { configurable: { thread_id: "1" } };

async function main() {
  // Step 1: invoke - both parallel nodes hit interrupt() and pause
  const interruptedResult = await graph.invoke({ vals: [] }, config);
  console.log(interruptedResult);
  /*
  {
    vals: [],
    __interrupt__: [
      { id: '...', value: 'question_a' },
      { id: '...', value: 'question_b' }
    ]
  }
  */

  // Step 2: resume all pending interrupts at once
  const resumeMap: Record<string, string> = {};
  if (isInterrupted(interruptedResult)) {
    for (const i of interruptedResult[INTERRUPT]) {
      if (i.id != null) {
        resumeMap[i.id] = `answer for ${i.value}`;
      }
    }
  }
  const result = await graph.invoke(new Command({ resume: resumeMap }), config);

  console.log("Final state:", result);
  //> Final state: { vals: ['a:answer for question_a', 'b:answer for question_b'] }
}

main().catch(console.error);

批准或拒绝

中断最常见的用途之一是在关键操作之前暂停并请求批准。例如,您可能希望让人类批准API调用、数据库更改或任何其他重要决策。

const approvalNode: typeof State.Node = (state) => {
  // Pause execution; payload surfaces in result.__interrupt__
  const isApproved = interrupt({
    question: "Do you want to proceed?",
    details: state.actionDetails
  });

  // Route based on the response
  if (isApproved) {
    return new Command({ goto: "proceed" }); // Runs after the resume payload is provided
  } else {
    return new Command({ goto: "cancel" });
  }
}

当您恢复图时,传递 true 以批准,或 false 以拒绝:

// To approve
await graph.invoke(new Command({ resume: true }), config);

// To reject
await graph.invoke(new Command({ resume: false }), config);

Full example

      Command,
      MemorySaver,
      START,
      END,
      StateGraph,
      StateSchema,
      interrupt,
    } from "@langchain/langgraph";


    const State = new StateSchema({
      actionDetails: z.string(),
      status: z.enum(["pending", "approved", "rejected"]).nullable(),
    });

    const graphBuilder = new StateGraph(State)
      .addNode("approval", async (state) => {
        // Expose details so the caller can render them in a UI
        const decision = interrupt({
          question: "Approve this action?",
          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);

    // Use a more durable checkpointer in production
    const checkpointer = new MemorySaver();
    const graph = graphBuilder.compile({ checkpointer });

    const config = { configurable: { thread_id: "approval-123" } };
    const initial = await graph.invoke(
      { actionDetails: "Transfer $500", status: "pending" },
      config,
    );
    console.log(initial.__interrupt__);
    // [{ value: { question: ..., details: ... } }]

    // Resume with the decision; true routes to proceed, false to cancel
    const resumed = await graph.invoke(new Command({ resume: true }), config);
    console.log(resumed.status); // -> "approved"
    

审阅和编辑状态

有时您希望让人类在继续之前审阅和编辑图状态的部分内容。这对于纠正LLM、添加缺失信息或进行调整非常有用。

const reviewNode: typeof State.Node = (state) => {
  // Pause and show the current content for review (surfaces in result.__interrupt__)
  const editedContent = interrupt({
    instruction: "Review and edit this content",
    content: state.generatedText
  });

  // Update the state with the edited version
  return { generatedText: editedContent };
}

恢复时,提供编辑的内容:

await graph.invoke(
  new Command({ resume: "The edited and improved text" }), // Value becomes the return from interrupt()
  config
);

Full example

      Command,
      MemorySaver,
      START,
      END,
      StateGraph,
      StateSchema,
      interrupt,
    } from "@langchain/langgraph";


    const State = new StateSchema({
      generatedText: z.string(),
    });

    const builder = new StateGraph(State)
      .addNode("review", async (state) => {
        // Ask a reviewer to edit the generated content
        const updated = interrupt({
          instruction: "Review and edit this content",
          content: state.generatedText,
        });
        return { generatedText: updated };
      })
      .addEdge(START, "review")
      .addEdge("review", END);

    const checkpointer = new MemorySaver();
    const graph = builder.compile({ checkpointer });

    const config = { configurable: { thread_id: "review-42" } };
    const initial = await graph.invoke({ generatedText: "Initial draft" }, config);
    console.log(initial.__interrupt__);
    // [{ value: { instruction: ..., content: ... } }]

    // Resume with the edited text from the reviewer
    const finalState = await graph.invoke(
      new Command({ resume: "Improved draft after review" }),
      config,
    );
    console.log(finalState.generatedText); // -> "Improved draft after review"
    

工具中的中断

您也可以直接在工具函数中放置中断。这使得工具本身在每次被调用时暂停以等待批准,并允许在执行前对工具调用进行人工审阅和编辑。

首先,定义一个使用interrupt:

const sendEmailTool = tool(
  async ({ to, subject, body }) => {
    // Pause before sending; payload surfaces in result.__interrupt__
    const response = interrupt({
      action: "send_email",
      to,
      subject,
      body,
      message: "Approve sending this email?",
    });

    if (response?.action === "approve") {
      // Resume value can override inputs before executing
      const finalTo = response.to ?? to;
      const finalSubject = response.subject ?? subject;
      const finalBody = response.body ?? body;
      return `Email sent to ${finalTo} with subject '${finalSubject}'`;
    }
    return "Email cancelled by user";
  },
  {
    name: "send_email",
    description: "Send an email to a recipient",
    schema: z.object({
      to: z.string(),
      subject: z.string(),
      body: z.string(),
    }),
  },
);

当您希望审批逻辑与工具本身绑定,使其可在图的不同部分重复使用时,这种方法特别有用。LLM可以自然地调用工具,而中断会在工具被触发时暂停执行,让您可以批准、编辑或取消该操作。

Full example

      Command,
      MemorySaver,
      START,
      END,
      StateGraph,
      StateSchema,
      MessagesValue,
      GraphNode,
      interrupt,
    } from "@langchain/langgraph";


    const sendEmailTool = tool(
      async ({ to, subject, body }) => {
        // Pause before sending; payload surfaces in result.__interrupt__
        const response = interrupt({
          action: "send_email",
          to,
          subject,
          body,
          message: "Approve sending this email?",
        });

        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 `Email sent to ${finalTo}`;
        }
        return "Email cancelled by user";
      },
      {
        name: "send_email",
        description: "Send an email to a recipient",
        schema: z.object({
          to: z.string(),
          subject: z.string(),
          body: z.string(),
        }),
      },
    );

    const model = new ChatAnthropic({ model: "claude-sonnet-4-6" }).bindTools([sendEmailTool]);

    const State = new StateSchema({
      messages: MessagesValue,
    });

    const agent: typeof State.Node = async (state) => {
      // LLM may decide to call the tool; interrupt pauses before sending
      const response = await model.invoke(state.messages);
      return { messages: [response] };
    };

    const graphBuilder = new StateGraph(State)
      .addNode("agent", agent)
      .addEdge(START, "agent")
      .addEdge("agent", END);

    const checkpointer = new MemorySaver();
    const graph = graphBuilder.compile({ checkpointer });

    const config = { configurable: { thread_id: "email-workflow" } };
    const initial = await graph.invoke(
      {
        messages: [
          { role: "user", content: "Send an email to alice@example.com about the meeting" },
        ],
      },
      config,
    );
    console.log(initial.__interrupt__); // -> [{ value: { action: 'send_email', ... } }]

    // Resume with approval and optionally edited arguments
    const resumed = await graph.invoke(
      new Command({
        resume: { action: "approve", subject: "Updated subject" },
      }),
      config,
    );
    console.log(resumed.messages.at(-1)); // -> Tool result returned by send_email
    

验证人工输入

有时您需要验证人工输入的值,若无效则重新提示。建议的方法是调用 interrupt() **每节点调用一次**,从节点返回并将错误信息存储在状态中,然后使用 **条件边** 循环回到节点,直到提供有效值。

正确的模式:

  1. 将重新提示的问题存储在状态中(例如 pendingQuestion).
  2. 在节点中,调用 interrupt() **恰好一次**,传递状态中的当前问题。
  3. 如果答案无效,返回更新后的 pendingQuestion 以便下次调用时重新提示。
  4. 使用 addConditionalEdges 路由回节点,直到收集到有效值。

每次恢复调用 getAgeNode 恰好一次,运行 interrupt() 调用一次,然后退出。当答案无效时,条件边会循环返回,下次中断会用更新后的问题重新提示。

Full example

      Command,
      MemorySaver,
      START,
      END,
      StateGraph,
      StateSchema,
      interrupt,
    } from "@langchain/langgraph";


    const State = new StateSchema({
      age: z.number().nullable(),
      pendingQuestion: z.string().nullable(),
    });

    const builder = new StateGraph(State)
      .addNode("collectAge", (state) => {
        const question = state.pendingQuestion ?? "What is your age?";
        const answer = interrupt(question); // called exactly once per invocation

        if (typeof answer === "number" && answer > 0) {
          return { age: answer, pendingQuestion: null };
        }
        return { pendingQuestion: `'${answer}' is not a valid age. Please enter a positive number.` };
      })
      .addEdge(START, "collectAge")
      .addConditionalEdges("collectAge", (state) =>
        state.age !== null ? END : "collectAge"
      );

    const checkpointer = new MemorySaver();
    const graph = builder.compile({ checkpointer });

    const config = { configurable: { thread_id: "form-1" } };
    const first = await graph.invoke({ age: null, pendingQuestion: null }, config);
    console.log(first.__interrupt__); // -> [{ value: "What is your age?", ... }]

    // Provide invalid data; the node re-prompts via the conditional edge
    const retry = await graph.invoke(new Command({ resume: "thirty" }), config);
    console.log(retry.__interrupt__); // -> [{ value: "'thirty' is not a valid age...", ... }]

    // Provide valid data; route returns END and the graph finishes
    const final = await graph.invoke(new Command({ resume: 30 }), config);
    console.log(final.age); // -> 30
    

中断规则

当你在节点内调用 interrupt 时,LangGraph 通过抛出一个特殊异常来暂停执行,该异常向运行时发出暂停信号。这个异常会向上传播通过调用栈,被运行时捕获,运行时通知图保存当前状态并等待外部输入。

当执行恢复时(在你提供请求的输入之后),运行时从头重新启动整个节点——它不会从调用 interrupt 的确切行继续。这意味着在 interrupt 之前运行的任何代码都会再次执行。正因为如此,在使用中断时需要遵循一些重要规则以确保它们按预期行为。

不要包装 interrupt calls in try/catch

interrupt 通过抛出一个特殊异常来在调用点暂停执行。如果你用 try-catch 包装 interrupt call in a try/catch block, you will catch this exception and the interrupt will not be passed back to the graph.

  • * ✅ 将 interrupt 调用与易出错的代码分开
  • * ✅ 必要时可以有条件地捕获错误
const nodeA: GraphNode<typeof State> = async (state) => {
  // ✅ Good: interrupting first, then handling error conditions separately
  const name = interrupt("What's your name?");
  try {
    await fetchData(); // This can fail
  } catch (err) {
    console.error(error);
  }
  return state;
}
const nodeA: GraphNode<typeof State> = async (state) => {
  // ✅ Good: re-throwing the exception will
  // allow the interrupt to be passed back to
  // the graph
  try {
    const name = interrupt("What's your name?");
    await fetchData(); // This can fail
  } catch (err) {
    if (error instanceof NetworkError) {
      console.error(error);
    }
    throw error;
  }
  return state;
}
  • * 🔴 不要包装 interrupt calls in bare try/catch blocks
async function nodeA(state: State) {
    // ❌ Bad: wrapping interrupt in bare try/catch will catch the interrupt exception
    try {
        const name = interrupt("What's your name?");
    } catch (err) {
        console.error(error);
    }
    return state;
}

不要重新排序 interrupt 节点内的调用

在单个节点中使用多个中断很常见,但如果处理不当可能会导致意外行为。

当节点包含多个 interrupt 调用时,LangGraph 会为执行该节点的任务维护一个特定的恢复值列表。每当执行恢复时,它会从节点的开头开始。对于遇到的每个 interrupt,LangGraph 会检查任务的恢复列表中是否存在匹配的值。匹配是 **严格基于索引的**,因此节点内 interrupt 调用的顺序很重要。

  • * ✅ 保持 interrupt 调用在节点执行过程中保持一致
async function nodeA(state: State) {
    // ✅ Good: interrupt calls happen in the same order every time
    const name = interrupt("What's your name?");
    const age = interrupt("What's your age?");
    const city = interrupt("What's your city?");

    return {
        name,
        age,
        city
    };
}
  • * 🔴 不要有条件地跳过 interrupt 在节点内的调用
  • * 🔴 不要循环 interrupt 使用在执行过程中不确定的逻辑进行调用,包括 while True 验证循环。请改用条件边(请参阅 验证人工输入)
const nodeA: GraphNode<typeof State> = async (state) => {
  // ❌ Bad: conditionally skipping interrupts changes the order
  const name = interrupt("What's your name?");

  // On first run, this might skip the interrupt
  // On resume, it might not skip it - causing index mismatch
  if (state.needsAge) {
    const age = interrupt("What's your age?");
  }

  const city = interrupt("What's your city?");

  return { name, city };
}
const nodeA: GraphNode<typeof State> = async (state) => {
  // ❌ Bad: looping based on non-deterministic data
  // The number of interrupts changes between executions
  const results = [];
  for (const item of state.dynamicList || []) {  // List might change between runs
    const result = interrupt(`Approve ${item}?`);
    results.push(result);
  }

  return { results };
}

不要在 interrupt 调用中返回复杂值

根据所使用的检查点不同,复杂值可能无法序列化(例如,您无法序列化函数)。为了使图能够适应任何部署环境,最佳实践是仅使用可以合理序列化的值。

  • * ✅ 传递简单的、JSON 可序列化的类型到 interrupt
  • * ✅ Pass dictionaries/objects with simple values
const nodeA: GraphNode<typeof State> = async (state) => {
  // ✅ Good: passing simple types that are serializable
  const name = interrupt("What's your name?");
  const count = interrupt(42);
  const approved = interrupt(true);

  return { name, count, approved };
}
const nodeA: GraphNode<typeof State> = async (state) => {
  // ✅ Good: passing objects with simple values
  const response = interrupt({
    question: "Enter user details",
    fields: ["name", "email", "age"],
    currentValues: state.user || {}
  });

  return { user: response };
}
  • * 🔴 不要将函数、类实例或其他复杂对象传递给 interrupt
function validateInput(value: string): boolean {
    return value.length > 0;
}

const nodeA: GraphNode<typeof State> = async (state) => {
  // ❌ Bad: passing a function to interrupt
  // The function cannot be serialized
  const response = interrupt({
    question: "What's your name?",
    validator: validateInput  // This will fail
  });
  return { name: response };
}
class DataProcessor {
    constructor(private config: any) {}
}

const nodeA: GraphNode<typeof State> = async (state) => {
  const processor = new DataProcessor({ mode: "strict" });

  // ❌ Bad: passing a class instance to interrupt
  // The instance cannot be serialized
  const response = interrupt({
    question: "Enter data to process",
    processor: processor  // This will fail
  });
  return { result: response };
}

interrupt 之前调用的副作用必须是幂等的

由于 interrupt 的工作方式是通过重新运行调用它们的节点,因此在 interrupt 之前调用的副作用(理想情况下)应该是幂等的。从上下文来看,幂等性意味着同一操作可以多次应用,而不会改变初次执行之后的结果。

例如,您可能在节点内部有一个 API 调用来更新记录。如果 interrupt 在该调用之后被调用,那么当节点恢复时它会被重新运行多次,可能会覆盖初始更新或创建重复记录。

  • * ✅ 在 interrupt
  • * ✅ 将副作用放在 interrupt 之后
  • * ✅ 尽可能将副作用分离到单独的节点中
const nodeA: GraphNode<typeof State> = async (state) => {
  // ✅ Good: using upsert operation which is idempotent
  // Running this multiple times will have the same result
  await db.upsertUser({
    userId: state.userId,
    status: "pending_approval"
  });

  const approved = interrupt("Approve this change?");

  return { approved };
}
const nodeA: GraphNode<typeof State> = async (state) => {
  // ✅ Good: placing side effect after the interrupt
  // This ensures it only runs once after approval is received
  const approved = interrupt("Approve this change?");

  if (approved) {
    await db.createAuditLog({
      userId: state.userId,
      action: "approved"
    });
  }

  return { approved };
}
const approvalNode: GraphNode<typeof State> = async (state) => {
  // ✅ Good: only handling the interrupt in this node
  const approved = interrupt("Approve this change?");

  return { approved };
}

const notificationNode: GraphNode<typeof State> = async (state) => {
  // ✅ Good: side effect happens in a separate node
  // This runs after approval, so it only executes once
  if (state.approved) {
    await sendNotification({
      userId: state.userId,
      status: "approved",
    });
  }

  return state;
}
  • * 🔴 不要在 interrupt
  • * 🔴 不要在创建新记录前不检查其是否存在
const nodeA: GraphNode<typeof State> = async (state) => {
  // ❌ Bad: creating a new record before interrupt
  // This will create duplicate records on each resume
  const auditId = await db.createAuditLog({
    userId: state.userId,
    action: "pending_approval",
    timestamp: new Date()
  });

  const approved = interrupt("Approve this change?");

  return { approved, auditId };
}
const nodeA: GraphNode<typeof State> = async (state) => {
  // ❌ Bad: appending to an array before interrupt
  // This will add duplicate entries on each resume
  await db.appendToHistory(state.userId, "approval_requested");

  const approved = interrupt("Approve this change?");

  return { approved };
}

将 interrupt 与作为函数调用的子图结合使用

在节点内调用子图时,父图将从 **节点的开头** (子图被调用的位置)恢复执行,并且 interrupt 被触发时也会发生类似情况 **子图** 也将从节点的开头恢复执行interrupt被调用的位置)

async function nodeInParentGraph(state: State) {
    someCode(); // <-- This will re-execute when resumed
    // Invoke a subgraph as a function.
    // The subgraph contains an `interrupt` call.
    const subgraphResult = await subgraph.invoke(someInput);
    // ...
}

async function nodeInSubgraph(state: State) {
    someOtherCode(); // <-- This will also re-execute when resumed
    const result = interrupt("What's your name?");
    // ...
}

使用 interrupt 进行调试

要调试和测试图,您可以使用静态 interrupt 作为断点来逐步执行图,每次一个节点。静态 interrupt 在定义的点触发,要么在节点执行之前,要么在节点执行之后。您可以通过指定 interruptBeforeinterruptAfter 编译图时。

At compile time

        const graph = builder.compile({
            interruptBefore: ["node_a"],  // [!code highlight]
            interruptAfter: ["node_b", "node_c"],  // [!code highlight]
            checkpointer,
        });

        // Pass a thread ID to the graph
        const config = {
            configurable: {
                thread_id: "some_thread"
            }
        };

        // Run the graph until the breakpoint
        await graph.invoke(inputs, config);# [!code highlight]

        await graph.invoke(null, config);  # [!code highlight]
        
  1. 断点在 compile time.
  2. interruptBefore 指定执行应在节点执行之前暂停的节点。
  3. interruptAfter 指定执行应在节点执行之后暂停的节点。
  4. 需要检查点器来启用断点。
  5. 图运行直到命中第一个断点。
  6. 通过传入 null 作为输入来恢复图。这将运行图直到命中下一个断点。

At run time

        // Run the graph until the breakpoint
        graph.invoke(inputs, {
            interruptBefore: ["node_a"],  // [!code highlight]
            interruptAfter: ["node_b", "node_c"],  // [!code highlight]
            configurable: {
                thread_id: "some_thread"
            }
        });

        // Resume the graph
        await graph.invoke(null, config);  // [!code highlight]
        
  1. graph.invoke 使用 interruptBeforeinterruptAfter 参数调用。这是一种运行时配置,可以在每次调用时更改。
  2. interruptBefore 指定执行应在节点执行之前暂停的节点。
  3. interruptAfter 指定执行应在节点执行之后暂停的节点。
  4. 图运行直到命中第一个断点。
  5. 通过传入 null 作为输入来恢复图。这将运行图直到命中下一个断点。

使用 LangSmith Studio

您可以使用 LangSmith Studio 在 UI 中运行图之前设置静态中断。您还可以使用 UI 检查执行过程中任何点的图状态。

!图片