中断允许您在图执行的特定点暂停,并在继续之前等待外部输入。这实现了“人在回路”的模式,适用于需要外部输入才能继续执行的场景。当中断被触发时,LangGraph 会使用其持久化层保存图的状态,并无限期等待,直到您恢复执行。 中断通过在您图的节点中的任何位置调用 interrupt() 函数来工作。该函数接受任何 JSON 可序列化的值,该值会暴露给调用者。当您准备好继续执行时,通过使用 Command 重新调用图来恢复执行,然后 Command 中的值就成为节点内部 interrupt() 调用的返回值。 与静态断点(在特定节点之前或之后暂停)不同,中断是动态的:它们可以放置在代码中的任何位置,并且可以根据您的应用逻辑设置为有条件的。
  • 检查点保留您的进度: 检查点器会写入精确的图状态,因此您稍后可以恢复,即使处于错误状态时也是如此。
  • thread_id 是您的指针: 设置 config={"configurable": {"thread_id": ...}} 以告知检查点器要加载哪个状态。
  • 中断载荷通过 stream.interrupts 暴露: 当使用事件流 (graph.stream_events(..., version="v3")) 时,您传递给 interrupt() 的值会出现在 stream.interrupts 中,并且当运行因等待输入而暂停时,stream.interrupted 为 True。
您选择的 thread_id 实际上就是您的持久化游标。重用它可以恢复到同一个检查点;使用新值则会启动一个全新的、具有空状态的线程。

使用 interrupt 暂停

interrupt 函数会暂停图的执行并返回一个值给调用者。当您在节点内调用 interrupt 时,LangGraph 会保存当前的图状态,并等待您用输入恢复执行。 要使用 interrupt,您需要:
  1. 一个检查点器来持久化图的状态(在生产环境中使用持久化的检查点器)
  2. 在您的配置中设置一个线程 ID,以便运行时知道要从哪个状态恢复
  3. 在您想要暂停的地方调用 interrupt()(载荷必须是 JSON 可序列化的)
当您调用 interrupt 时,会发生以下情况:
  1. 图的执行被挂起 在调用 interrupt 的确切位置
  2. 状态被保存 使用检查点器,以便稍后可以恢复执行。在生产环境中,这应该是持久化的检查点器(例如,由数据库支持)
  3. 值被返回 给调用者,当使用事件流 (graph.stream_events(..., version="v3")) 时通过 stream.interrupts 返回,或使用默认的 invoke() API 时通过 __interrupt__ 返回;它可以是任何 JSON 可序列化的值(字符串、对象、数组等)
  4. 图无限期等待 直到您用响应恢复执行
  5. 响应被传回 节点,当您恢复时,它成为 interrupt() 调用的返回值

恢复中断

在中断暂停执行后,您可以通过再次调用图并传递一个包含恢复值的 Command 来恢复图。该恢复值会被传回 interrupt 调用,允许节点使用外部输入继续执行。 驱动可能中断的图的推荐方式是事件流 ——它通过 stream.interrupts 和 stream.interrupted 暴露中断,并通过 stream.output 暴露最终状态。
默认的 graph.invoke(...) API 仍然有效,并通过 result["__interrupt__"] 暴露中断。当您不需要流式投影时使用它;否则请首选 graph.stream_events(..., version="v3")。
关于恢复的关键点:
  • 恢复时必须使用与中断发生时相同的线程 ID
  • 传递给 Command(resume=...) 的值会成为 interrupt 调用的返回值
  • 节点在恢复时会从调用 interrupt 的节点开头重新开始运行,因此 interrupt 之前的任何代码都会再次运行
  • 您可以传递任何 JSON 可序列化的值作为恢复值
Command(resume=...) 是唯一设计用于作为 invoke()/stream()/stream_events() 输入的 Command 模式。其他 Command 参数(update、goto、graph)是设计用于从节点函数返回的。不要传递 Command(update=...) 作为输入来继续多轮对话——而是传递一个普通的输入字典。

常见模式

中断解锁的关键能力是暂停执行并等待外部输入。这对于多种用例都很有用,包括:
  • 审批工作流: 在执行关键操作(API 调用、数据库更改、金融交易)之前暂停
  • 处理多个中断: 在单次调用中恢复多个中断时,将中断 ID 与恢复值配对
  • 审查和编辑: 让人类在继续之前审查和修改 LLM 输出或工具调用
  • 中断工具调用: 在执行工具调用之前暂停,以便在执行前审查和编辑工具调用
  • 验证人类输入: 在继续下一步之前暂停,以验证人类输入

使用人在回路 (HITL) 中断进行流式处理

当构建具有人在回路工作流的交互式代理时,您可以使用事件流在处理中断的同时并发消费消息块和状态快照。 使用 graph.stream_events(..., version="v3") 返回的类型化投影,循环处理直到运行完成:
  • 通过 stream.messages 逐个标记地流式传输 AI 响应
  • 通过 stream.values 观察每一步的状态快照
  • 通过 stream.interrupted 检测中断,并从 stream.interrupts 读取其载荷
  • 通过再次使用 Command(resume=...) 调用 stream_events 来恢复执行,并重复此过程直到 stream.interrupted 为 false
  • stream.messages:聊天模型输出作为内容块;迭代每个 message.text 获取标记增量。对于嵌套子图,从 stream.subgraphs[*].messages 读取消息块。
  • stream.values:每步之后的完整状态快照
  • stream.interrupted / stream.interrupts:每次运行后,检查图是否暂停;从 stream.interrupts 读取载荷
  • Command(resume=...):作为下一个 stream_events 输入传递以恢复;循环直到运行完成而不中断

