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

使用 interrupt 暂停

interrupt 函数会暂停图的执行并将一个值返回给调用者。当你在节点内调用 interrupt 时,LangGraph 会保存当前的图状态并等待你使用输入来恢复执行。 要使用 interrupt,你需要:
  1. 一个检查点记录器来持久化图状态(生产环境中应使用持久化的检查点记录器)
  2. 在配置中提供一个线程 ID,以便运行时知道从哪个状态恢复
  3. 在你希望暂停的位置调用 interrupt()(有效负载必须是 JSON 可序列化的)
from langgraph.types import interrupt

def approval_node(state: State):
    # 暂停并请求批准
    approved = interrupt("你批准这个操作吗?")

    # 当你恢复时,Command(resume=...) 会在此处返回该值
    return {"approved": approved}
当你调用 interrupt 时,会发生以下情况:
  1. 图的执行在调用 interrupt 的准确位置被挂起
  2. 使用检查点记录器保存状态,以便稍后恢复执行。在生产环境中,这应当是一个持久化的检查点记录器(例如由数据库支持)
  3. 值被返回给调用者:当使用事件流 (graph.stream_events(..., version="v3")) 时,它会出现在 stream.interrupts 上;若使用默认的 invoke() API,则会在 __interrupt__ 下返回;它可以是任何 JSON 可序列化的值(字符串、对象、数组等)
  4. 图无限期等待,直到你使用响应恢复执行
  5. 当你恢复时,响应会传回节点,成为 interrupt() 调用的返回值

恢复中断

中断暂停执行后,你可以通过使用包含恢复值的 Command 再次调用图来恢复它。这个恢复值会被传递回 interrupt 调用,使得节点能够使用外部输入继续执行。 推荐使用事件流来驱动可能中断的图——它会通过 stream.interruptsstream.interrupted 暴露中断,并通过 stream.output 暴露最终状态。
from langgraph.types import Command

# Initial run - hits the interrupt and pauses
# thread_id is the persistent pointer (stores a stable ID in production)
config = {"configurable": {"thread_id": "thread-1"}}
stream = graph.stream_events({"input": "data"}, config=config, version="v3")

# Drain the stream to drive the run; stream.output awaits the final state.
final = stream.output

# stream.interrupted is True when the run paused for human input, and
# stream.interrupts contains the payloads passed to interrupt().
if stream.interrupted:
    print(stream.interrupts)
    # > (Interrupt(value='Do you approve this action?'),)

# Resume with the human's response
# The resume payload becomes the return value of interrupt() inside the node
resumed = graph.stream_events(Command(resume=True), config=config, version="v3")
final = resumed.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 参数(updategotograph)是设计用于从节点函数返回的。不要将 Command(update=...) 作为输入传递给多轮对话——请传递一个普通输入字典。

常见模式

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

使用人工介入(HITL)中断进行流式处理

在构建具有人工介入工作流的交互式智能体时,你可以使用事件流在循环中并发消费消息块和状态快照,同时处理中断。 在循环中使用 graph.stream_events(..., version="v3") 返回的类型化投影,直到运行完成:
  • 通过 stream.messages 逐令牌地流式传输 AI 响应
  • 通过 stream.values 观察每一步的状态快照
  • 通过 stream.interrupted 检测中断,并从 stream.interrupts 读取其有效负载
  • 通过再次调用 stream_events 并传入 Command(resume=...) 来恢复执行,并重复此过程直到 stream.interrupted 为 false
from langgraph.types import Command

stream_input: dict | Command = initial_input

while True:
    stream = graph.stream_events(stream_input, config=config, version="v3")

    # Stream LLM message chunks (including any in subgraphs) as they arrive.
    for message in stream.messages:
        for token in message.text:
            display_streaming_content(token)

    # After the run finishes (or pauses), check for interrupts and resume.
    if not stream.interrupted:
        final_state = stream.output
        break

    interrupt_info = stream.interrupts[0].value
    user_response = get_user_input(interrupt_info)
    stream_input = Command(resume=user_response)
  • stream.messages: 以内容块形式输出的聊天模型输出;迭代每个 message.text 以获取令牌差异。对于嵌套子图,从 stream.subgraphs[*].messages 读取消息块。
  • stream.values: 每一步之后完整的状态快照
  • stream.interrupted / stream.interrupts: 每次运行后,检查图是否暂停;从 stream.interrupts 读取有效负载
  • Command(resume=...): 作为下一个 stream_events 输入传递以恢复执行;循环直到运行完成且不再中断

