Agent 条件审批应该写到什么程度,才不会变成另一套规则引擎?
2026/7/21 19:21:14 网站建设 项目流程

Agent 能调用业务接口之后,下一个问题往往是“什么情况下必须停下来等人”。真正困难的不是加一个审批按钮,而是划清边界:治理契约应该声明哪些可移植条件,运行时应该控制什么,业务系统又必须保留什么。

当团队用 Dify、MCP、函数调用或自研 Agent 接通一套业务系统后,审批需求通常很快出现。

最初的规则也许很简单:

退款金额超过 1000 元时,需要人工审批。

接着,规则会逐渐变成:

退款金额超过 1000 元,并且客户不是 VIP 时,需要经理审批; 如果订单来自高风险地区,还要经过风控; 如果是全额退款,则由财务复核; 黑名单客户直接拒绝; 夜间发起的退款只能等到次日处理。

于是一个很自然的想法出现了:

既然已经有一份 Agent 能力声明,为什么不把这些规则全部写进去?

如果沿着这条路继续走,能力声明很快就会长出嵌套的ANDORNOT,再加入变量引用、数据库查询、身份属性、时间窗口、审批人匹配、工作流跳转和自定义函数。

最后,它不再是一份不同运行时可以共同理解的能力契约,而会变成另一门尚未被完整定义的业务规则语言。

问题因此不是“条件越多越好”,而是:

条件审批应该表达多少,才能既让不同运行时得到一致的治理意图,又不假装自己能够取代企业的策略引擎、审批系统和最终业务授权?

1. 先把四个经常混在一起的问题拆开

一次 Agent 工具调用进入审批链路时,至少存在四个不同问题。

1.1 这次调用是否需要外部决策

例如:

amount > 1000

这是调用发生之前,可以根据已声明参数判断的治理意图。

1.2 外部决策由谁作出

可能是当前用户、门店店长、财务人员、OA 流程、风控系统或另一个组织。

这是审批所有权和组织流程问题。

1.3 审批过程怎样暂停和恢复执行

有界面的 Agent 可以弹出确认页;无界面的 API Gateway 可以返回待处理状态;异步运行时可以创建审批任务并挂起调用;确定性工作流可以暂停节点。

这是运行时控制问题。

1.4 当前主体最终有没有权执行

审批通过之后,业务系统仍要检查:

  • 当前员工是否有退款权限;
  • 订单是否属于当前租户;
  • 订单状态是否仍允许退款;
  • 可退款余额是否充足;
  • 同一笔退款是否已经执行。

这是最终业务授权,也就是 Authority。

这四件事相关,却不能被压缩成一个approved: true

一份可移植的治理契约,最多只能稳定声明第一件事:这次调用是否产生了外部决策意图。

2. 条件审批不是业务授权

假设接口声明了:

approval:when:-param:amountop:">"value:1000

它表达的是:

当本次调用参数amount大于 1000 时,运行时应在执行前创建审批意图。

它不表达:

  • 金额小于等于 1000 时业务系统必须放行;
  • 审批通过后退款一定合法;
  • 调用者具有处理该订单的权限;
  • 当前订单还有足够的可退款余额;
  • 这笔退款没有违反黑名单或监管规则。

条件未触发,只代表这条声明没有要求外部审批。

它不代表业务授权已经完成。

这一区分非常重要。否则,团队很容易把一个轻量的调用前治理判断,错误升级成业务系统必须信任的权限结论。

3. 什么样的条件才适合进入通用契约

一个条件要成为跨运行时可移植的声明语义,至少应该同时满足几项要求。

3.1 输入来自当前调用,而不是私有业务数据库

运行时可以直接得到:

{"order_id":"SO-1001","amount":1500}

因此,它可以一致判断amount > 1000

但“退款金额是否超过当前可退款余额”需要查询订单、历史退款和实时状态。不同运行时并不知道:

  • 去哪里获取余额;
  • 用谁的身份查询;
  • 数据多久算新鲜;
  • 查询失败时如何处理;
  • 审批等待期间数据变化后是否重新计算。

这类规则属于业务操作或专门的预检 API,不应被伪装成静态契约条件。