处理多个中断

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

批准或拒绝

中断最常见的用途之一是在关键操作之前暂停并请求批准。例如,您可能希望让人类批准一个 API 调用、数据库更改或任何其他重要决定。
当您恢复图时,传递 True 以批准或 False 以拒绝:

审查和编辑状态

有时您希望让人类在继续之前审查并编辑部分图状态。这对于纠正 LLM、添加缺失信息或进行调整很有用。
恢复时,提供编辑后的内容:

工具中的中断

您还可以将中断直接放置在工具函数内部。这使得工具本身在被调用时会暂停等待批准,并允许在执行前对工具调用进行人工审查和编辑。 首先,定义一个使用 interrupt 的工具:
当您希望审批逻辑与工具本身一起存在,使其在图的不同部分可重用时,这种方法很有用。LLM 可以自然地调用该工具,中断会在工具被调用时暂停执行,允许您批准、编辑或取消该操作。

验证人类输入

有时您需要验证来自人类的输入,如果值无效则重新提示。推荐的方法是在每次节点调用时只调用一次 interrupt(),将错误消息存储在状态中从节点返回,并使用条件边循环回该节点,直到提供有效值。
避免在单个节点内使用 while True + interrupt() 循环。 因为节点在每次恢复时都会从头开始重新运行(参见中断规则),调用 interrupt() 多次的循环会导致每次恢复时重播所有之前的迭代:第一次恢复重播 1 次迭代,第二次重播 2 次,依此类推。结果是循环体内的任何代码都会被指数级地重新执行。
正确的模式:
  1. 将重新提示的问题存储在状态中(例如 pending_question)。
  2. 在节点中,只调用一次 interrupt(),传递状态中的当前问题。
  3. 如果答案无效,返回更新的 pending_question,以便下次调用时重新提示。
  4. 使用 add_conditional_edges 路由回该节点,直到收集到有效值。
每次恢复恰好调用一次 get_age_node,运行一次 interrupt() 调用,然后退出。当答案无效时,条件边循环回来,下一个中断使用更新的问题重新提示。每次恢复时没有代码运行超过一次。

中断规则

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

不要用 try/except 包裹 interrupt 调用

interrupt 在调用点暂停执行的方式是抛出一个特殊异常。如果您将 interrupt 调用包裹在 try/except 块中,您将捕获此异常,中断将不会被传回图。
  • ✅ 将 interrupt 调用与容易出错的代码分开
  • ✅ 在 try/except 块中使用特定的异常类型
  • 🔴 不要用裸的 try/except 块包裹 interrupt 调用

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

在单个节点中使用多个中断很常见,然而如果不小心处理,这可能会导致意外行为。 当一个节点包含多个中断调用时,LangGraph 会为执行该节点的任务维护一个恢复值列表。每当执行恢复时,它会从节点的开头开始。对于遇到的每个中断,LangGraph 会检查任务的恢复列表中是否存在匹配的值。匹配是严格基于索引的,因此节点内中断调用的顺序很重要。
  • ✅ 在跨节点执行时保持 interrupt 调用一致
  • 🔴 不要条件性地跳过节点内的 interrupt 调用
  • 🔴 不要使用跨执行不确定性的逻辑(包括 while True 验证循环)来循环 interrupt 调用。请改用条件边(参见验证人类输入)

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

根据使用的检查点器,复杂值可能无法序列化(例如,您无法序列化一个函数)。为了使您的图能够适应任何部署环境,最佳实践是只使用可以合理序列化的值。
  • ✅ 将简单的、JSON 可序列化的类型传递给 interrupt
  • ✅ 传递具有简单值的字典/对象
  • 🔴 不要将函数、类实例或其他复杂对象传递给 interrupt

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

因为中断的工作原理是重新运行它们被调用的节点,所以在 interrupt 之前调用的副作用(理想情况下)应该是幂等的。作为背景,幂等性意味着相同的操作可以多次应用,而不会在初始执行之外改变结果。 例如,您可能在节点内部有一个更新记录的 API 调用。如果在该调用之后调用了 interrupt,当节点恢复时,它将被重新运行多次,可能会覆盖初始更新或创建重复记录。
  • ✅ 在 interrupt 之前使用幂等操作
  • ✅ 将副作用放在 interrupt 调用之后
  • ✅ 尽可能将副作用分离到单独的节点中
  • 🔴 不要在 interrupt 之前执行非幂等操作
  • 🔴 在不检查记录是否存在的情况下,不要创建新记录

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

当在节点内调用子图时,父图将从触发子图调用和 interrupt 的节点开头恢复执行。同样,子图也会从调用 interrupt 的节点开头恢复执行。

使用中断进行调试

要调试和测试图,你可以使用静态中断作为断点,逐节点地单步执行图的执行过程。静态中断在节点执行之前或之后的定义点触发。你可以在编译图时通过指定 interrupt_before 和 interrupt_after 来进行设置。
静态中断不推荐用于人机交互工作流。请改用 interrupt 函数。
  1. 断点是在 compile(编译)时设置的。
  2. interrupt_before 指定在节点执行前应暂停执行的节点。
  3. interrupt_after 指定在节点执行后应暂停执行的节点。
  4. 需要一个检查点记录器(checkpointer)来启用断点。
  5. 图会运行直到命中第一个断点。
  6. 通过传入 None 作为输入来恢复图。这将运行图直到命中下一个断点。
要调试你的中断,请使用 LangSmith。

使用 LangSmith Studio

你可以在运行图之前,通过 LangSmith Studio 的 UI 界面为你的图设置静态中断。你也可以使用 UI 在执行过程中的任意节点检查图的状态。 image