跳到主要内容
中断允许您在特定点暂停图执行,并等待外部输入后再继续。这实现了需要外部输入才能继续的人工参与模式。当触发中断时,LangGraph 会使用其持久化层保存图状态,并无限期地等待您恢复执行。 中断的工作原理是在图节点中的任何点调用 interrupt() 函数。该函数接受任何 JSON 可序列化的值,并将其返回给调用者。当您准备好继续时,您可以通过使用 Command 重新调用图来恢复执行,该 Command 随后成为节点内部 interrupt() 调用的返回值。 与静态断点(在特定节点之前或之后暂停)不同,中断是动态的——它们可以放置在代码中的任何位置,并且可以根据应用程序逻辑进行条件设置。
  • 检查点保留您的位置:检查点会将精确的图状态写入,以便您以后可以恢复,即使在错误状态下也能恢复。
  • thread_id 是您的指针:使用 { configurable: { thread_id: ... } } 作为 invoke 方法的选项,以告诉检查点器加载哪个状态。
  • 中断负载以 __interrupt__ 形式呈现:您传递给 interrupt() 的值以 __interrupt__ 字段的形式返回给调用者,以便您知道图正在等待什么。
您选择的 thread_id 实际上是您的持久游标。重复使用它会恢复相同的检查点;使用新值会启动一个全新的空状态线程。

使用 interrupt 暂停

interrupt 函数会暂停图执行并向调用者返回一个值。当您在节点内调用interrupt时,LangGraph 会保存当前图状态并等待您带输入恢复执行。 要使用interrupt,您需要:
  1. 一个检查点器来持久化图状态(在生产环境中使用持久检查点器)
  2. 配置中的一个线程 ID,以便运行时知道从哪个状态恢复
  3. 在您想要暂停的地方调用 interrupt()(负载必须是 JSON 可序列化的)
import { interrupt } from "@langchain/langgraph";

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. 状态被保存使用检查点器,以便以后可以恢复执行。在生产环境中,这应该是一个持久的检查点器(例如,由数据库支持)
  3. 值返回给调用者,位于 __interrupt__ 字段下;它可以是任何 JSON 可序列化的值(字符串、对象、数组等)
  4. 图无限期等待直到您带响应恢复执行
  5. 响应被传递回节点,当您恢复时,它成为 interrupt() 调用的返回值

恢复中断

中断暂停执行后,您通过再次调用它,并带有一个包含恢复值的 Command 来恢复图。恢复值被传递回 interrupt 调用,允许节点继续执行外部输入。
import { Command } from "@langchain/langgraph";

// 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
  • 传递给 Command(resume=...) 的值成为interrupt调用的返回值
  • 节点从调用interrupt的节点开头重新启动,因此interrupt之前的任何代码会再次运行
  • 您可以将任何 JSON 可序列化的值作为恢复值

常见模式

中断解锁的关键功能是暂停执行并等待外部输入的能力。这对于各种用例都很有用,包括
  • 审批工作流:在执行关键操作(API 调用、数据库更改、金融交易)之前暂停
  • 审查和编辑:允许人类在继续之前审查和修改 LLM 输出或工具调用
  • 中断工具调用:在执行工具调用之前暂停,以便在执行前审查和编辑工具调用
  • 验证人类输入:在进入下一步之前暂停以验证人类输入

批准或拒绝

中断最常见的用途之一是在关键操作之前暂停并请求批准。例如,您可能希望要求人工批准 API 调用、数据库更改或任何其他重要决策。
import { interrupt, Command } from "@langchain/langgraph";