3.2 比较值是明确字面量,而不是未定义的上下文变量

下面的声明容易理解:

-param:amountop:">"value:1000

而下面的写法看似更强,实际上引入了新的协议问题:

-param:amountop:"=="value:"$order.refundable_balance"

$order.refundable_balance从哪里来、谁能读取、何时失效、拿不到时怎么办,都没有答案。

一个没有数据来源、授权、时效和失败语义的“变量”,不会增强契约,只会制造可执行的假象。

3.3 条件必须具有确定的类型语义

如果调用参数是字符串:

{"amount":"1500"}

运行时不能擅自把它转换成数字1500再进行比较。

否则,不同语言和框架可能采用不同的隐式转换规则:

  • 某个 JavaScript 实现认为条件成立;
  • 某个严格类型实现直接报错;
  • 某个实现把非法值当成条件未命中,于是绕过审批。

对治理条件而言,“类型不兼容”不能被安静地解释为“不需要审批”。正确行为是:在调用到达业务操作之前拒绝这次无效调用。

3.4 独立实现必须得到同一个判断结果

如果一条声明在两个兼容运行时中可以分别被解释成“需要审批”和“不需要审批”,那就不是实现自由,而是规范歧义。

审批界面的样式可以不同,审批任务存在哪里也可以不同;但相同参数是否产生审批意图必须一致。

4. ACC v1 为什么使用有界的条件模型

ACC v1 的approval可以表达无条件审批意图:

x-agent-capability:version:1enabled:truescope:refund.createapproval:required:trueprompt:"Approve refund for order {order_id}?"

approval.required: true表示每一次调用都需要创建审批意图。

条件规则不能降低这项要求。即使所有条件都未命中,无条件审批仍然成立。

它也可以表达参数级条件审批:

x-agent-capability:version:1enabled:truescope:refund.createapproval:when:-param:amountop:">"value:1000label:"退款金额超过 1000 元"-param:batch_countop:">"value:20label:"批量退款超过 20 笔"

approval.requiredfalse或省略时,approval.when使用ANY语义:任意一条条件命中,就创建审批意图。

也就是说,上面的规则表达:

amount > 1000 或者 batch_count > 20

任一成立,都需要外部决策。

如果approval.when不存在或为空,则不会产生条件审批意图。

ACC v1 把操作符限制为>>=<<===!=incontainsexists。其中:

  • 大小比较只接受数值参数和有限数值字面量;
  • ==!=in使用 JSON 类型感知的严格相等;
  • contains只适用于字符串或数组,数组元素仍按严格 JSON 相等判断;
  • exists只判断参数路径是否存在,不要求value
  • param必须解析到绑定操作已经声明的输入,不能凭空访问其他上下文。

这套模型不复杂,但它有一个重要性质:审批要求是单调增加的。

  • 增加一条条件,只会增加可能触发审批的情况;
  • 某条条件未命中,不能取消另一条已经命中的条件;
  • 条件规则不能取消approval.required: true
  • 模型输出不能把治理元数据改成更宽松的值。

在安全边界中,这种可预测性往往比表达式的丰富程度更有价值。

5. 为什么 v1 不急着加入任意 AND / OR 嵌套

开发者很容易提出这样的需求:

金额超过 1000,并且商品属于电子产品时才审批。

如果只看这一条规则,加入allany好像很简单。

但一旦允许通用布尔组合,下一步通常会出现:

(A AND B) OR (C AND NOT D)

再下一步会出现:

  • 嵌套条件的优先级;
  • 缺失字段在NOT中的含义;
  • 数组中“任意元素”和“全部元素”的区别;
  • 条件求值错误时是拒绝、审批还是跳过;
  • 新旧运行时忽略未知操作符时是否会降低安全性;
  • 不同语言对数字、空值和集合比较的差异。

这时,规范已经站在通用策略语言的门口。

通用策略语言当然有价值,但它需要完整定义:

  • 表达式语法;
  • 类型系统;
  • 数据来源;
  • 求值顺序;
  • 错误和失败语义;
  • 沙箱与资源限制;
  • 版本与兼容策略。

