写在前面
前面的文章里,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 或 Falseapproval_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})
- 读取最后一个 Checkpoint
- 获取 Checkpoint 中的 State
- 用
values更新 State 的指定字段 - 创建新的 Checkpoint(包含更新后的 State)
- 保持
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 包含该节点结果
- 适用:审核、修改
使用流程:
- 配置 Checkpointer 和中断点
- 执行到中断点暂停
- 检查状态(
app.get_state(config)) - 更新 State(
app.update_state(config, values=...)) - 恢复执行(
app.invoke(None, config=config))
关键要点:
- 必须配置 Checkpointer
- 恢复时传 None,不要传 input
- State 设计要清晰,字段职责明确
- 只在关键节点设置中断点