处理多个中断

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

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt


class State(TypedDict):
    vals: Annotated[list[str], operator.add]


def node_a(state):
    answer = interrupt("question_a")
    return {"vals": [f"a:{answer}"]}


def node_b(state):
    answer = interrupt("question_b")
    return {"vals": [f"b:{answer}"]}


graph = (
    StateGraph(State)
    .add_node("a", node_a)
    .add_node("b", node_b)
    .add_edge(START, "a")
    .add_edge(START, "b")
    .add_edge("a", END)
    .add_edge("b", END)
    .compile(checkpointer=InMemorySaver())
)

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

# Step 1: stream events to drive the run; both parallel nodes hit interrupt() and pause
stream = graph.stream_events({"vals": []}, config, version="v3")
_ = stream.output  # drive the stream to completion
# stream.interrupts contains the pending Interrupt payloads
print(stream.interrupts)
# > (Interrupt(value='question_a', id='...'), Interrupt(value='question_b', id='...'))

# Step 2: resume all pending interrupts at once
resume_map = {
    i.id: f"answer for {i.value}" for i in stream.interrupts
}
resumed = graph.stream_events(Command(resume=resume_map), config, version="v3")

print("Final state:", resumed.output)
# Final state: {'vals': ['a:answer for question_a', 'b:answer for question_b']}

批准或拒绝

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

def approval_node(state: State) -> Command[Literal["proceed", "cancel"]]:
    # 暂停执行;有效负载会出现在 stream.interrupts(使用 stream_events 时)或 result["__interrupt__"](使用 invoke 时)
    is_approved = interrupt({
        "question": "是否继续此操作?",
        "details": state["action_details"]
    })

    # 根据响应进行路由
    if is_approved:
        return Command(goto="proceed")  # 在提供恢复有效负载后执行
    else:
        return Command(goto="cancel")
恢复图时,传递 True 表示批准,传递 False 表示拒绝:
# 批准
graph.stream_events(Command(resume=True), config=config, version="v3").output

# 拒绝
graph.stream_events(Command(resume=False), config=config, version="v3").output
from typing import Literal, Optional, TypedDict

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt


class ApprovalState(TypedDict):
    action_details: str
    status: Optional[Literal["pending", "approved", "rejected"]]


def approval_node(state: ApprovalState) -> Command[Literal["proceed", "cancel"]]:
    # Expose details so the caller can render them in a UI
    decision = interrupt(
        {
            "question": "Approve this action?",
            "details": state["action_details"],
        }
    )

    # Route to the appropriate node after resume
    return Command(goto="proceed" if decision else "cancel")


def proceed_node(state: ApprovalState):
    return {"status": "approved"}


def cancel_node(state: ApprovalState):
    return {"status": "rejected"}


builder = StateGraph(ApprovalState)
builder.add_node("approval", approval_node)
builder.add_node("proceed", proceed_node)
builder.add_node("cancel", cancel_node)
builder.add_edge(START, "approval")
builder.add_edge("proceed", END)
builder.add_edge("cancel", END)

# Use a more durable checkpointer in production
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "approval-123"}}
initial = graph.stream_events(
    {"action_details": "Transfer $500", "status": "pending"},
    config=config,
    version="v3",
)
_ = initial.output  # drive the stream to completion
print(initial.interrupts)  # -> (Interrupt(value={'question': ..., 'details': ...}),)

# Resume with the decision; True routes to proceed, False to cancel
resumed = graph.stream_events(Command(resume=True), config=config, version="v3")
print(resumed.output["status"])

审查和编辑状态

有时,你希望让人类在继续之前审查并编辑图状态的某一部分。这对于纠正 LLM 输出、添加缺失信息或进行调整非常有用。
from langgraph.types import interrupt

def review_node(state: State):
    # 暂停并显示当前内容以供审查(有效负载会暴露在 stream.interrupts 中)
    edited_content = interrupt({
        "instruction": "审查并编辑此内容",
        "content": state["generated_text"]
    })

    # 用编辑后的版本更新状态
    return {"generated_text": edited_content}
恢复时,提供编辑后的内容:
graph.stream_events(
    Command(resume="已编辑和改进的文本"),  # 该值将成为 interrupt() 的返回值
    config=config,
    version="v3",
).output
from typing import TypedDict

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt


class ReviewState(TypedDict):
    generated_text: str


def review_node(state: ReviewState):
    # Ask a reviewer to edit the generated content
    updated = interrupt(
        {
            "instruction": "Review and edit this content",
            "content": state["generated_text"],
        }
    )
    return {"generated_text": updated}


