Langgraph基础(4)-中断interrupt.实现图执行的动态暂停与外部交互

大家好,今天给大家分享LangGraph中一个非常实用的功能------Interrupts(中断)。在LangGraph构建复杂智能体或工作流时,我们经常需要在特定节点暂停执行,等待外部输入(比如人工审批、用户反馈)后再继续,而Interrupts正是为这个场景量身打造的核心能力。

本文将从核心概念、使用方法、常见场景、注意事项四个维度,结合可直接运行的Python示例,帮大家快速掌握Interrupts的用法,避免踩坑,轻松落地到实际项目中。

一、核心概念:什么是LangGraph Interrupts?

Interrupts(中断)是LangGraph提供的动态暂停机制,允许我们在图的任意节点、任意代码位置暂停执行,等待外部输入(如人工确认、用户输入)后再恢复执行。其核心价值在于实现"人机协同"(Human-in-the-Loop, HITL)工作流,让智能体不再是"黑盒执行",而是可以在关键步骤接受人类干预。

和静态断点(固定在节点前后暂停)不同,Interrupts具备以下特性:

  • 动态性:可放在节点内任意代码位置,支持根据业务逻辑条件触发(比如只有金额大于1000时才触发审批中断)。
  • 状态持久化:暂停时,LangGraph会通过Checkpointer(检查点)保存当前图的完整状态,即使服务重启也能恢复执行。
  • 灵活恢复:恢复时可传入任意JSON可序列化的值,该值会作为中断函数的返回值,供节点继续执行。
    关键前提:使用Interrupts必须配置Checkpointer(如InMemorySaver、SqliteSaver),用于持久化图状态;同时需要指定thread_id,作为恢复执行的"唯一标识"(相当于执行上下文的ID)。
    Interrupts核心工作流程示意图(清晰展示"执行-暂停-恢复"全流程):

执行节点代码
保存状态(Checkpointer)
接收外部输入(Command(resume=...))
节点执行完成
图开始执行
触发interrupt
暂停执行,等待外部输入
恢复执行,继续节点代码
图继续运行/结束

说明:图示中,Checkpointer负责在暂停时保存状态,thread_id作为"状态指针",确保恢复时能找到对应的暂停节点。

二、快速上手:Interrupts基础使用流程

使用Interrupts只需3步:配置Checkpointer → 调用interrupt()函数暂停 → 用Command(resume=...)恢复执行。下面结合最简示例,带大家快速跑通流程。

基础使用三步流程图:
示例:InMemorySaver/SqliteSaver
传入提示信息,触发暂停
传入外部输入,返回interrupt()结果
1.配置Checkpointer
2.调用interrupt暂停
3.用Command resume=...恢复
节点继续执行,图完成运行

2.1 环境准备

首先安装所需依赖(确保LangGraph版本≥1.1,支持v2版本的流模式):

bash 复制代码
pip install langgraph langchain_anthropic  # anthropic可选,用于后续复杂示例

2.2 基础示例:简单暂停与恢复

需求:构建一个简单的图,在节点中暂停,等待用户输入"是否批准"后,继续执行并返回结果。

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import interrupt, Command
from typing import TypedDict

# 1. 定义图的状态(存储执行过程中的数据)
class State(TypedDict):
    approved: bool  # 存储审批结果

# 2. 定义节点函数(包含中断逻辑)
def approval_node(state: State):
    # 触发中断,暂停执行,向外部请求输入(JSON可序列化值均可)
    approved = interrupt("Do you approve this action?")
    # 恢复执行后,approved即为外部传入的resume值
    return {"approved": approved}

# 3. 构建图并配置Checkpointer(必须配置,否则无法持久化状态)
builder = StateGraph(State)
builder.add_node("approval", approval_node)  # 添加节点
builder.add_edge(START, "approval")          # 起始节点→审批节点
builder.add_edge("approval", END)            # 审批节点→结束节点

# 编译图,使用内存Checkpointer(生产环境建议用SqliteSaver、PostgresSaver等持久化存储)
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)

# 4. 执行图(触发中断)
# thread_id:唯一标识,恢复时必须使用相同的ID
config = {"configurable": {"thread_id": "thread-1001"}}
# 首次执行,触发中断并暂停
initial_result = graph.invoke({"approved": False}, config=config, version="v2")