如果这些内容没有被完整定义,只增加几个看起来灵活的字段,反而会让实现者产生“规则已经被可靠执行”的错觉。

因此,ACC v1 选择有界的 ANY 条件,不是在否认复杂策略的存在,而是在拒绝用半套表达式语言冒充可移植标准。

需要复杂布尔策略的部署,可以在明确的运行时策略命名空间中使用 OPA、Cedar、自有规则引擎或其他成熟方案;但它们的结果不能被伪装成所有 ACC 运行时都必须理解的核心语义。

6. 为什么这些规则不应该写进条件审批

下面几类常见要求都很重要,但不属于通用条件审批声明。

6.1 黑名单客户直接拒绝

这是业务拒绝规则,不是审批意图。

黑名单数据的来源、更新频率、申诉机制和最终效力都由业务系统掌握。即使 Agent 运行时提前发现风险,业务系统仍必须在所有调用路径上执行同一规则。

6.2 退款金额不能超过当前可退款余额

这是依赖实时业务状态的跨字段不变量。

它应由退款操作或业务预检 API 验证,而不能依赖运行时持有一份可能过期的订单快照。

6.3 超过 5 万元由总监审批

“超过 5 万元需要外部决策”可以是可移植声明;“由哪位总监审批”则依赖组织目录、岗位代理、休假委托和企业流程。

前者可以属于能力契约,后者属于审批所有者。

6.4 审批 30 分钟后过期

有效期涉及时钟来源、等待队列、重试、跨时区和失效后的恢复方式。它应由审批协议或部署策略完整定义。

简单增加一个时间字符串,却让旧运行时忽略它继续执行,会造成安全意图降级。

6.5 只有工作日 9:00 到 18:00 才允许执行

时间窗口需要时区、节假日日历、可靠时钟和失败语义。它是有价值的运行时策略,却不因此自动成为通用核心字段。

关键不是这些需求“高级”或“不重要”,而是它们分别依赖不同的权威数据和执行责任。

7. 审批意图为什么不等于审批工作流

同一份审批声明,可以被不同类型的运行时一致理解,却以不同方式兑现:

运行时形态可能的实现
带 UI 的 Agent 应用展示结构化确认页并等待决定
无 UI 的 API Gateway返回待审批状态和后续查询入口
异步任务系统创建审批任务并挂起执行
确定性工作流暂停当前节点,收到外部决定后恢复
企业控制面接入现有 OA、工单或业务审批系统

这些实现不必拥有相同界面、数据库和任务状态机。

它们需要一致的是:

在相同声明和相同调用参数下,是否必须先取得外部决策,再进入业务执行。

ACC 因此声明 approval intent,而不定义:

  • 谁来审批;
  • 审批人如何匹配;
  • 是否需要多人会签;
  • 审批 UI 长什么样;
  • 任务如何持久化;
  • 超时后怎样升级;
  • 批准后由哪个队列恢复执行。

这些是运行时和企业流程真正应该拥有的自由度。

8. 审批通过,也只能批准被看见的那一次调用

条件判断解决的是“是否需要外部决策”,还没有解决“批准的究竟是什么”。

如果审批页面展示的是:

{"order_id":"SO-1001","amount":1500}

审批通过后,Agent 重新推理成:

{"order_id":"SO-1001","amount":15000}

原审批不能授权这组新参数。

一个可靠运行时通常需要:

  1. 在创建审批意图前校验并冻结能力标识和调用参数;
  2. 将审批决定绑定到被审阅的精确调用;
  3. 防止批准被另一组参数复用;
  4. 默认把批准作为一次性消费;
  5. 区分拒绝、过期、执行失败和业务系统拒绝;
  6. 恢复执行时再次核对调用快照。

这些属于实现安全属性和配套审批协议,不是approval.when本身能够证明的事情。

同样,一条由运行时自己写入的“已审批”日志,也不自动构成第三方可验证证据。高保证场景还需要定义规范化序列化、签名者、密钥发现、时效、重放保护和失败关闭行为。

通用契约应该明确自己的非保证,而不是用一个字段暗示整条证据链已经成立。

