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 元,是否确认?

用户回答“确认”后,模型再调用工具。

这种交互可以改善体验,却不能成为可靠的审批边界。

原因并不复杂:

  1. 提示词可能被覆盖、遗漏或受到提示注入影响;
  2. 不同模型和 Agent 框架对同一句要求的执行并不完全一致;
  3. “用户回复确认”未必能证明回复者具有业务审批资格;
  4. 对话中的确认文字没有天然绑定精确工具参数;
  5. 后续模型仍可能重新生成另一组参数。

模型可以负责组织对话、解释风险和发起审批,但它不应该成为判断“这次调用已经获得有效批准”的信任根。

一个基本安全原则是:

模型生成的文本和参数都应被视为不可信输入,不能自行覆盖能力范围、风险、主体和审批决定。

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. 一条完整退款审批链路应该怎样运行

把前面的设计落实为事件序列,可以得到:

  1. 用户提出退款目标;
  2. Agent 选择退款工具并生成参数;
  3. 运行时按照 OpenAPI Schema 校验参数;
  4. 运行时读取可信能力声明与当前路由策略;
  5. 从签名票据、可信会话或身份系统解析操作主体;
  6. 评估无条件或参数级审批意图;
  7. 命中后冻结工具、参数、主体与任务标识;
  8. 创建审批意图,暂停工具外发;
  9. 将审批意图投递到业务后台、OA 或其他企业流程;
  10. 业务侧完成身份、角色和流程判断并返回决定;
  11. 运行时验证决定与 job_id/tool/args_hash 一致;
  12. 原子消费批准单并按原快照恢复任务;
  13. 业务系统重新执行最终权限和业务状态校验;
  14. 成功、拒绝或失败结果写入同一条 Trace 与审计链路。

其中任何一步都不应该由模型文本自行宣布完成。

11. Trace 至少应该回答哪些问题

一次退款事后出现争议时,日志不能只剩一句“工具调用成功”。

完整证据至少应回答:

  1. 谁发起了原始请求?
  2. Agent 选择了哪个工具和哪些参数?
  3. 哪条治理声明或运行时策略触发了审批?
  4. 审批人看到并批准的是哪一份参数快照?
  5. 批准后的执行参数是否与快照一致?
  6. 业务系统最终返回了什么结果?
  7. 整个过程发生在什么时间,耗时多久?
  8. 如果失败,失败发生在哪一层?

普通应用日志、Trace 和审计记录可以共享事件来源,但目的不同:应用日志用于排障,Trace 用于还原调用链,审计记录用于责任证明和合规检查。

因此,审批不应是孤立的一张表,而应成为整条 Agent 行动链中的一个可关联事件。

12. 一个公开实现如何落地这条链路

百灵中枢(BailingHub)是 ACC 的一个开源实现,不是 ACC 的唯一或特权实现。

它在公开 Docker Demo 中采用了以下工程路径:

  1. 退款能力通过 ACC 声明进入工具治理模型;
  2. 高风险或命中确认条件的调用先冻结为审批意图;
  3. 业务侧审批入口接收订单、金额、工具和参数摘要;
  4. 批准决定必须与 approval_idjob_idrequest_idargs_hash 对齐;
  5. 批准后按原参数快照重跑,不让模型借用批准改变调用;
  6. 业务退款接口继续执行自己的权限和状态校验;
  7. 请求、等待审批、外部决定、重跑、工具调用和结果进入同一条 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
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