function approvalNode(state: State): Command {
  // 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);
import {
  Command,
  MemorySaver,
  START,
  END,
  StateGraph,
  interrupt,
} from "@langchain/langgraph";
import * as z from "zod";

const State = z.object({
  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)、添加缺失信息或进行调整很有用。
import { interrupt } from "@langchain/langgraph";

function reviewNode(state: 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
);
import {
  Command,
  MemorySaver,
  START,
  END,
  StateGraph,
  interrupt,
} from "@langchain/langgraph";
import * as z from "zod";

const State = z.object({
  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的工具:
import { tool } from "@langchain/core/tools";
import { interrupt } from "@langchain/langgraph";
import * as z from "zod";

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 可以自然地调用工具,并且中断将在每次调用工具时暂停执行,从而允许您批准、编辑或取消操作。
import { tool } from "@langchain/core/tools";
import { ChatAnthropic } from "@langchain/anthropic";
import {
  Command,
  MemorySaver,
  START,
  END,
  StateGraph,
  interrupt,
} from "@langchain/langgraph";
import * as z from "zod";

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-5-20250929" }).bindTools([sendEmailTool]);

const Message = z.object({
  role: z.enum(["user", "assistant", "tool"]),
  content: z.string(),
});

const State = z.object({
  messages: z.array(Message),
});

const graphBuilder = new StateGraph(State)
  .addNode("agent", async (state) => {
    // LLM may decide to call the tool; interrupt pauses before sending
    const response = await model.invoke(state.messages);
    return { messages: [...state.messages, response] };
  })
  .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调用来实现此目的。
import { interrupt } from "@langchain/langgraph";

function getAgeNode(state: State) {
  let prompt = "What is your age?";

  while (true) {
    const answer = interrupt(prompt); // payload surfaces in result.__interrupt__

    // Validate the input
    if (typeof answer === "number" && answer > 0) {
      // Valid input - continue
      return { age: answer };
    } else {
      // Invalid input - ask again with a more specific prompt
      prompt = `'${answer}' is not a valid age. Please enter a positive number.`;
    }
  }
}
每次您使用无效输入恢复图时,它都会以更清晰的消息再次询问。一旦提供了有效输入,节点完成,图继续。
import {
  Command,
  MemorySaver,
  START,
  END,
  StateGraph,
  interrupt,
} from "@langchain/langgraph";
import * as z from "zod";

const State = z.object({
  age: z.number().nullable(),
});

const builder = new StateGraph(State)
  .addNode("collectAge", (state) => {
    let prompt = "What is your age?";

    while (true) {
      const answer = interrupt(prompt); // payload surfaces in result.__interrupt__

      if (typeof answer === "number" && answer > 0) {
        return { age: answer };
      }

      prompt = `'${answer}' is not a valid age. Please enter a positive number.`;
    }
  })
  .addEdge(START, "collectAge")
  .addEdge("collectAge", END);

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

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

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

// Provide valid data; loop exits and state updates
const final = await graph.invoke(new Command({ resume: 30 }), config);
console.log(final.age); // -> 30

中断规则

当您在节点内调用interrupt时,LangGraph 会通过抛出一个特殊异常来暂停执行,该异常指示运行时暂停。此异常会向上通过调用栈传播,并被运行时捕获,运行时会通知图保存当前状态并等待外部输入。 当执行恢复时(在您提供请求的输入之后),运行时会从头开始重新启动整个节点——它不会从调用interrupt的精确行恢复。这意味着在interrupt之前运行的任何代码都将再次执行。因此,在使用中断时需要遵循一些重要规则,以确保它们按预期运行。

不要将 interrupt 调用包装在 try/catch 中

interrupt在调用点暂停执行的方式是抛出一个特殊异常。如果您将interrupt调用包装在 try/catch 块中,您将捕获此异常,并且中断将不会传递回图中。
  • ✅ 将interrupt调用与易出错的代码分开
  • ✅ 必要时有条件地捕获错误
async function nodeA(state: 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;
}
  • 🔴 不要将interrupt调用包装在裸 try/catch 块中
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 调用

在一个节点中使用多个中断是很常见的,但如果不小心处理,这可能会导致意外行为。 当一个节点包含多个中断调用时,LangGraph 会为执行该任务的特定任务保留一个恢复值列表。每当执行恢复时,它都会从节点的开头开始。对于遇到的每个中断,LangGraph 都会检查任务的恢复列表中是否存在匹配值。匹配是严格基于索引的,因此中断调用在节点中的顺序很重要。
  • ✅ 保持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调用
async function nodeA(state: 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 };
}

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

根据所使用的检查点器,复杂值可能无法序列化(例如,您无法序列化函数)。为了使您的图能够适应任何部署,最佳实践是仅使用可以合理序列化的值。
  • ✅ 将简单、JSON 可序列化的类型传递给interrupt
  • ✅ 传递带有简单值的字典/对象
async function nodeA(state: 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 };
}
  • 🔴 不要将函数、类实例或其他复杂对象传递给interrupt
function validateInput(value: string): boolean {
    return value.length > 0;
}

async function nodeA(state: 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 };
}

中断前调用的副作用必须是幂等的

由于中断的工作原理是重新运行它们被调用的节点,因此在调用interrupt之前调用的副作用应该(理想情况下)是幂等的。上下文是,幂等性意味着相同的操作可以多次应用,而结果除了初始执行之外不会改变。 例如,您可能在一个节点内有一个 API 调用来更新记录。如果在此调用之后调用interrupt,当节点恢复时,它将被多次重新运行,可能会覆盖初始更新或创建重复记录。
  • ✅ 在interrupt之前使用幂等操作
  • ✅ 将副作用放置在interrupt调用之后
  • ✅ 尽可能将副作用分离到单独的节点中
async function nodeA(state: 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 };
}
  • 🔴 不要在interrupt之前执行非幂等操作
  • 🔴 不要不检查记录是否存在就创建新记录
async function nodeA(state: 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 };
}

与作为函数调用的子图一起使用

当在一个节点内调用子图时,父图将从子图被调用并且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?");
    // ...
}

使用中断进行调试

为了调试和测试图,您可以使用静态中断作为断点,一次步进一个节点执行。静态中断在节点执行之前或之后在定义点触发。您可以通过在编译图时指定 interruptBeforeinterruptAfter 来设置这些。
静态中断建议用于人工参与工作流。请改用interrupt方法。
  • 编译时
  • 运行时
const graph = builder.compile({
    interruptBefore: ["node_a"],  
    interruptAfter: ["node_b", "node_c"],  
    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 时设置。
  2. interruptBefore 指定在节点执行之前应暂停执行的节点。
  3. interruptAfter 指定在节点执行之后应暂停执行的节点。
  4. 需要检查点器才能启用断点。
  5. 图将运行直到遇到第一个断点。
  6. 通过传入 null 作为输入来恢复图。这将运行图直到下一个断点被命中。

使用 LangGraph Studio

您可以使用LangGraph Studio在运行图之前在 UI 中设置图中的静态中断。您还可以使用 UI 在执行的任何点检查图状态。 image
以编程方式连接这些文档到 Claude、VSCode 等,通过 MCP 获取实时答案。
© . This site is unofficial and not affiliated with LangChain, Inc.