Tool Approval、Interrupt 与 Resume
做 Agent 系统时,审批流很容易停在概念层:
- 哪些动作要审批
- 哪些动作可以自动执行
- 哪些步骤需要人工接手
真正开始实现之后,问题会立刻变具体:
- 工具调用是在什么地方被拦住的
- 系统暂停之后,状态存在哪里
- 审批结果怎样回到原来的执行链里
- reject、edit、timeout 之后流程怎么继续
这里专门讨论这一层。
参考资料主要来自:
1. 先把 3 个词分开
1.1 Tool Approval
Tool approval 讨论的是:
某个工具调用在真正执行前,是否要先经过人工确认。
它关注的是工具级策略。
1.2 Interrupt
interrupt 讨论的是:
系统在执行过程中,能不能安全地停下来,把控制权暂时交出去。
它关注的是暂停点和状态保存。
1.3 Resume
resume 讨论的是:
暂停之后,系统怎样从原来的位置继续往下跑。
它关注的是恢复执行。
把这三件事连起来看,会更容易理解实现链路:
- 先决定某个工具是否需要 approval
- 如果需要,就触发 interrupt
- 人工决定之后,再用 resume 继续执行
2. 最小执行链路
一个最小实现通常会长这样:
这里先看 4 个实现要点:
- 暂停前状态已经保存
- 审批数据结构是明确的
- resume 时知道恢复到哪个执行点
- reject 不是报错,而是流程分支
3. Tool approval 放在哪一层
常见有 3 种做法。
3.1 放在 tool policy 层
也就是先定义:
- 哪些工具需要审批
- 哪些参数组合需要审批
- 哪些风险级别需要审批
这种做法最适合:
- 工具边界清楚
- 风险主要跟工具种类有关
- 主流程本身不复杂
例如 :
send_emaildelete_filerun_sqlcreate_payment
这类工具通常适合先挂 tool policy。
3.2 放在 workflow 节点层
也就是把审批看成流程中的一个显式节点。
这种做法更适合:
- 审批本身就是业务流程的一部分
- 审批可能跨时间、跨人、跨角色
- 需要 checkpoint / resume
- 需要审计和追踪
3.3 放在 guardrail / middleware 层
也就是让工具调用先经过一层统一检查,再决定是否打断。
这种做法更适合:
- 工具很多
- 想集中做风险控制
- 希望把策略和业务节点分开
4. interrupt 到底做了什么
从实现角度看,interrupt 不是“弹个框”,而是 4 件事:
- 停止当前执行链
- 暴露一个可供人审的 payload
- 保存当前状态
- 等待外 部输入后继续
在 LangGraph 里,这个模式很明确:
- 调用
interrupt(...) - graph 停在这里
- checkpointer 保存状态
- 后面通过
Command({ resume: ... })恢复
这一点需要单独记住。
如果没有可靠的状态保存,所谓 interrupt 往往只是:
- 前端拦一下
- 后端重新跑一遍
- 或者重新生成一次 tool call
这种实现很容易出问题,尤其在下面几类场景里:
- 有副作用的工具
- 长链路任务
- 多步推理后才出现的写操作
5. resume 不是“重新开始”
resume 的重点不是重跑,而是:
从暂停点继续。
这意味着实现上至少要满足两件事:
5.1 能定位到原来的执行上下文
也就是要知道:
- 当前是哪条线程 / run
- 中断发生在哪个节点
- 当前 state 是什么
- 当前还挂着哪些未处理 interrupt
5.2 能把审批结果送回暂停点
例如:
approverejectedit后的新参数- 额外人工说明
如果 resume 只是“重新把用户问题发一遍”,那就不算真正意义上的 resume。
6. approve / edit / reject 的实现分支
6.1 approve
最简单。
- 原参数执行
- trace 记一条 approval 记录
- 后续流程继续
6.2 edit
这一支经常被低估。
实际业务里很常见:
- 邮件内容要改
- SQL 条件要缩小
- 金额要修正
- 收件人要替换
所以 interrupt payload 最好不要只支持布尔值,还要支持:
- 参数修改
- 批注
- 人工补充字段
6.3 reject
reject 之后一般有 3 种处理:
- 直接结束
- 回到规划步骤
- 转人工完全接管
如果 reject 后只是抛异常,链路通常不够完整。
7. 一个最小 payload 该包含什么
审批 payload 至少应该有这些字段:
{
toolName: string,
reason: string,
args: Record<string, unknown>,
riskLevel: "low" | "medium" | "high",
reversible: boolean,
traceId: string,
runId?: string,
suggestedAction?: "approve" | "edit" | "reject"
}
如果还要更实用,可以再加:
- 目标对象
- 影响范围
- 参数 diff
- 上一步来源
- 是否命中某条策略
8. 超时、断线和重复提交怎么处理
这一层如果不处理,审批流上线后会很不稳。
8.1 超时
常见策略:
- 超时自动 reject
- 超时转人工队列
- 超时后把 run 标成 pending review
8.2 断线
关键是中断状态要落盘。
只要 state 和 interrupt id 还在,前端掉线后也能重新接上。
8.3 重复提交
审批动作要尽量幂等。
例如:
- 同一个 interrupt 只能被最终处理一次
- 第二次 approve 不应再次执行 tool
- edit 后再 approve,要能区分版本
9. 多 interrupt 怎么处理
当系统开始并行执行时,会遇到一个更实际的问题:
多个分支同时停下来怎么办?
LangGraph 官方也专门提到过这一点:
- 并行分支可能同时触发多个 interrupt
- resume 时需要按 interrupt id 对应各自的 resume value
这一层最好尽早定清:
- 是串行处理
- 还是支持批量审批
- 还是由不同审批人分别处理
10. 哪些动作最适合先做 interrupt
第一批通常先选这类:
send_emailrun_sqldelete_*write_*publish_*payment_*browser / computer use中带外部副作用的动作
理由很简单:
- 副作用明确
- 风险清楚
- 容易定义 policy
- 最容易做出第一版闭环
11. 和现有专题里的关系
这篇文档最适合和下面几篇一起看:
- Guardrails and Human-in-the-Loop
- Approval 与 Human Review Workflow
- Browser and Computer Use Agents
- Agent Observability and Debugging
- LangGraph 入门与编排设计
- OpenAI Agents SDK 指南
12. 小结
这条实现链路可以压缩成 3 步:
tool approval决定哪些动作要停interrupt负责把链路安全停下resume负责把链路从原位置接起来
如果这三层分清了,审批流就不再只是一个概念,而会开始变成可实现、可调试、可审计的系统能力。