builder = StateGraph(ReviewState)
builder.add_node("review", review_node)
builder.add_edge(START, "review")
builder.add_edge("review", END)

checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "review-42"}}
initial = graph.stream_events(
    {"generated_text": "Initial draft"}, config=config, version="v3"
)
_ = initial.output  # drive the stream to completion
print(initial.interrupts)  # -> (Interrupt(value={'instruction': ..., 'content': ...}),)

# Resume with the edited text from the reviewer
final_state = graph.stream_events(
    Command(resume="Improved draft after review"),
    config=config,
    version="v3",
)
print(final_state.output["generated_text"])  # -> "Improved draft after review"

工具中的中断

你也可以直接在工具函数内部放置中断。这使得工具在被调用时自身会暂停以等待批准,并允许在工具调用执行之前进行人工审查和编辑。 首先,定义一个使用 interrupt 的工具:
from langchain.tools import tool
from langgraph.types import interrupt

@tool
def send_email(to: str, subject: str, body: str):
    """Send an email to a recipient."""

    # 发送前暂停;使用事件流时有效负载会暴露在 stream.interrupts 上
    response = interrupt({
        "action": "send_email",
        "to": to,
        "subject": subject,
        "body": body,
        "message": "批准发送这封邮件吗?"
    })

    if response.get("action") == "approve":
        # 恢复值可以在执行前覆盖输入
        final_to = response.get("to", to)
        final_subject = response.get("subject", subject)
        final_body = response.get("body", body)
        return f"邮件已发送至 {final_to},主题为 '{final_subject}'"
    return "用户取消了邮件"
当你希望审批逻辑与工具本身共存,使其在图的不同部分可重用时,这种方法非常有用。LLM 可以自然地调用该工具,并且每当工具被调用时,中断都会暂停执行,允许你批准、编辑或取消该操作。
import sqlite3
import operator
from typing import TypedDict, Annotated, Literal
from langchain.tools import tool
from langchain_anthropic import ChatAnthropic
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, interrupt
from langchain.messages import AnyMessage, SystemMessage, ToolMessage


class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]


@tool
def send_email(to: str, subject: str, body: str):
    """Send an email to a recipient."""

    # 发送前暂停;使用事件流时有效负载会暴露在 stream.interrupts 上
    response = interrupt({
        "action": "send_email",
        "to": to,
        "subject": subject,
        "body": body,
        "message": "批准发送这封邮件吗?",
    })

    if response.get("action") == "approve":
        final_to = response.get("to", to)
        final_subject = response.get("subject", subject)
        final_body = response.get("body", body)

        # 实际发送邮件(此处为你的实现)
        print(f"[send_email] to={final_to} subject={final_subject} body={final_body}")
        return f"邮件已发送至 {final_to}"

    return "用户取消了邮件"


model = ChatAnthropic(model="claude-sonnet-4-6").bind_tools([send_email])
tools_by_name = {"send_email": send_email}


def agent_node(state: AgentState):
    # LLM 可能决定调用工具;中断会在发送前暂停
    result = model.invoke(state["messages"])
    return {"messages": [result]}

def tool_node(state: AgentState):
    """执行工具调用"""
    result = []
    for tool_call in state["messages"][-1].tool_calls:
        tool = tools_by_name[tool_call["name"]]
        observation = tool.invoke(tool_call["args"])
        result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
    return {"messages": result}

def should_continue(state: AgentState) -> Literal["tool_node", END]:
    """根据 LLM 是否进行了工具调用来决定是继续循环还是停止"""
    messages = state["messages"]
    last_message = messages[-1]

    if last_message.tool_calls:
        return "tool_node"
    return END

builder = StateGraph(AgentState)
builder.add_node("agent", agent_node)
builder.add_node("tool_node", tool_node)

builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, ["tool_node", END])  # 路由到 "tools" 或 END
builder.add_edge("tool_node", "agent")  # 工具执行后返回

checkpointer = SqliteSaver(
    sqlite3.connect("tool-approval.db", check_same_thread=False)
)
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "email-workflow"}}
initial = graph.stream_events(
    {
        "messages": [
            {"role": "user", "content": "发邮件给 alice@example.com,讨论会议事宜"}
        ]
    },
    config=config,
    version="v3",
)
initial.output  # 驱动流完成
print(initial.interrupts)  # -> (Interrupt(value={'action': 'send_email', ...}),)