9. 一条完整的条件审批链路应该怎样工作

把各层重新组合,一次调用可以经历下面的顺序:

Agent 选择能力并提出参数 -> 按绑定协议的输入 Schema 校验参数 -> 解析可信行动主体 -> 评估能力范围与运行时策略 -> 评估 risk 与 approval 意图 -> 条件命中时冻结本次调用并创建审批意图 -> 由外部审批所有者作出决定 -> 核对决定绑定的能力、主体和参数 -> 恢复被批准的精确调用 -> 业务系统按实时权限和业务状态作最终授权 -> 记录结果、拒绝或失败

在这条链路中:

  • 模型可以提出工具和参数,但不能批准自己;
  • 契约可以声明审批意图,但不能任命企业审批人;
  • 运行时可以暂停和恢复,但不能替代业务最终授权;
  • 业务系统可以拒绝已经审批的调用;
  • 审批系统可以作出决定,但不能悄悄改变被批准的参数。

责任边界越清楚,系统越不需要依赖某一个“万能组件”。

10. 六个常见误区

误区一:把审批要求写进系统提示词就够了

提示词可以改善模型行为,不能成为执行层的信任边界。

误区二:条件未命中就代表调用已获授权

条件只决定是否产生审批意图,最终 Authority 仍属于业务系统。

误区三:字符串"1500"可以自动当成数字比较

治理条件必须使用严格、类型感知的比较。类型不兼容应在执行前被拒绝。

误区四:条件越复杂,契约越专业

没有完整语法、数据协议和失败语义的复杂表达式,只会增加跨实现歧义。

误区五:批准“退款”就等于批准之后任意退款参数

审批必须绑定被审阅的精确能力和调用参数。

误区六:审批通过后业务系统必须执行

订单状态、租户隔离、余额和当前主体权限仍由业务系统实时判断。

11. 一份最小设计检查表

在为 Agent 工具增加条件审批时,可以先回答下面的问题:

  • 条件是否只读取当前调用中已经声明、已经校验的参数?
  • 比较值是否是明确字面量,而不是来源不明的上下文变量?
  • 每个操作符是否具有严格、跨语言一致的类型语义?
  • 类型不兼容时是否在业务执行前拒绝,而不是按“未命中”放行?
  • 无条件审批是否不会被条件规则取消?
  • 模型输出是否无法修改风险、主体和审批治理元数据?
  • 审批意图是否与审批人、UI 和工作流实现保持分离?
  • 审批决定是否绑定精确能力、主体和参数快照?
  • 审批通过后,业务系统是否仍执行最终授权?
  • 需要复杂业务规则时,是否交给有权威数据的策略或业务层?

如果这些问题没有被回答,“加了审批”往往只意味着系统多了一个页面,还没有建立可靠的行动边界。

12. 结语:好的条件审批不是表达一切,而是只承诺能够一致兑现的部分

Agent 进入真实业务系统后,条件审批确实是不可回避的治理需求。

但标准化的价值不在于把企业所有规则搬进同一份 YAML,而在于找到一组不同运行时都能一致理解、验证和执行的最小公共语义。

对于条件审批,这个最小语义可以非常克制:

  • 条件只读取已声明的调用参数;
  • 只与明确字面量进行类型严格的比较;
  • 任一独立条件命中就产生审批意图;
  • 无条件审批不能被条件取消;
  • 审批意图不冒充审批流程;
  • 审批通过不冒充最终业务授权。

复杂策略、组织流程和动态业务状态都是真问题。但尊重这些问题所属的层次,比把它们全部塞进契约更专业。

一份行业契约最难的地方,从来不是还能增加多少字段。

而是它是否有足够的纪律,只声明那些自己真的能够跨系统兑现的承诺。


延伸阅读

  • ACC 规范:SPEC.md中的approval语义
  • ACC 设计边界:DESIGN_RATIONALE.md
  • ACC 实现者指南:审批意图、调用冻结与恢复参考流程
  • 前序拆解:风险分级应该描述操作,还是直接规定审批流程?
  • 前序拆解:Agent 代表谁行动,可信主体为什么不能来自模型输出?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询