Agent 调用业务发起退款时,审批应该放在哪一层?
Human-in-the-loop 不只是弹一个确认框。真正需要设计的是:Agent 何时暂停、批准的究竟是哪组参数、谁有权作出决定,以及批准后为什么仍不能绕过业务系统的最终授权。
假设一套商城已经允许 Agent 查询订单。
用户接着说:
帮订单
SO-1001退款 199 元。
模型找到了退款工具,也生成了看起来正确的参数:
{
"order_id": "SO-1001",
"amount": 199,
"reason": "duplicate payment"
}
如果这是查询订单,完成参数校验后或许就可以继续执行。但退款会改变真实业务状态,甚至影响资金。工程团队很快会提出一个合理要求:
先让人确认,再执行。
这句话听起来简单,真正实现时却会遇到一连串问题:
- 确认框放在聊天界面,还是放在业务后台?
- Agent 从网页、企业微信、API 和定时任务进入时,是否都能经过同一套审批?
- 审批人批准的是“允许退款”这句话,还是订单、金额、原因组成的精确调用?
- 审批等待期间,Agent 任务应该怎样暂停和恢复?
- 批准之后模型重新推理,把 199 元改成 1990 元怎么办?
- 审批通过后,订单状态已经变化或余额已经不足,又该由谁拒绝?
- 从用户请求到最终退款,事后能否还原完整责任链?
所以,“给 Agent 加审批”不是增加一个按钮,而是在调用链中建立一条可验证的人工决策边界。
本文从一笔退款出发,逐层判断审批究竟应该放在哪里。
1. 先区分四件经常被混在一起的事
一次 Agent 退款链路中,至少存在四个不同问题。
1.1 Agent 是否可以触达退款能力
客服问答场景也许只允许查询订单;商家运营场景才允许提出退款申请;匿名访客不应该看见任何资金类工具。
这属于能力可达范围,也就是 Reach。
1.2 这次调用是否需要外部决策
退款 10 元、199 元和 10000 元,企业可能采用不同的人工介入策略。某些退款每次都需要审批,某些退款只有金额超过阈值才需要审批。
这属于审批意图。
1.3 谁来批准这次调用
可能是门店店长、财务人员、OA 审批流,也可能是业务系统已有的售后流程。审批人、组织层级和审批入口取决于企业自身。
这属于业务审批所有权。
1.4 当前主体此刻是否仍有权退款
即使审批已经通过,业务系统仍要检查:
- 当前员工是否有权处理该订单;
- 订单是否属于当前门店或租户;
- 订单现在是否仍处于可退款状态;
- 剩余可退款金额是否足够;
- 同一退款是否已经执行过。
这属于最终业务授权,也就是 Authority。
四者不能用一个“审批通过”全部替代。
2. 方案一:让模型自己决定是否询问用户
最容易实现的方案,是在系统提示词里写:
调用退款工具前,必须先询问用户是否确认。
然后模型先回复:
即将为 SO-1001 退款 199 元,是否确认?
用户回答“确认”后,模型再调用工具。
这种交互可以改善体验,却不能成为可靠的审批边界。
原因并不复杂:
- 提示词可能被覆盖、遗漏或受到提示注入影响;
- 不同模型和 Agent 框架对同一句要求的执行并不完全一致;
- “用户回复确认”未必能证明回复者具有业务审批资格;
- 对话中的确认文字没有天然绑定精确工具参数;
- 后续模型仍可能重新生成另一组参数。
模型可以负责组织对话、解释风险和发起审批,但它不应该成为判断“这次调用已经获得有效批准”的信任根。
一个基本安全原则是:
模型生成的文本和参数都应被视为不可信输入,不能自行覆盖能力范围、风险、主体和审批决定。
3. 方案二:在聊天界面弹出确认框
第二种方案,是在聊天组件中拦截退款工具,显示一个确认弹窗:
订单:SO-1001
退款金额:199 元
[取消] [确认退款]
这比纯提示词更可靠,因为用户确实看到了结构化参数。但它仍然只是一个交互界面,不是完整的治理机制。
3.1 Agent 不一定只从这个界面进入
同一个退款能力可能同时被以下入口调用:
- 网页聊天组件;
- 企业微信或钉钉;
- 后台管理端;
- API 触发的自动任务;
- 本地执行器或其他 Agent 平台。
如果审批只存在于某个前端组件,换一个入口就可能绕过它。
3.2 点击确认的人不一定是审批人
发起退款的人、使用聊天界面的人和有权批准退款的人可能不是同一个人。真实企业流程还可能要求店长、财务或风控系统参与。
3.3 前端状态不能独自证明执行边界
即使前端确认了 199 元,后端仍需证明真正执行的工具和参数与页面展示的一致。否则确认框只是“看起来确认过”,不能形成可审计证据。
因此,前端确认框可以是审批体验的一部分,但不能是审批事实的唯一存储和执行依据。
4. 方案三:把审批完全塞进 Agent 编排流程
很多 Agent 平台和工作流引擎支持暂停节点:工具调用前挂起,等待人工点击,再恢复工作流。
这已经接近正确方向,因为它解决了“执行前暂停”和“决策后恢复”。但仍要回答三个问题。
4.1 治理是否被绑定在单一 Agent 平台中
如果企业同时使用多个 Agent 框架,或者未来更换模型平台,每个平台都维护一份退款审批配置,治理规则就会迅速分裂。
4.2 恢复的是原调用,还是让模型重新猜一次
有些实现会在审批通过后重新运行整个 Agent。模型再次推理时,可能选择不同工具,或者改变退款金额、原因和订单号。
如果审批只记录“允许继续”,却没有冻结原始调用,那么恢复执行不等于执行被批准的内容。
4.3 审批流程是否越过了业务系统
Agent 平台通常不了解企业完整的组织结构、门店关系、财务权限和订单状态。它可以承载通用的暂停与恢复,却不应该凭空成为业务审批系统和最终授权系统。
所以,Agent 编排层可以参与实现 Human-in-the-loop,但必须有清晰的跨入口治理语义、参数绑定和业务侧决策边界。
5. 方案四:全部交给业务 API 自己处理
另一种极端是:不在 Agent 侧做任何通用治理,退款 API 收到请求后自己返回“等待审批”。
这条路线有一个重要优点:业务系统最了解自己的权限和流程,也必须保留最终决定权。
但如果所有问题都留给每个业务接口单独解决,企业仍会重复建设:
- 哪些能力可以暴露给 Agent;
- Agent 代表哪个可信主体;
- 哪些调用应在到达业务副作用之前暂停;
- 如何统一冻结参数、消费批准和恢复任务;
- 如何把 Agent 请求、审批和工具结果串成一条 Trace;
- 多个业务系统如何采用一致的治理词汇。
更重要的是,业务接口不应该承担模型循环和 Agent 任务恢复。它负责业务规则与最终授权,但不必理解每一种 Agent 框架的运行方式。
因此,正确答案也不是“审批只放在业务系统里”,而是让业务系统拥有审批决定和最终授权,同时由一个模型无关的治理运行时承接通用执行控制。
6. 真正应该分开的,是“审批声明、执行暂停、决策归属和最终授权”
把前面的失败路径收拢,可以得到一条更稳定的分层:
OpenAPI + 能力治理契约
声明:这项能力是否需要审批,哪些参数条件产生审批意图
↓
Agent 治理运行时 / 控制面
执行:校验参数、解析可信主体、冻结调用、暂停任务、等待决策
↓
业务系统 / OA / 企业审批流程
决策:谁能批准、在哪里批准、是否需要多级审批
↓
治理运行时恢复执行
校验:只允许原工具和原参数快照,批准单只能消费一次
↓
业务系统
授权:结合实时用户、租户、对象状态和业务规则作最终判断
这四个位置分别回答不同问题:
| 层次 | 回答的问题 |
|---|---|
| 能力治理契约 | 这项调用是否表达了人工决策需求? |
| 治理运行时 | 何时暂停,如何冻结和恢复? |
| 业务审批流程 | 谁有资格作出什么决定? |
| 业务系统 | 当前主体此刻能否操作这个具体对象? |
这也是“审批应该放在哪一层”的准确答案:
审批不是只放在某一层。审批意图属于能力声明,执行闸门属于治理运行时,审批决定属于业务侧流程,最终 Authority 始终属于业务系统。
7. ACC 为什么只声明审批意图,而不规定谁来审批
如果一个能力使用 ACC(Agent Capability Contract)声明,可以在 OpenAPI operation 上表达无条件或参数级审批意图。
每次调用都需要审批:
x-agent-capability:
version: 1
enabled: true
scope: refund.execute
risk:
level: high
subject:
required: true
approval:
required: true
prompt: "Approve refund for order {order_id}?"
audit:
sensitive: true
execution:
readonly: false
idempotent: true
只有金额超过 1000 元时产生审批意图:
x-agent-capability:
version: 1
enabled: true
scope: refund.execute
risk:
level: medium
subject:
required: true
approval:
prompt: "Approve refund for order {order_id}?"
when:
- param: amount
op: ">"
value: 1000
label: Refund amount exceeds 1000.
ACC v1 对 approval.when 使用 ANY 语义:任意一条条件命中即可产生审批意图;参数比较遵循严格 JSON 类型,不应把字符串 "1000" 自动当作数字 1000。
但 ACC 有意不定义:
- 审批人是谁;
- 企业采用 OA、IM 卡片还是业务后台;
- 是否需要一级或多级审批;
- 审批组织结构如何配置;
- 业务系统最终是否执行。
原因不是这些问题不重要,而是它们依赖企业身份系统、组织结构和业务流程,无法成为跨企业、跨运行时成立的通用声明。
ACC 负责提供可机器读取的审批意图。实现方负责兑现它,业务方负责作出真实决定。
8. 审批最容易被忽视的漏洞:批准内容和执行内容发生漂移
设想审批页面展示的是:
{
"order_id": "SO-1001",
"amount": 199
}
审批人点击通过。
任务恢复后,Agent 重新读取上下文并再次生成:
{
"order_id": "SO-1001",
"amount": 1990
}
如果系统只记录“退款工具已经获批”,1990 元就可能借用 199 元的批准继续执行。这不是模型是否聪明的问题,而是审批边界没有绑定到具体行动。
一个可靠的实现至少需要冻结:
job_id:哪一次 Agent 任务;tool:批准的是哪个工具;args:批准的完整参数快照;args_hash:参数规范化后的摘要;subject:谁代表谁行动;approval_id:哪一份审批意图;- 决策状态与消费状态。
批准后的放行条件应类似:
同一个 job
+ 同一个 tool
+ 同一个 args_hash
+ 审批已通过
+ 批准单尚未被消费
只要订单号、金额、原因或工具发生变化,就不应复用原批准,而应重新产生审批意图。
这里的哈希不是为了隐藏参数,而是为了建立“审批看到什么,执行就只能是什么”的可验证绑定。系统仍应安全保存必要的参数快照,以便恢复执行和审计。
9. 为什么审批通过后不能直接认为业务已经授权
审批通过只说明一个外部决策者同意了某个精确调用意图。
它不保证审批等待期间业务事实没有变化。
例如:
- 订单已经被另一名员工退款;
- 当前操作主体的退款权限被撤销;
- 订单所属门店发生变化;
- 剩余可退款金额已经不足;
- 业务系统进入结算或冻结状态。
所以,真正执行 POST /refunds 时,业务系统必须重新校验实时状态,并且可以拒绝一笔已经获得人工批准的调用。
这不是审批失效,而是两种控制各司其职:
审批:人是否同意这项精确行动意图?
授权:当前业务事实是否仍允许这项行动发生?
审批不能替代最终授权,正如 ACC 的 Reach 不能替代业务系统的 Authority。
10. 一条完整退款审批链路应该怎样运行
把前面的设计落实为事件序列,可以得到:
- 用户提出退款目标;
- Agent 选择退款工具并生成参数;
- 运行时按照 OpenAPI Schema 校验参数;
- 运行时读取可信能力声明与当前路由策略;
- 从签名票据、可信会话或身份系统解析操作主体;
- 评估无条件或参数级审批意图;
- 命中后冻结工具、参数、主体与任务标识;
- 创建审批意图,暂停工具外发;
- 将审批意图投递到业务后台、OA 或其他企业流程;
- 业务侧完成身份、角色和流程判断并返回决定;
- 运行时验证决定与
job_id/tool/args_hash一致; - 原子消费批准单并按原快照恢复任务;
- 业务系统重新执行最终权限和业务状态校验;
- 成功、拒绝或失败结果写入同一条 Trace 与审计链路。
其中任何一步都不应该由模型文本自行宣布完成。
11. Trace 至少应该回答哪些问题
一次退款事后出现争议时,日志不能只剩一句“工具调用成功”。
完整证据至少应回答:
- 谁发起了原始请求?
- Agent 选择了哪个工具和哪些参数?
- 哪条治理声明或运行时策略触发了审批?
- 审批人看到并批准的是哪一份参数快照?
- 批准后的执行参数是否与快照一致?
- 业务系统最终返回了什么结果?
- 整个过程发生在什么时间,耗时多久?
- 如果失败,失败发生在哪一层?
普通应用日志、Trace 和审计记录可以共享事件来源,但目的不同:应用日志用于排障,Trace 用于还原调用链,审计记录用于责任证明和合规检查。
因此,审批不应是孤立的一张表,而应成为整条 Agent 行动链中的一个可关联事件。
12. 一个公开实现如何落地这条链路
百灵中枢(BailingHub)是 ACC 的一个开源实现,不是 ACC 的唯一或特权实现。
它在公开 Docker Demo 中采用了以下工程路径:
- 退款能力通过 ACC 声明进入工具治理模型;
- 高风险或命中确认条件的调用先冻结为审批意图;
- 业务侧审批入口接收订单、金额、工具和参数摘要;
- 批准决定必须与
approval_id、job_id、request_id和args_hash对齐; - 批准后按原参数快照重跑,不让模型借用批准改变调用;
- 业务退款接口继续执行自己的权限和状态校验;
- 请求、等待审批、外部决定、重跑、工具调用和结果进入同一条 Trace。
公开演示使用订单 SO-1001 和 199 元退款,只用于说明一种实现方式。其他运行时也可以采用不同的存储、消息系统、审批适配器和任务恢复机制,只要能够兑现相同的安全边界。
这也再次说明:
契约负责形成共同语义,实现负责形成执行保证,业务系统负责形成最终事实。
13. 给开发团队的一份最小检查表
在把退款、库存、员工状态和权限变更等能力开放给 Agent 前,可以先检查:
- 审批规则是否来自可信配置,而不是只存在于提示词?
- 所有调用入口是否经过同一个服务端治理闸门?
- 审批是否发生在业务副作用之前?
- 审批页面是否展示工具、主体和完整关键参数?
- 批准是否绑定到精确参数快照或哈希?
- 改参数、改工具或改主体后是否必须重新审批?
- 同一批准单是否只能消费一次?
- 审批决定是否来自可信业务流程并具有幂等标识?
- 业务系统是否在执行时重新验证实时权限和对象状态?
- Trace 是否能串起请求、审批、恢复执行和最终结果?
- 敏感参数是否按组织策略脱敏或摘要化?
如果这些问题没有答案,一个确认按钮通常只是界面功能,还不是可依赖的 Human-in-the-loop。
14. 结语:审批不是一个按钮,而是一条责任边界
当 Agent 只回答问题时,“请确认”可能只是一段交互文案。
当 Agent 开始退款、改库存、停用员工或执行运维命令时,确认必须升级为一种可验证的工程事实:
- 谁提出了行动;
- 行动的精确内容是什么;
- 为什么需要外部决定;
- 谁作出了决定;
- 真正执行的内容是否与批准内容一致;
- 业务系统为什么最终接受或拒绝;
- 事后能否还原完整链路。
所以,审批不应该被塞进模型提示词,也不应该只存在于某一个前端确认框,更不能在批准后取代业务授权。
更稳定的设计是:
用公共契约声明审批意图,用治理运行时冻结并暂停精确调用,用业务侧流程承接真实决策,再由业务系统完成最终授权。
这条链路看起来比“弹框后直接调用”多了几步,却正是企业敢于把只读 Agent 推进到真实业务写操作所需要的基础设施。
延伸阅读与公开演示
- ACC 官网:https://www.agentcapability.org/
- ACC 规范仓库:https://github.com/agent-capability/agent-capability-contract
- 百灵中枢开源仓库:https://github.com/bailinghub/bailinghub
- 退款审批与 Trace 公开演示:https://github.com/bailinghub/bailinghub/discussions/7
- 百灵中枢在线体验:https://trial.bailinghub.com/console/login
更多推荐




所有评论(0)