【LangGraph】五.人机协作:审批和中断

写在前面

前面的文章里,Agent 都是自动执行的。但有些场景需要人来把关:删除数据要确认、大额转账要审批、生成内容要审核。

这篇文章深入讲解 LangGraph 的人机协作机制。不仅讲怎么用,还要讲清楚背后的工作原理。


一、核心概念

1.1 为什么需要人机协作

Agent 自动执行有个前提:所有操作都是可预测、可逆的。但现实世界中:

  • 高风险操作不可逆:删除数据、转账、发布内容,一旦执行就无法挽回
  • 复杂决策需要人类判断:某些场景 AI 无法做出准确判断,需要人类经验
  • 责任归属需要人工确认:涉及法律责任的决策,必须由人来承担责任

人机协作的核心思想:让 Agent 在关键节点暂停,等人做决策,然后继续执行

1.2 interrupt 机制的本质

interrupt 不是简单的"暂停",而是一套完整的状态管理机制。它涉及三个关键问题:

问题 1:暂停时保存什么?

答案是:保存完整的 State 快照。包括:

  • 所有 State 字段的当前值
  • 执行进度(下一个要执行的节点)
  • 元数据(时间戳、执行历史等)

这就是为什么必须配置 Checkpointer------没有 Checkpointer,就无法保存状态。

问题 2:如何知道在哪里暂停?

答案是:在编译时指定中断点。LangGraph 会在执行到这些节点前(或后)自动检查。

问题 3:恢复时如何继续?

答案是:从 Checkpoint 中读取最后一个状态,然后执行下一个节点。

1.3 Checkpoint 的工作原理

理解 Checkpoint 是理解人机协作的关键。

Checkpoint 是什么?

Checkpoint 是 State 在某个时间点的完整快照。它包含:

python 复制代码
Checkpoint {
    values: State,           // State 的完整数据
    next: [node_name],       // 下一个要执行的节点
    metadata: {              // 元数据
        step: 1,
        created_at: "...",
        thread_id: "..."
    }
}

Checkpoint 何时创建?

每个节点执行完后,LangGraph 会自动创建一个 Checkpoint。

Checkpoint 如何组织?

Checkpoint 按 Thread 组织。每个 Thread 有自己的 Checkpoint 链:

python 复制代码
Thread: user-123
  Checkpoint 1 (step=1, after=node_a)
  Checkpoint 2 (step=2, after=node_b)
  Checkpoint 3 (step=3, after=node_c)

这就是为什么每个会话需要唯一的 thread_id------不同会话的 Checkpoint 必须隔离。


二、interrupt_before 详解

2.1 工作原理

interrupt_before=["node_name"] 的含义是:在执行 node_name 节点之前暂停。

执行流程:

复制代码
1. 执行到 node_name 的前一个节点
2. 创建 Checkpoint(包含当前 State)
3. 检查:下一个节点是 node_name 吗?
4. 如果是,暂停执行
5. 等待外部输入
6. 更新 State(可选)
7. 恢复执行:从 Checkpoint 读取 State,执行 node_name

关键点: 暂停时,node_name 还没有执行。State 中不包含 node_name 的执行结果。

2.2 适用场景

interrupt_before 适合审批、确认场景------在执行某个操作前,等人确认。

典型例子:

  • 执行删除操作前,等人确认
  • 执行转账前,等人审批
  • 执行发布前,等人审核

2.3 完整示例:退款审批

python 复制代码
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver


class State(TypedDict):
    request: str           # 退款申请
    approved: bool | None  # 审批结果(初始为 None)
    result: str            # 最终结果


def analyze_node(state: State) -> dict:
    return {"request": state["request"]}


def approval_node(state: State) -> dict:
    if state["approved"] is True:
        return {"result": "退款已批准"}
    elif state["approved"] is False:
        return {"result": "退款申请已拒绝"}
    else:
        raise ValueError("未经审批,无法执行退款")


# 构建图
graph = StateGraph(State)
graph.add_node("analyze", analyze_node)
graph.add_node("approval", approval_node)
graph.set_entry_point("analyze")
graph.add_edge("analyze", "approval")
graph.add_edge("approval", END)

# 编译(关键:设置中断点)
checkpointer = MemorySaver()
app = graph.compile(
    checkpointer=checkpointer,
    interrupt_before=["approval"]
)

State 设计要点:

  • approved: bool | None:初始值为 None,等人设置为 True 或 False
  • approval_node 会检查 approved 字段,如果为 None 就报错

使用流程:

python 复制代码
config = {"configurable": {"thread_id": "refund-001"}}

# 第 1 步:提交申请
result = app.invoke(
    {"request": "用户申请退款,原因:商品质量问题"},
    config=config
)