# 恢复时批准并可选择编辑参数
resumed = graph.stream_events(
    Command(resume={"action": "approve", "subject": "已更新的主题"}),
    config=config,
    version="v3",
)
print(resumed.output["messages"][-1])  # -> send_email 返回的工具结果

验证人类输入

有时你需要验证人类的输入,如果无效则再次询问。你可以通过在一个循环中使用多个 interrupt 调用来实现这一点。
from langgraph.types import interrupt

def get_age_node(state: State):
    prompt = "你的年龄是多少?"

    while True:
        answer = interrupt(prompt)  # 使用事件流时有效负载会暴露在 stream.interrupts 上

        # 验证输入
        if isinstance(answer, int) and answer > 0:
            # 有效输入 - 继续
            break
        else:
            # 无效输入 - 用更具体的提示再次询问
            prompt = f"'{answer}' 不是一个有效的年龄。请输入一个正数。"

    return {"age": answer}
每次你用无效输入恢复图时,它都会用更清晰的消息再次询问。一旦提供了有效输入,节点就完成,图继续执行。
import sqlite3
from typing import TypedDict

from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt


class FormState(TypedDict):
    age: int | None


def get_age_node(state: FormState):
    prompt = "What is your age?"

    while True:
        answer = interrupt(prompt)

        if isinstance(answer, int) and answer > 0:
            return {"age": answer}

        prompt = f"'{answer}' is not a valid age. Please enter a positive number."


builder = StateGraph(FormState)
builder.add_node("collect_age", get_age_node)
builder.add_edge(START, "collect_age")
builder.add_edge("collect_age", END)

checkpointer = SqliteSaver(sqlite3.connect("forms.db"))
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "form-1"}}
first = graph.stream_events({"age": None}, config=config, version="v3")
_ = first.output  # drive the stream to completion
print(first.interrupts)  # -> (Interrupt(value='What is your age?', ...),)

# Provide invalid data; the node re-prompts
retry = graph.stream_events(Command(resume="thirty"), config=config, version="v3")
_ = retry.output  # drive the stream to completion
print(retry.interrupts)  # -> (Interrupt(value="'thirty' is not a valid age...", ...),)

# Provide valid data; loop exits and state updates
final = graph.stream_events(Command(resume=30), config=config, version="v3")
print(final.output["age"])  # -> 30

中断的规则

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

不要将 interrupt 调用包裹在 try/except 中

interrupt 通过在调用点抛出一个特殊异常来暂停执行。如果你将 interrupt 调用包裹在 try/except 块中,你会捕获这个异常,中断将不会传回给图。
  • ✅ 将 interrupt 调用与容易出错的代码分开
  • ✅ 在 try/except 块中使用特定的异常类型
def node_a(state: State):
    # ✅ 好的做法:先中断,然后单独处理
    # 错误情况
    interrupt("你叫什么名字?")
    try:
        fetch_data()  # 这可能会失败
    except Exception as e:
        print(e)
    return state
  • 🔴 不要将 interrupt 调用包裹在裸 try/except 块中
def node_a(state: State):
    # ❌ 糟糕的做法:在裸 try/except 中包裹中断
    # 会捕获中断异常
    try:
        interrupt("你叫什么名字?")
    except Exception as e:
        print(e)
    return state

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

在单个节点内使用多个中断是很常见的,但如果不小心处理,可能会导致意外行为。 当一个节点包含多个中断调用时,LangGraph 会维护一个特定于执行该节点的任务的恢复值列表。每当执行恢复时,它都会从节点的开头开始。对于遇到的每个中断,LangGraph 都会检查任务的恢复列表中是否存在匹配的值。匹配是严格基于索引的,因此节点内中断调用的顺序很重要。
  • ✅ 保持 interrupt 调用在节点执行之间一致
def node_a(state: State):
    # ✅ 好的做法:中断调用每次都以相同的顺序发生
    name = interrupt("你叫什么名字?")
    age = interrupt("你的年龄是多少?")
    city = interrupt("你住在哪个城市?")

    return {
        "name": name,
        "age": age,
        "city": city
    }
  • 🔴 不要有条件地跳过节点内的 interrupt 调用
  • 🔴 不要使用在不同执行之间不是确定性的逻辑来循环 interrupt 调用
def node_a(state: State):
    # ❌ 糟糕的做法:有条件地跳过中断会改变顺序
    name = interrupt("你叫什么名字?")

    # 在第一次运行时,这可能会跳过中断
    # 在恢复时,它可能不会跳过——导致索引不匹配
    if state.get("needs_age"):
        age = interrupt("你的年龄是多少?")

    city = interrupt("你住在哪个城市?")

    return {"name": name, "city": city}

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

