人机协同
Agent 需要人在关键节点把关:转账、发邮件、改代码。LangGraph 的 interrupt 是官方机制:执行到任意位置暂停、等人、带结果恢复。
interrupt 机制
interrupt: 暂停 → 等待 → 恢复
from langgraph.types import Command, interrupt
def approval_node(state):
decision = interrupt({
"question": "批准这笔转账?",
"amount": state["amount"],
}) # 暂停点: 返回值来自外部 resume
if decision:
return Command(goto="transfer")
return Command(goto="cancel")interrupt(payload):暂停图执行,payload(JSON 可序列化)暴露给调用方- 恢复:用
Command(resume=值)重新调用图,该值成为 interrupt() 的返回值 - 动态:可在任意代码位置、按条件触发,不同于静态断点(固定节点前后)
审批流实现
config = {"configurable": {"thread_id": "tx-42"}}
# 第一次运行: 执行到 interrupt 暂停
result = graph.invoke({"amount": 10000}, config=config)
pending = result["__interrupt__"] # 拿到待审批信息
# > (Interrupt(value={'question': '批准这笔转账?', ...}),)
# 人工审批后恢复: 带 True/False
graph.invoke(Command(resume=True), config=config) # 批准, 走 transfer关键点:
- checkpoint 是前提:中断时状态已落盘(见持久化篇),恢复时从暂停点继续
- thread_id 是会话指针:审批系统保存 thread_id,用户确认后用它恢复
- interrupt 前别写副作用:恢复会从节点开头重跑,中断前的外部写入会重复执行
典型场景
| 场景 | 中断点 |
|---|---|
| 转账/支付 | 执行支付工具前 |
| 发邮件/消息 | 调用 send 工具前(工具内 interrupt) |
| 代码生成 | 写文件前人工确认 |
| 数据删除 | 破坏性操作前 |
工具内 interrupt 的写法(在 @tool 函数里调用 interrupt):
@tool
def send_email(to: str, subject: str, body: str):
"""发送邮件"""
decision = interrupt({"action": "send_email", "to": to,
"subject": subject, "body": body})
if decision.get("action") == "approve":
return "已发送"
return "已取消"与 LangChain 生态的关系
- DeepAgents 内置 HITL:批准/编辑/拒绝工具调用(approve/edit/reject),底层就是 interrupt
- LangSmith 提供人工审批界面(Agent 交互式界面),thread_id 对接
面试追问
- interrupt 怎么工作? 代码任意位置暂停,payload 暴露给外部;Command(resume) 恢复,resume 值成为 interrupt 的返回值
- 审批流怎么实现? 工具/节点里 interrupt → 保存 thread_id → 人工审批 → Command(resume=True/False) 恢复走不同分支
- 静态断点和 interrupt 的区别? 静态断点编译时固定位置;interrupt 动态、可在工具内、按条件触发
- 中断前要注意什么? 别写外部副作用:恢复会重跑节点开头,中断前的写入会重复(不是幂等的)
- interrupt 依赖什么? checkpoint:中断时状态落盘,恢复才能从暂停点继续