# 查看中断信息(interrupts字段存储中断请求)
print("中断请求:", initial_result.interrupts)
# 输出:中断请求:(Interrupt(value='Do you approve this action?'),)

# 5. 恢复执行(传入外部输入)
resumed_result = graph.invoke(Command(resume=True), config=config, version="v2")
print("恢复执行结果:", resumed_result)
# 输出:恢复执行结果: {'approved': True}

核心说明:

  • interrupt()函数:传入任意JSON可序列化值(字符串、字典、列表等),作为中断请求的"提示信息",供外部感知需要输入什么。
  • Command(resume=...):恢复执行时必须传入该对象,resume的值会作为interrupt()的返回值,供节点继续执行。
  • version="v2":LangGraph 1.1+推荐使用v2版本,中断信息会放在result.interrupts中,更易获取。

三、实战场景:Interrupts常见用法(附完整示例)

Interrupts的核心应用场景是"需要外部干预"的工作流,下面结合4个高频场景,给出可直接复用的代码示例。

Interrupts四大高频应用场景汇总:
适用:转账、发邮件等敏感操作
适用:并行工作流,多节点同时暂停
适用:工具调用安全校验
适用:用户输入合法性校验
Interrupts高频场景
场景1:关键操作审批
场景2:多并行节点中断
场景3:工具调用前中断
场景4:用户输入校验
人工审批后执行
一次性恢复所有中断
审核参数后执行工具
循环中断直到输入有效

场景1:关键操作审批(最常用)

需求:在执行敏感操作(如转账、发送邮件)前,暂停并请求人工审批,审批通过则执行,拒绝则取消。

关键操作审批流程图:
暂停,等待人工输入
批准
拒绝
开始
审批节点:触发中断请求审批
人工审批结果
执行操作节点:更新状态为approved
取消操作节点:更新状态为rejected
结束

python 复制代码
from typing import Literal, TypedDict
from langgraph.checkpoint.sqlite import SqliteSaver  # 生产环境用SQLite持久化
from langgraph.graph import StateGraph, START, END
from langgraph.types import interrupt, Command

# 定义状态
class ApprovalState(TypedDict):
    action_details: str  # 操作详情
    status: Literal["pending", "approved", "rejected"]  # 审批状态