根据所使用的检查点记录器,复杂的值可能无法序列化(例如,你无法序列化一个函数)。为了使你的图能够适应任何部署,最佳实践是仅使用可以合理序列化的值。
  • ✅ 向 interrupt 传递简单的、可 JSON 序列化的类型
  • ✅ 传递包含简单值的字典/对象
def node_a(state: State):
    # ✅ 好的做法:传递可序列化的简单类型
    name = interrupt("你叫什么名字?")
    count = interrupt(42)
    approved = interrupt(True)

    return {"name": name, "count": count, "approved": approved}
  • 🔴 不要向 interrupt 传递函数、类实例或其他复杂对象
def validate_input(value):
    return len(value) > 0

def node_a(state: State):
    # ❌ 糟糕的做法:向 interrupt 传递函数
    # 函数无法被序列化
    response = interrupt({
        "question": "你叫什么名字?",
        "validator": validate_input  # 这会失败
    })
    return {"name": response}

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

由于中断会重新运行调用它们的节点,因此在 interrupt 之前调用的副作用(理想情况下)应该是幂等的。作为背景,幂等性意味着同一个操作可以被多次应用,而不会改变初始执行之外的结果。 例如,你可能会在节点内部有一个更新记录的 API 调用。如果在该调用之后又调用了 interrupt,那么当节点恢复时,该调用会被多次重新运行,可能会覆盖初始更新或创建重复记录。
  • ✅ 在 interrupt 之前使用幂等操作
  • ✅ 将副作用放在 interrupt 调用之后
  • ✅ 尽可能将副作用分隔到单独的节点中
def node_a(state: State):
    # ✅ 好的做法:使用幂等的 upsert 操作
    # 多次运行此操作将得到相同的结果
    db.upsert_user(
        user_id=state["user_id"],
        status="pending_approval"
    )

    approved = interrupt("批准此更改吗?")

    return {"approved": approved}
  • 🔴 不要在 interrupt 之前执行非幂等的操作
  • 🔴 不要在未检查记录是否存在的情况下创建新记录
def node_a(state: State):
    # ❌ 糟糕的做法:在中断之前创建新记录
    # 每次恢复都会创建重复记录
    audit_id = db.create_audit_log({
        "user_id": state["user_id"],
        "action": "pending_approval",
        "timestamp": datetime.now()
    })

    approved = interrupt("批准此更改吗?")

    return {"approved": approved, "audit_id": audit_id}

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

当在节点内调用子图时,父图将从调用子图并且触发 interrupt 的节点的开头恢复执行。同样,子图也将从调用 interrupt 的节点开头恢复。
def node_in_parent_graph(state: State):
    some_code()  # <-- 恢复时将重新执行这部分
    # 以函数形式调用子图。
    # 该子图包含一个 `interrupt` 调用。
    subgraph_result = subgraph.invoke(some_input)
    # ...

def node_in_subgraph(state: State):
    some_other_code()  # <-- 恢复时这也将重新执行
    result = interrupt("你叫什么名字?")
    # ...

使用中断进行调试

要调试和测试图,你可以使用静态中断作为断点,一次一个节点地单步通过图执行。静态中断是在节点执行之前或之后的已定义点触发的。你可以在编译图时通过指定 interrupt_beforeinterrupt_after 来设置它们。
静态中断推荐用于人工介入工作流。请改用 interrupt 函数。
graph = builder.compile(
    interrupt_before=["node_a"],
    interrupt_after=["node_b", "node_c"],
    checkpointer=checkpointer,
)

# 向图传递一个线程 ID
config = {
    "configurable": {
        "thread_id": "some_thread"
    }
}

# 运行图直到断点
graph.invoke(inputs, config=config)

# 恢复图
graph.invoke(None, config=config)
  1. 断点在 compile 时设置。
  2. interrupt_before 指定在节点执行之前应暂停的节点。
  3. interrupt_after 指定在节点执行之后应暂停的节点。
  4. 需要使用检查点记录器来启用断点。
  5. 图运行直到遇到第一个断点。
  6. 通过传入 None 作为输入来恢复图。这将运行图直到遇到下一个断点。
要调试你的中断,请使用 LangSmith

使用 LangSmith Studio

你可以使用 LangSmith Studio 在 UI 中为你的图设置静态中断,然后再运行图。你也可以使用 UI 在执行中的任意点检查图状态。 image