# 此时执行已暂停,Checkpoint 保存了 State
# next: ["approval"]

# 第 2 步:检查状态
state = app.get_state(config)
print(state.next)  # ['approval']

# 第 3 步:人工审批
approved = input("批准退款?(yes/no): ")

# 第 4 步:更新 State
app.update_state(
    config,
    values={"approved": approved == "yes"}
)

# 第 5 步:恢复执行
result = app.invoke(None, config=config)
print(result)

2.4 为什么恢复时要传 None

这是一个关键问题。

传 input 会发生什么?

python 复制代码
# ❌ 错误
result = app.invoke({"request": "新的申请"}, config=config)

这会重新开始一次执行,而不是恢复。

传 None 会发生什么?

python 复制代码
# ✅ 正确
result = app.invoke(None, config=config)

这会恢复执行:读取最后一个 Checkpoint,从断点继续。

关键区别:

  • 传 input:创建新 State,从头执行
  • 传 None:读取 Checkpoint,从断点继续

三、interrupt_after 详解

3.1 工作原理

interrupt_after=["node_name"] 的含义是:在执行 node_name 节点之后暂停。

执行流程:

复制代码
1. 执行 node_name 节点
2. 更新 State(包含 node_name 的执行结果)
3. 创建 Checkpoint
4. 检查:刚执行的节点是 node_name 吗?
5. 如果是,暂停执行
6. 等待外部输入
7. 更新 State(可选)
8. 恢复执行:从 Checkpoint 读取 State,执行下一个节点

关键点: 暂停时,node_name 已经执行完了。State 中包含 node_name 的执行结果。

3.2 与 interrupt_before 的区别

|----------|------------------|-----------------|
| 特性 | interrupt_before | interrupt_after |
| 暂停时机 | 节点执行 | 节点执行 |
| State 包含 | 不包含该节点结果 | 包含该节点结果 |
| 适用场景 | 审批、确认 | 审核、修改 |

例子对比:

python 复制代码
# interrupt_before: 执行前暂停(审批)
app = graph.compile(
    checkpointer=checkpointer,
    interrupt_before=["delete"]
)

# interrupt_after: 执行后暂停(审核)
app = graph.compile(
    checkpointer=checkpointer,
    interrupt_after=["generate"]
)

3.3 完整示例:内容审核

python 复制代码
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver


class State(TypedDict):
    topic: str
    content: str | None
    reviewed: bool | None
    published: bool | None


def generate_content(state: State) -> dict:
    prompt = f"为以下主题生成营销文案:{state['topic']}"
    content = llm.generate(prompt)
    return {"content": content}


def publish_content(state: State) -> dict:
    if state["published"]:
        return {"result": "内容已发布"}
    else:
        return {"result": "内容未发布"}


# 构建图
graph = StateGraph(State)
graph.add_node("generate", generate_content)
graph.add_node("publish", publish_content)
graph.set_entry_point("generate")
graph.add_edge("generate", "publish")
graph.add_edge("publish", END)

# 编译:生成后暂停,等人审核
checkpointer = MemorySaver()
app = graph.compile(
    checkpointer=checkpointer,
    interrupt_after=["generate"]
)

使用流程:

python 复制代码
config = {"configurable": {"thread_id": "content-001"}}

# 第 1 步:生成内容
result = app.invoke({"topic": "夏季促销"}, config=config)

# 此时已暂停,State 包含生成的内容
# next: ["publish"]

# 第 2 步:查看生成的内容
state = app.get_state(config)
print(state.values["content"])

# 第 3 步:人工审核
approved = input("内容是否合适?(yes/no): ")

# 第 4 步:更新 State
app.update_state(
    config,
    values={
        "reviewed": True,
        "published": approved == "yes"
    }
)

# 第 5 步:恢复执行
result = app.invoke(None, config=config)

四、状态管理深入

4.1 State 的生命周期

理解 State 的生命周期对正确使用人机协作至关重要。

完整生命周期:

复制代码
1. 创建 State(invoke 时传入 input)
2. 执行节点 A → 更新 State
3. 创建 Checkpoint 1(保存 State 快照)
4. 执行节点 B → 更新 State
5. 创建 Checkpoint 2(保存 State 快照)
6. ...
7. 执行完成或中断

关键点:

  • State 是可变的,每个节点都可以更新它
  • Checkpoint 是 State 的快照,记录了某个时间点的 State
  • 恢复执行时,从 Checkpoint 读取 State,然后继续更新

4.2 update_state 的工作原理

update_state 是人机协作中的关键操作。它的作用是在暂停时更新 State。

工作流程:

python 复制代码
app.update_state(config, values={"approved": True})
  1. 读取最后一个 Checkpoint
  2. 获取 Checkpoint 中的 State
  3. values 更新 State 的指定字段
  4. 创建新的 Checkpoint(包含更新后的 State)
  5. 保持 next 字段不变(指向下一个要执行的节点)

重要细节:

  • update_state 不会改变执行进度(next 字段)
  • update_state 创建新的 Checkpoint
  • 多次 update_state 会创建多个 Checkpoint

五、高级用法

5.1 多中断点

可以在多个节点设置中断点。

python 复制代码
app = graph.compile(
    checkpointer=checkpointer,
    interrupt_before=["step2", "step3", "final"]
)

执行流程:

复制代码
step1 → 暂停 → 恢复 →
step2 → 暂停 → 恢复 →
step3 → 暂停 → 恢复 →
final → 结束

适用场景: 多步确认,每步都需要人审核。

5.2 分支中断

在不同分支设置不同的中断点。

python 复制代码
# 高风险操作需要审批,低风险操作自动执行
graph.add_conditional_edges(
    "check_risk",
    route_by_risk,
    {
        "high": "approval",    # 高风险 → 审批
        "low": "execute",      # 低风险 → 直接执行
    }
)

app = graph.compile(
    checkpointer=checkpointer,
    interrupt_before=["approval"]
)

六、常见错误

错误 1:忘记设置 Checkpointer

python 复制代码
# ❌ 错误
app = graph.compile(interrupt_before=["approval"])
# 没有 Checkpointer,无法保存状态

# ✅ 正确
checkpointer = MemorySaver()
app = graph.compile(
    checkpointer=checkpointer,
    interrupt_before=["approval"]
)

错误 2:恢复时传了 input

python 复制代码
# ❌ 错误
result = app.invoke({"new_data": "..."}, config=config)
# 这会重新开始

# ✅ 正确
result = app.invoke(None, config=config)
# None 表示恢复执行

错误 3:不更新 State 就恢复

python 复制代码
# ❌ 错误
app.invoke(None, config=config)
# approved 还是 None

# ✅ 正确
app.update_state(config, values={"approved": True})
app.invoke(None, config=config)

错误 4:中断点设置过多

python 复制代码
# ❌ 错误:每个节点都中断
app = graph.compile(
    interrupt_before=["node1", "node2", "node3", "node4", "node5"]
)

# ✅ 正确:只在关键节点中断
app = graph.compile(
    interrupt_before=["sensitive_operation", "high_risk_action"]
)

小结

核心概念:

  • interrupt:在特定节点暂停,等人决策
  • Checkpoint:State 的快照,记录执行进度
  • Thread:会话隔离单位,每个 Thread 有独立的 Checkpoint 链

两种中断方式:

  • interrupt_before:节点执行前暂停
    • State 不包含该节点结果
    • 适用:审批、确认
  • interrupt_after:节点执行后暂停
    • State 包含该节点结果
    • 适用:审核、修改

使用流程:

  1. 配置 Checkpointer 和中断点
  2. 执行到中断点暂停
  3. 检查状态(app.get_state(config)
  4. 更新 State(app.update_state(config, values=...)
  5. 恢复执行(app.invoke(None, config=config)

关键要点:

  • 必须配置 Checkpointer
  • 恢复时传 None,不要传 input
  • State 设计要清晰,字段职责明确
  • 只在关键节点设置中断点
相关推荐
AI进化营-智能译站6 小时前
ROS2 C++开发系列01:在ROS2上编写第一个C++ hello word
开发语言·c++·ai·word
dFObBIMmai6 小时前
golang如何实现数据导入进度跟踪_golang数据导入进度跟踪实现教程
jvm·数据库·python
步辞6 小时前
golang如何实现即时通讯IM系统_golang即时通讯IM系统实现方案
jvm·数据库·python
我才是一卓6 小时前
2026 Python 入门教程,结合 vscode 和 miniforge/miniconda
开发语言·vscode·python
m0_602857766 小时前
CSS如何实现图片悬停时的缩放裁剪效果_利用transform与overflow
jvm·数据库·python
其实防守也摸鱼6 小时前
CTF密码学综合教学指南--第二章
开发语言·网络·python·安全·网络安全·密码学·ctf
七颗糖很甜7 小时前
基于IRI-2016模型计算电子密度、TEC、foF2等参数的技术原理与代码实现
大数据·python·算法
FIT2CLOUD飞致云7 小时前
集成MiniMax,移动端适配,SQLBot开源智能问数系统v1.8.0版本发布
ai·数据分析·开源·智能问数·sqlbot
echome8887 小时前
Python 生成器与 yield 关键字实战:5 个节省内存的高级用法与性能优化技巧
开发语言·python