# 审批节点:触发中断,根据审批结果路由
def approval_node(state: ApprovalState) -> Command[Literal["proceed", "cancel"]]:
    # 中断时传入操作详情,方便人工判断
    decision = interrupt({
        "question": "是否批准该操作?",
        "action_details": state["action_details"]
    })
    # 根据审批结果,路由到不同节点
    if decision:
        return Command(goto="proceed")  # 批准→执行操作
    else:
        return Command(goto="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)

# 编译图(使用SQLite持久化状态,避免服务重启丢失)
checkpointer = SqliteSaver("approval.db")
graph = builder.compile(checkpointer=checkpointer)

# 执行流程
config = {"configurable": {"thread_id": "transfer-20240501"}}
# 首次执行:触发审批中断
initial = graph.invoke(
    {"action_details": "转账500元给用户A", "status": "pending"},
    config=config
)
print("中断请求:", initial["__interrupt__"])  # v1版本获取中断信息

# 恢复执行(批准)
resumed_approved = graph.invoke(Command(resume=True), config=config)
print("批准结果:", resumed_approved["status"])  # 输出:approved

# 重新执行(拒绝,用新的thread_id)
config_new = {"configurable": {"thread_id": "transfer-20240502"}}
initial_new = graph.invoke(
    {"action_details": "转账1000元给用户B", "status": "pending"},
    config=config_new
)
resumed_rejected = graph.invoke(Command(resume=False), config=config_new)
print("拒绝结果:", resumed_rejected["status"])  # 输出:rejected

场景2:多并行节点中断(处理并行工作流)

需求:图中两个并行节点同时触发中断,需要一次性恢复所有中断,分别传入对应输入。

多并行节点中断流程图:
暂停,等待输入
暂停,等待输入
一次性传入所有中断的恢复值
开始
并行节点A:触发中断1
并行节点B:触发中断2
收集所有中断请求
节点A、B同时恢复执行
合并结果,图结束

python 复制代码
from typing import Annotated, TypedDict
import operator
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, interrupt

# 定义状态(使用operator.add实现并行节点结果合并)
class State(TypedDict):
    vals: Annotated[list[str], operator.add]

# 并行节点A
def node_a(state):
    answer = interrupt("请输入节点A的答案:")
    return {"vals": [f"a:{answer}"]}

# 并行节点B
def node_b(state):
    answer = interrupt("请输入节点B的答案:")
    return {"vals": [f"b:{answer}"]}

# 构建并行图(START同时指向两个节点)
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": "parallel-1"}}

# 首次执行:两个节点同时触发中断
interrupted_result = graph.invoke({"vals": []}, config)
print("并行中断信息:", interrupted_result["__interrupt__"])
# 输出包含两个中断,各有唯一id

# 一次性恢复所有中断(用中断id映射对应输入)
resume_map = {
    i.id: f"答案:{i.value.split(':')[1]}"
    for i in interrupted_result["__interrupt__"]
}
final_result = graph.invoke(Command(resume=resume_map), config)
print("并行执行结果:", final_result["vals"])
# 输出:['a:答案:节点A的答案', 'b:答案:节点B的答案']

场景3:工具调用前中断(安全校验)

需求:在调用工具(如发送邮件、调用API)前,暂停并让人工审核工具参数,可修改参数后再执行。

工具调用前中断流程图:
暂停,等待人工审核
批准(可修改参数)
拒绝
开始
智能体节点:准备调用工具
工具函数:触发中断,展示参数
人工审核结果
执行工具调用,返回结果
取消工具调用,返回提示
图结束

python 复制代码
from langchain.tools import tool
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, interrupt
from typing import TypedDict

# 定义状态
class AgentState(TypedDict):
    messages: list[dict]

# 定义带中断的工具(发送邮件前需审批)
@tool
def send_email(to: str, subject: str, body: str):
    """发送邮件工具"""
    # 中断:展示邮件详情,请求审批(可修改参数)
    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 "邮件发送已取消"

# 智能体节点(调用工具)
def agent_node(state: AgentState):
    # 此处可结合LLM判断是否调用工具,简化示例直接调用
    tool_result = send_email(to="alice@example.com", subject="会议通知", body="明天10点开会")
    return {"messages": state["messages"] + [{"role": "assistant", "content": tool_result}]}

# 构建图
builder = StateGraph(AgentState)
builder.add_node("agent", agent_node)
builder.add_edge(START, "agent")
builder.add_edge("agent", END)

# 编译图
checkpointer = SqliteSaver("tool_interrupt.db")
graph = builder.compile(checkpointer=checkpointer)

# 执行流程
config = {"configurable": {"thread_id": "email-1001"}}
initial = graph.invoke(
    {"messages": [{"role": "user", "content": "发送会议通知邮件"}]},
    config=config
)
print("工具中断请求:", initial["__interrupt__"])

# 恢复执行(批准并修改主题)
resumed = graph.invoke(
    Command(resume={"action": "approve", "subject": "紧急会议通知"}),
    config=config
)
print("工具执行结果:", resumed["messages"][-1]["content"])

场景4:用户输入校验(循环中断)

需求:收集用户年龄,若输入无效(非数字、负数),则重复中断请求,直到输入有效为止。

用户输入校验(循环中断)流程图:
暂停,接收用户输入
无效(非数字/负数)
有效(正整数)
开始
收集年龄节点:触发中断,请求输入
输入是否有效?
更新提示信息,重新触发中断
返回年龄,节点执行完成
图结束

python 复制代码
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, interrupt
from typing import TypedDict

# 定义状态
class FormState(TypedDict):
    age: int | None  # 存储用户年龄

# 收集年龄节点(循环中断校验)
def get_age_node(state: FormState):
    prompt = "请输入你的年龄(正整数):"
    while True:
        # 触发中断,请求用户输入
        answer = interrupt(prompt)
        # 校验输入有效性
        if isinstance(answer, int) and answer > 0:
            break  # 输入有效,退出循环
        # 输入无效,重新提示
        prompt = f"'{answer}' 不是有效的年龄,请输入正整数:"
    return {"age": answer}

# 构建图
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("form.db")
graph = builder.compile(checkpointer=checkpointer)

# 执行流程
config = {"configurable": {"thread_id": "form-1001"}}
# 第一次中断:输入无效
first = graph.invoke({"age": None}, config=config)
print("第一次中断:", first["__interrupt__"])  # 提示:请输入你的年龄(正整数)

# 恢复执行:输入无效值(字符串)
retry = graph.invoke(Command(resume="三十"), config=config)
print("第二次中断:", retry["__interrupt__"])  # 提示:'三十' 不是有效的年龄...

# 恢复执行:输入有效值(整数)
final = graph.invoke(Command(resume=30), config=config)
print("最终结果:", final["age"])  # 输出:30

四、避坑指南:Interrupts使用注意事项

使用Interrupts时,有4个关键规则必须遵守,否则会出现状态错乱、中断失效等问题。

Interrupts四大避坑要点汇总:
原因:捕获中断异常,导致中断失效
原因:索引匹配错乱,状态异常
原因:Checkpointer无法序列化,报错
原因:重复执行副作用,数据异常
Interrupts避坑要点
避坑1:不包裹try/except
避坑2:不条件跳过interrupt
避坑3:传入值需JSON可序列化
避坑4:中断前副作用需幂等
正确:分离中断与异常处理
正确:保持中断顺序/数量固定
正确:使用字符串/字典等简单类型
正确:用幂等操作或后置副作用

  1. 不要用try/except包裹interrupt()调用
    interrupt()通过抛出特殊异常实现暂停,若用try/except(尤其是裸except)包裹,会捕获该异常,导致中断无法传递给LangGraph runtime,中断失效。
python 复制代码
# ❌ 错误:用裸except包裹interrupt()
def bad_node(state):
    try:
        interrupt("请输入信息")
    except Exception as e:
        print(e)
    return state

# ✅ 正确:分离interrupt和异常处理,或捕获特定异常
def good_node(state):
    interrupt("请输入信息")
    try:
        # 其他可能出错的逻辑(如API调用)
        fetch_data()
    except NetworkError as e:  # 捕获特定异常
        print(e)
    return state
  1. 不要在节点内条件性跳过interrupt()
    LangGraph恢复执行时,会重新运行整个节点,中断的匹配是"索引-based"的。若条件性跳过interrupt(),会导致恢复时输入值与中断不匹配,出现逻辑错乱。
python 复制代码
# ❌ 错误:条件性跳过interrupt()
def bad_node(state):
    name = interrupt("请输入姓名")
    if state.get("need_age"):  # 条件可能变化,导致中断数量不一致
        age = interrupt("请输入年龄")
    return {"name": name}

# ✅ 正确:保持interrupt()调用顺序和数量固定
def good_node(state):
    name = interrupt("请输入姓名")
    age = interrupt("请输入年龄(无需请填0)")  # 即使不需要,也不跳过
    return {"name": name, "age": age if age != 0 else None}
  1. interrupt()传入的值必须JSON可序列化
    Checkpointer需要序列化中断状态,因此interrupt()的参数不能是函数、类实例等复杂对象,建议使用字符串、字典、列表等简单类型。
python 复制代码
# ❌ 错误:传入不可序列化的对象
def bad_node(state):
    def validator(x):
        return x > 0
    # 函数不可序列化,会报错
    interrupt({"question": "请输入数字", "validator": validator})
    return state

# ✅ 正确:传入简单可序列化值
def good_node(state):
    interrupt({"question": "请输入数字", "rule": "必须是正整数"})
    return state
  1. 中断前的副作用需保证幂等性
    恢复执行时,节点会重新运行,因此中断前的操作(如数据库写入、API调用)必须是幂等的(多次执行结果一致),避免重复创建数据、重复调用API。
python 复制代码
# ❌ 错误:中断前执行非幂等操作(重复创建记录)
def bad_node(state):
    # 每次恢复都会创建新的审计记录,导致重复
    db.create_audit_log({"action": "pending"})
    interrupt("请审批")
    return state

# ✅ 正确:中断前执行幂等操作(或把副作用放在中断后)
def good_node(state):
    # 幂等操作:存在则更新,不存在则创建
    db.upsert_audit_log({"action": "pending", "id": state["id"]})
    approved = interrupt("请审批")
    # 副作用放在中断后,只执行一次
    if approved:
        db.create_audit_log({"action": "approved", "id": state["id"]})
    return state

五、调试技巧:静态中断(断点)

除了动态中断(interrupt()函数),LangGraph还支持静态中断(断点),用于调试图的执行流程,可在编译时或运行时指定节点前后暂停。

静态中断与动态中断对比图:
触发方式:代码中调用interrupt()
用途:生产环境,人机协同
触发方式:编译/运行时指定节点
用途:调试阶段,调试图流程
中断类型对比
动态中断interrupt
静态中断_断点
特点:动态、灵活,支持条件触发
示例:审批、输入校验
特点:固定、静态,无需修改代码
示例:节点执行前后暂停

python 复制代码
# 编译时设置静态中断(断点)
graph = builder.compile(
    interrupt_before=["node_a"],  # 执行node_a前暂停
    interrupt_after=["node_b"],   # 执行node_b后暂停
    checkpointer=checkpointer
)

# 运行时设置静态中断
config = {"configurable": {"thread_id": "debug-1"}}
# 执行到第一个断点暂停
graph.invoke(inputs, interrupt_before=["node_a"], config=config)
# 恢复执行,直到下一个断点
graph.invoke(None, config=config)

注意:静态中断仅用于调试,生产环境建议使用动态中断(interrupt()函数)实现人机协同。

六、总结

LangGraph Interrupts是实现"人机协同"工作流的核心能力,其核心价值在于:动态暂停图执行、持久化执行状态、灵活接收外部输入。通过本文的讲解,我们掌握了:

  • Interrupts的核心概念和基础使用流程;
  • 4个高频实战场景(审批、并行中断、工具校验、输入校验)的完整示例;
  • 4个关键避坑规则,避免状态错乱和中断失效;
  • 静态中断的调试技巧。
    LangGraph Interrupts 整体知识框架图:

动态性、状态持久化、灵活恢复
3步:配置→暂停→恢复
4大场景
4大规则
静态中断
LangGraph Interrupts
核心概念
基础流程
实战场景
避坑指南
调试技巧
依赖Checkpointer和thread_id
核心API:interrupt()、Command(resume=...)
审批、并行、工具校验、输入校验
不包裹try/except等
仅用于调试,生产用动态中断

实际项目中,Interrupts可广泛应用于智能体审批、工具安全校验、用户输入交互等场景,让LangGraph构建的工作流更灵活、更安全、更具可交互性。

如果觉得本文有用,欢迎点赞、收藏、关注,后续会分享更多LangGraph实战技巧!

相关推荐
好家伙VCC2 小时前
# 发散创新:用 Rust实现高性能物理引擎的底层架构设计与实战在游戏开发、虚拟仿真和机器人控
java·开发语言·python·rust·机器人
147API2 小时前
多模型时代,如何根据任务轻重合理分配模型资源?
人工智能·大模型api·api中转·ai架构
boonya2 小时前
一文读懂MCP:AI连接万物的“USB-C接口”
c语言·开发语言·人工智能
石榴树下的七彩鱼2 小时前
图片去水印 API 哪个好?5种方案实测对比(附避坑指南 + 免费在线体验)
图像处理·人工智能·后端·python·api接口·图片去水印·电商自动化
liliangcsdn2 小时前
多轮对话长上下文-向量检索和混合召回示例
开发语言·数据库·人工智能·python
MFXWW22 小时前
特斯拉 Optimus Gen3 手臂设计解析:从 “能抓“ 到 “会用“ 的工程革命
人工智能·机器人
user_admin_god2 小时前
OpenCode入门到入坑
java·人工智能·spring boot·语言模型
Agent产品评测局2 小时前
律所行业自动化平台选型,合同审核与案件管理优化 | 2026年法律科技Agent化演进与企业级智能体实测横评
运维·人工智能·科技·ai·chatgpt·自动化