Human-in-the-loop Agent 设计:哪些步骤必须人工审批

"OpenAI Agents SDK Human-in-the-loop 文档说明,工具可以声明需要 approval,运行结果会通过 interruptions 暂停,并可用 RunState approve/reject 后恢复。"
飞书消息草稿已经生成好了:标题、正文、附件链接都填完,只差一步——点发送。但你的 Agent 在 send_message 前停了下来,等你确认。
邮件内容可以自动生成,发给客户前必须展示收件人、主题、正文摘要。CMS 表单填完了,提交时被策略拦下,等待 owner approve。这些场景看起来是 UI 层面的”点确认按钮”,但背后是运行状态暂停。Agent 的 RunState 被保存下来,等你做决策后恢复执行。审批要在哪一步停下来、谁来批、拒绝或超时怎么处理,这些不是前端弹窗的事,是工具系统的安全边界。
这篇文章给出一套可执行的风险分级矩阵、审批点清单、暂停恢复机制和审计字段模板,帮你把审批从”加个确认按钮”改写为”可序列化、可恢复、可审计的系统状态”。
风险分级矩阵:决定哪些动作要审批
不是所有动作都要审批。只读操作可以自动执行,删除和付款必须停下来等人。怎么判断?用这五个维度:
| 维度 | 分级标准 | 示例动作 |
|---|---|---|
| 外部影响 | 涉及外部系统/用户 | 发邮件、提交表单、调用外部 API、写协作系统(飞书/Slack) |
| 可逆性 | 动作是否可撤销 | 删除记录(不可逆)、写草稿(可逆)、付款(部分可逆需补偿)、发送消息(不可逆) |
| 数据敏感度 | 涉及的数据级别 | 查询公开数据(公开)、修改内部记录(内部)、导出用户隐私(敏感)、读取生产配置(敏感) |
| 金额/权限阈值 | 涉及资金或权限变更 | 付款、转账、退款、改权限、批量操作、删除用户数据 |
| 自动程度 | 允许的自动级别 | 只读查询(全自动)、写草稿(全自动)、外发消息(需确认)、删除/付款(需审批) |
这套矩阵来自 OWASP LLM06 的”过度功能、过度权限、过度自主”风险分类(发布前需核验 OWASP 编号)。你可以直接套用,或者根据业务场景调整阈值。
具体来说:
外部影响:只要涉及外部系统或用户,就要警惕。邮件发出去就收不回,表单提交可能触发订单,外部 API 调用可能改别人家的数据。
可逆性:删除记录不可逆,写草稿可随时改,付款部分可逆但需要退款流程补偿。
数据敏感度:公开数据可以自由读,内部数据要限制写,敏感数据(用户隐私、生产配置)必须审批。
金额/权限阈值:任何涉及资金或权限变更的动作,都该停下来。付款、转账、退款、改权限、批量操作都是高风险点。
自动程度:读操作可以全自动,写草稿也可以自动(毕竟只是草稿),外发需要确认,删除和付款必须审批。
矩阵不是一次性配置,要根据业务场景调整。比如你的业务场景中”发送消息”风险低(内部通知),可以降到”需确认”;但”付款”风险高(涉及真实资金),必须”需审批+双人复核”。
三个真实场景的分级案例
案例1:飞书消息草稿
Agent 写飞书消息草稿:自动执行(L0)。草稿只是保存在飞书草稿箱,没有发送出去,可逆、无外部影响、不涉及敏感数据。但当 Agent 调用 send_message 发送给客户:需审批(L2)。消息一旦发送就不可逆,外部用户会收到内容,可能包含敏感信息或误导性内容。MCP tools/call 前确认就是这个场景。
案例2:邮件发送
Agent 生成邮件内容:自动执行(L0)。内容只是字符串,没有发送出去,可随时修改。但当 Agent 调用邮件 API 发送给客户:需审批展示收件人/主题/附件(L2)。审批 UI 必须展示摘要和证据,不能只显示”确认发送”按钮。
案例3:CMS 表单提交
Agent 填写表单:自动执行(L0)。表单只是填写,没有提交,数据还在本地。但当 Agent 调用 CMS API 提交表单:策略拦截需 owner approve(L2)。策略可以是 guardrail 自动拦截(比如”金额超阈值”),也可以是 policy 静态规则(比如”所有 CMS 提交都要审批”)。
案例4:生产数据库删除
Agent 查询生产数据库:自动执行(L0)。查询只是读操作,无外部影响、可逆、不修改数据。但当 Agent 调用删除 API 删除生产库:强制人工审批+backup 审计(L3)。删除生产数据库不可逆、涉及敏感数据、影响外部用户,必须 Policy 强制规则限制,不能依赖 Guardrail 自动拦截。
这四个案例说明:同一个任务的不同步骤,风险级别不同。写草稿可以自动,发送必须审批,生产库删除必须双人复核。风险分级不是一刀切,要拆到具体动作。
审批点定义清单:具体到动作类型
有了风险矩阵,现在定义审批等级。这套清单覆盖常见动作类型:
L0 自动执行:读操作(查询数据库、检索向量库、读取配置)、写草稿(保存草稿、生成预览)。这些动作没有外部影响、可逆、不涉及敏感数据,可以自动执行。
L1 需确认:外发消息/数据(发送邮件、提交表单、调用外部 API)、批量读操作(导出数据、批量查询)。这些动作有外部影响,但风险相对可控,需要确认但不需要严格审批。
L2 需审批:删除/改权限(删除记录、修改权限、批量删除)、写协作系统(飞书消息、Slack 消息、CRM 记录)。这些动作不可逆或有较高外部影响,必须停下来审批。
L3 强制审批+双人复核:付款/转账(支付订单、退款)、敏感数据操作(导出用户隐私、修改生产配置、删除生产库)。这些动作涉及资金或敏感数据,必须双人复核。
这套清单来自 OpenAI Agents SDK 的 needs_approval 和 MCP Tools 的安全建议(发布前需复核 API 字段名)。MCP spec 要求”工具调用应让用户能拒绝,敏感操作确认”,对应的就是 L2 和 L3 级别。
你可以按业务场景调整。比如:
如果你的业务场景中”发送飞书消息”风险低(内部通知),可以降到 L1 需确认。
如果”删除记录”风险高(用户数据),必须保持 L2 需审批。
如果”付款”风险极高(大额资金),可以升级到 L3 双人复核+审批理由必填。
清单不是静态的,要根据业务变化调整。比如某天业务规则变了,“发送消息”不再需要审批,可以直接从清单中移除。
审批流程状态机:如何暂停、保存状态、恢复执行
审批不是 UI 弹窗,是运行状态暂停。当工具调用需要审批时,Agent 的 RunState 被保存,等你做决策后恢复。
状态流转图
审批状态机包含以下流转:
request -> pending -> approved/rejected/timeout -> resume/abort/compensate
具体步骤:
request:工具调用触发审批请求,RunState 包含工具名、参数、上下文。
pending:等待人工决策,状态保存在 checkpoint 中,关联 thread_id。
approved:审批通过,从 checkpoint 恢复执行,调用工具。
rejected:审批拒绝,进入 abort 或 convert to draft。
timeout:审批超时,进入 escalate 或 auto-reject。
resume/abort/compensate:恢复执行、中断任务、补偿已执行步骤。
恢复方式
三种恢复方式:
approve:继续执行,调用工具,完成后继续下一步。
reject:中断或改草稿,不调用工具。
edit:修改参数后继续,比如修改邮件收件人或内容后重新审批。
checkpoint 和 thread state 是状态保存的技术背景。具体实现见站内已发布的 LangGraph checkpoint/thread state 文章。checkpoint 用于暂停时保存状态,thread state 用于恢复时回到执行点。
OpenAI Agents SDK HITL 代码示例
以下代码演示 OpenAI Agents SDK 的审批机制(API 易变,发布前需核对官方文档):
from agents import Agent, Runner, function_tool
@function_tool(needs_approval=True)
def send_email(to: str, subject: str, body: str) -> str:
return send_email_handler(to=to, subject=subject, body=body)
agent = Agent(
name="EmailAgent",
tools=[send_email],
instructions="写邮件草稿,发送前等待审批",
)
result = Runner.run_sync(agent, "给客户写封退款通知邮件")
if result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
print(f"待审批工具: {interruption.tool_name}")
print(f"参数: {interruption.arguments}")
decision = show_approval_ui(interruption)
if decision == "approve":
state.approve(interruption)
elif decision == "reject":
state.reject(interruption)
result = Runner.run_sync(agent, state)
核心点:
needs_approval=True 标记工具需要审批。
interruptions 包含待审批的工具调用列表。
result.to_state() 把暂停结果转换为可序列化的 RunState。
state.approve() 或 state.reject() 做决策。
Runner.run_sync(agent, state) 从暂停点继续。
注意:API 字段名可能在 2026-07 后变化,需核对官方文档。
LangGraph interrupt/resume 代码示例
以下代码演示 LangGraph 的 interrupt 和 Command resume:
from langgraph.graph import StateGraph, MessagesState
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command, interrupt
def send_email_node(state: MessagesState):
approved = interrupt({
"action": "send_email",
"summary": state["email_summary"],
})
if approved != "approved":
return {"messages": ["邮件发送被拒绝,已保存为草稿"]}
email_result = send_email(state["email_params"])
return {"messages": [email_result]}
graph = StateGraph(MessagesState)
graph.add_node("send_email", send_email_node)
graph.add_edge("draft_email", "send_email")
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
thread_id = "thread_123"
config = {"configurable": {"thread_id": thread_id}}
result = app.invoke(
{"messages": ["给客户写退款通知"]},
config=config,
)
# graph 暂停后,interrupt payload 会返回给调用方;展示审批 UI,等待人工决策。
decision = show_approval_ui(result["__interrupt__"])
if decision == "approve":
app.invoke(Command(resume="approved"), config=config)
elif decision == "reject":
app.invoke(Command(resume="rejected"), config=config)
elif decision == "edit":
app.update_state(config, {"email_params": {"to": "[email protected]"}})
app.invoke(Command(resume="approved"), config=config)
核心点:
interrupt() 暂停 graph。
Command(resume=...) 恢复执行。
checkpoint + thread_id 保证状态一致性。
支持 approve/reject/edit 三种恢复方式。
注意:API 可能变化,需核对 LangGraph 官方文档。
审批证据字段清单:保存什么信息,如何追溯
审批不是只做决策,还要记录决策。这套字段是审计日志的最小集:
| 字段 | 说明 | 示例 |
|---|---|---|
| tool_name | 工具名 + 操作类型 | send_email / delete_record |
| tool_arguments | 完整参数 JSON | {“to”: “[email protected]”, “subject”: “退款通知”} |
| invoker_id | 调用者身份(用户或系统) | [email protected] / agent_run_abc123 |
| request_time | 审批请求时间 | 2026-06-23T09:26:10Z |
| approver_id | 审批人身份 | [email protected] |
| decision_time | 审批决策时间 | 2026-06-23T09:35:12Z |
| decision | 决策结果 | approved / rejected / timeout_auto_reject |
| evidence | 审批证据(截图/摘要) | “收件人正确,内容无敏感信息” |
| audit_trail_id | 可关联到运行日志 | run_abc123_step_5_tool_3 |
这套字段来自 MCP Tools 的审计建议和 OpenAI API 的 mcp_approval_request item(发布前需核验字段名)。审批日志是可观测性的一部分,具体实践见站内已发布的 Agent monitoring/recovery 文章。
RunState 如何序列化?OpenAI Agents SDK 可通过 result.to_state() 把暂停结果转换为 RunState,LangGraph 使用 checkpoint + thread_id。序列化后保存到数据库或日志系统,关联 audit_trail_id。
审计日志有几个关键用途:
事故追溯:某天发现数据泄露,能查到是谁在什么时候审批了哪个操作。
合规审计:企业环境需要证明”高风险操作有人审批”,日志是证据。
策略改进:统计哪些动作审批频率高、哪些总是被拒绝,调整审批策略。
字段不是死的,可以扩展。比如你的业务需要记录”审批时长”、“审批渠道”(邮件/Slack/飞书)、“是否双人复核”,都可以加进去。但最小集必须包含上述九个字段。
拒绝和超时:审批失败后怎么办
审批不是总是通过。拒绝和超时需要处理,不能让任务卡住。
拒绝后的三种路径
路径1:continue with fallback。用降级动作继续。比如发送邮件被拒绝,改成保存草稿,继续后续步骤。适合可降级的动作。
路径2:convert to draft。把动作改成草稿状态。比如 CMS 表单提交被拒绝,改成草稿保存,等待人工修改后重新提交。适合需要人工介入的场景。
路径3:abort task。中断整个任务。比如删除生产数据库被拒绝,任务必须停止,不能继续。适合不可逆高风险动作。
选择路径要看动作类型:
可逆动作:fallback 或 convert to draft。
不可逆高风险动作:abort task。
需要人工介入的场景:escalate。
超时后的两种路径
路径1:escalate to backup approver。超时后转给备用审批人。比如主审批人 30 分钟未响应,转给 on-call engineer。适合需要人工决策的场景。
路径2:auto-reject。超时后自动拒绝。比如审批超时 1 小时,自动拒绝并中断任务。适合风险较低但时间敏感的场景。
选择路径要看业务场景:
高风险动作:escalate,不能 auto-reject。
时间敏感动作:auto-reject,避免任务卡住。
一般场景:escalate,给审批人更多时间。
如何回滚已执行步骤
如果任务在审批拒绝后中断,可能需要回滚已执行步骤。比如 Agent 已经创建了订单,但付款审批被拒绝,订单需要取消。
回滚策略:
checkpoint 回滚:从 checkpoint 恢复到审批前的状态,丢弃已执行步骤。
补偿事务:调用补偿 API,比如取消订单、撤销邮件发送。
人工介入:通知人工处理,比如手动取消订单。
回滚不是总能成功。比如邮件已经发送出去,无法撤销。这种情况下只能记录审计日志,事后处理。
四层安全边界:Policy、Guardrail、Approval、Audit 如何组合
审批不是孤立的安全机制。Policy、Guardrail、Approval、Audit 四层需要组合使用,互不替代。
四层职责对照表
| 层级 | 职责 | 示例 |
|---|---|---|
| Policy | 静态规则,限制工具范围 | ”禁止删除生产库”、“付款工具只能调用沙箱环境” |
| Guardrail | 自动检查,拦截异常输入/输出 | 输入验证(参数格式校验)、输出清理(敏感信息过滤)、金额阈值检查 |
| Approval | 人工决策,高风险动作确认 | 发送邮件前展示收件人/内容、删除记录前确认、付款审批 |
| Audit | 事后追溯,记录决策和执行 | 审批日志、工具调用日志、状态变更日志 |
四层互不替代:
Policy 不能替代 Guardrail:Policy 是静态规则,无法动态检查输入输出。
Guardrail 不能替代 Approval:Guardrail 是自动检查,无法处理需要人工判断的场景。
Approval 不能替代 Audit:Approval 是决策,Audit 是追溯,两者必须都存在。
Audit 不能替代前三层:Audit 是事后追溯,无法阻止风险发生。
组合使用示例:
付款场景:Policy 限制金额上限、Guardrail 检查参数格式、Approval 双人复核、Audit 记录审批日志。
发邮件场景:Policy 限制收件人范围、Guardrail 检查内容敏感度、Approval 展示摘要确认、Audit 记录发送日志。
MCP 工具安全边界
MCP(Model Context Protocol)工具调用有自己的安全边界(spec 版本 2025-06-18,发布前需核验):
tools/list 展示所有可用工具:用户可以看到 Agent 能调用哪些工具,避免隐藏风险。
tools/call 前确认:敏感操作需要用户确认,对应 Approval 层。
inputSchema 校验:参数格式校验,对应 Guardrail 层。
timeout:工具调用超时限制,避免卡住。
audit logging:工具调用日志,对应 Audit 层。
关键提醒:MCP approval 不能替代 OAuth scope 和 server-side authorization。MCP approval 是工具调用前的确认,OAuth scope 是 API 访问权限,server-side authorization 是业务逻辑权限检查。三者必须都存在。
比如飞书 MCP server OAuth 通过了,scope 包含 send_message,但这不代表每条消息都安全。MCP approval 需要在发送前确认内容,server-side authorization 需要检查收件人是否在允许范围内。
外发消息/写协作系统/批量改表的审批场景参考见计划中的飞书 MCP 调研文章。
审批 UI 设计要点
审批 UI 不是简单的”确认/拒绝”按钮,要展示足够信息让人做决策。
设计原则:
展示工具名和参数:让审批人知道 Agent 要调用什么工具、用什么参数。
展示预期影响:比如”发送邮件给 [email protected],主题是退款通知”。
展示可逆性:比如”发送后不可撤销”或”删除后可恢复”。
展示数据敏感度:比如”涉及用户隐私”或”公开数据”。
区分”取消”和”拒绝”:取消是放弃本次审批,拒绝是拒绝工具调用并记录审计日志。
核心元素:
工具名 + 操作类型
完整参数(可折叠)
预期影响摘要
可逆性提示
数据敏感度标签
审批理由输入框(可选)
确认/拒绝/取消按钮
重要提醒:UI 不能替代 server-side authorization。审批 UI 是前端展示,server-side authorization 是后端权限检查。审批人确认了,后端仍然要检查权限。
比如审批 UI 展示”删除记录 ID=123”,审批人点击确认,但后端仍然要检查记录是否属于当前用户、是否有删除权限。
HITL 不是孤立弹窗,需放进工具网关、日志和权限系统。具体架构见计划中的 MCP 生产架构文章。
OWASP LLM01/LLM06 风险映射
OWASP LLM Top 10 定义了 LLM 和 Agent 的安全风险(编号随版本变化,发布前需核验)。其中两个风险与审批直接相关:
| 风险编号 | 风险描述 | 审批对策 |
|---|---|---|
| LLM01 Prompt Injection | 外部输入诱导未授权函数调用、数据泄露、外部命令执行 | 高风险动作需人工审批,不能只靠 prompt 规则;审批 UI 展示参数和预期影响 |
| LLM06 Excessive Agency | 过度功能、过度权限、过度自主是 Agent 工具系统风险 | 限制工具范围(Policy)、限制自动级别(Approval)、审批按钮”本次会话总是允许”需设置范围限制 |
LLM01 说明 prompt injection 可以诱导模型绕过 prompt 规则,调用未授权工具。审批需要在高风险动作前停下来,展示参数和预期影响,让人工判断。
LLM06 说明过度自主是 Agent 工具系统风险。审批不是万能药,需要配合 Policy(限制工具范围)和 Approval(限制自动级别)。审批按钮”本次会话总是允许”必须设置范围限制,否则 prompt injection 可以诱导滥用。
NIST AI RMF Core 映射
NIST AI RMF Core 定义了 AI 系统风险管理的四阶段框架(个人/小团队可以用轻量版,不必企业级合规):
| 阶段 | 审批职责 | 示例 |
|---|---|---|
| Govern | 定义角色和风险规则 | 定义审批人角色(owner/on-call engineer)、定义审批等级(L0-L3)、定义拒绝和超时处理策略 |
| Map | 识别高风险场景 | 用风险分级矩阵识别高风险动作(删除、付款、改权限)、识别 prompt injection 场景(外部输入诱导) |
| Measure | 测量审批覆盖率、拒绝率 | 统计高风险动作审批覆盖率、统计拒绝率和超时率、优化审批策略 |
| Manage | 事故响应和恢复 | 审批日志用于事故追溯、回滚已执行步骤、补偿事务处理 |
Govern 阶段定义审批规则,Map 阶段识别高风险场景,Measure 阶段测量审批效果:统计高风险动作审批覆盖率、统计拒绝率和超时率、调整审批策略。
个人/小团队可以用轻量版:定义审批等级(L0-L3)、识别高风险动作、统计拒绝率、记录审计日志。不必企业级合规流程,但最小集必须有。
结论
风险分级是审批设计的第一步。不是所有动作都要审批,只读和写草稿可以自动执行,删除和付款必须停下来等人。用五个维度判断:外部影响、可逆性、数据敏感度、金额/权限阈值、自动程度。
审批不是弹窗,是可序列化、可恢复、可审计的系统状态。RunState 被保存到 checkpoint,从暂停点恢复执行,审计日志记录决策和执行链。
四层安全边界互不替代。Policy 限制工具范围,Guardrail 自动检查输入输出,Approval 人工决策高风险动作,Audit 事后追溯。组合使用才能覆盖完整风险。
OWASP LLM01 和 LLM06 说明 prompt injection 和过度自主是 Agent 工具系统的核心风险。审批需要配合 Policy 和 Guardrail,不能孤立使用。
NIST AI RMF Core 提供风险管理框架。个人/小团队可以用轻量版:定义审批等级、识别高风险动作、统计拒绝率、记录审计日志。
下一步阅读:
LangGraph checkpoint/thread state 文章(站内已发布):审批状态保存的技术背景。
Agent monitoring/recovery 文章(站内已发布):审批日志是可观测性的一部分。
MCP 生产架构文章(计划中):HITL 不是孤立弹窗,需放进工具网关和权限系统。
飞书 MCP 调研文章(计划中):外发消息/写协作系统的审批场景参考。
设计 Agent 人工审批流程
用风险分级、运行暂停、审批证据和审计日志设计可恢复的 Agent 人工审批流程。
- 1
步骤 1: 列出工具和动作
列出 Agent 能调用的工具、外部系统和具体动作,不要只按工具名分类。 - 2
步骤 2: 标注风险维度
按外部影响、可逆性、数据敏感度、金额或权限阈值、自主程度标注每个动作的风险。 - 3
步骤 3: 设置审批等级
为每类动作设置 auto、draft、approval、strong approval 或 deny 策略。 - 4
步骤 4: 保存运行状态
在运行时保存 approval request、RunState 或 checkpoint,并关联 taskId、runId 与 traceId。 - 5
步骤 5: 展示审批证据
给审批人展示工具名、参数摘要、影响对象、可逆性、敏感度和预期后果。 - 6
步骤 6: 处理 approve、reject 和 timeout
根据决策恢复执行、降级为草稿、补偿已执行步骤、升级审批人或停止任务。 - 7
步骤 7: 记录审计并回归测试
保存审批日志和恢复结果,把拒绝、超时和补偿路径加入回归测试。
常见问题
AI Agent 哪些操作必须人工审批?
只要有 guardrail,还需要人工审批吗?
OAuth scope 通过后,为什么还要审批?
审批按钮能不能本次会话总是允许?
Agent 被拒绝后应该怎么办?
审批记录应该保存哪些字段?
19 分钟阅读 · 发布于: 2026年9月11日 · 修改于: 2026年9月11日
AI Agent 工程化专题:架构、工具调用、评估与恢复
如果你是从搜索进入这篇文章,建议顺手补上上一篇或继续下一篇,这样更容易把同一主题读完整。
上一篇
Agent 上下文工程实战:System Prompt、Memory、Tools 和 Files 怎么分层
把 Agent 上下文拆成 system prompt、developer rules、memory、files、retrieval、tool schema、runtime state 和 output contract,给你一套避免上下文膨胀和规则失效的工程分层方法。
第 18 / 22 篇
下一篇
Agent 成本控制:模型路由、工具调用预算、缓存和失败重试怎么设计
把 AI Agent 成本控制拆成可落地的预算对象:模型路由、工具调用限额、prompt caching、Batch/Flex、失败重试、熔断、成本日志和告警字段,避免生产任务无声烧钱。
第 20 / 22 篇



评论
使用 GitHub 账号登录后即可评论