企业 AI Agent 的决策不能只靠 prompt,需要确定性规则引擎兜底。但规则本身如何保证覆盖所有边界情况?本文介绍三个开源项目从规则表达、无歧义验证到穷举测试的不同层次方案。
你的审计人员迟早会问这个问题:"这个 Agent 做出的决策——依据是什么?"
LLM 是概率性的。问两遍,得到两个不同的答案。如果你的企业把审批、退款或访问权限决策委托给一个 Agent,而它和一次糟糕决策之间只隔着一个 prompt,那你拥有的不是治理——是一个有头衔的概率分布。
行业正在汇聚到一个答案上:LLM 负责理解,规则负责判决。在模型前面放一个确定性规则引擎,让它来决定 Agent 可以做什么、不可以做什么,无论模型怎么说。
但这里有一个让人不舒服的问题:你怎么知道规则本身是确定性的?
大多数规则引擎用单元测试来支撑这个主张。而单元测试只证明一件事:你测试过的那些输入行为正确。对于你没测试过的输入,它们什么也说明不了。
这篇文章要讲的是如何弥合这个缺口——借助三个开源项目,它们在三个不同层面攻击这个问题:
三者结合在一起,将"确定性"从一种声明变成一种测量——并在极限情况下,变成一种证明。
ERDL(Entity-Rule Definition Language)是一种用于 AI Agent 行为治理的声明式规则格式。核心思想是一个 when → then 决策,用纯 YAML 表达:
protocol: "erdl/v2"
version: "2.1.0"
metadata:
name: "refund-guard"
decision: ALLOW
rules:
- name: "SEC-001-refund-limit"
priority: 10
when:
logic: AND
conditions:
- field: "tool.name"
operator: eq
value: "issue_refund"
- field: "tool.args.amount"
operator: gt
value: 5000
then: REQUEST_HUMAN
message: "Refund amount over 5000, human approval required"
有三件事让它不同于"只是 YAML 配置":
统一的语义树。每条规则都编译成同一个 34 节点表达式树——无论你用 Simple projection(30 个操作符)、Expression projection 还是决策表编写,生成的树都是同一棵树。三种编写方式,一种理解路径。
精确的求值语义。树在一组具名约束(E1–E12)下进行求值,这些约束钉定了现实世界规则中的模糊部分:货币用定点小数算术(scale=14,half-even 舍入——所以 0.1 + 0.2 不会漂移);缺失字段的三值逻辑(缺失字段折叠为 false,所以不会有 fail-open);空量词折叠(all([]) 为 false);以及 NFC 字符串规范化。
天生的可审计性。每次求值都产生一个可哈希、可链接的 Decision Object——这是哪条规则在什么输入上触发的审计记录,以及相应的上下文。
参考实现以 npm 包形式发布:
import { loadErdlFile, Evaluator } from '@openoba/erdl'
const { rules, metadata } = loadErdlFile('refund.erdl.yaml')
const result = new Evaluator().evaluate(rules, {
tool: { name: 'issue_refund', args: { amount: 8000 } },
'metadata.decision': metadata.decision,
})
console.log(result.decision) // 'REQUEST_HUMAN'
但一门语言的可信度取决于"每个实现都对它达成一致"这个主张是否成立。这就是第二层存在的意义。
erdl-vectors 是一个跨实现验证基准:301 个冻结测试向量,不属于任何一个单独的实现。
其机制有意对抗空话连篇:
中立的规范。向量仅从规范文本生成,答案存储在物理隔离的文件中(.gitignored),这样没有人能通过读取预言来"通过"测试。
从第一性原理验证。运行器必须从头重新实现 JCS(RFC 8785)和 SHA-256——不能使用 json-canonicalize 或 SDK——然后逐字节重新计算每个 Decision Object 的哈希。
诚实的金丝雀。一条向量(K01)由一个故意破坏的实现生成。正确的运行器必须将其报告为不匹配。如果某个运行器跳过独立重新计算、只是回显预期答案,就会当场被捕获。
审计层(78 条向量)现已由两个独立第三方运行器逐字节验证——一个用 Go(norviq-go),一个用 Python(concordia-python,由 Erik Newton 开发)——各自匹配了全部 107/107 规范字节。
这一切背后的原则浓缩在仓库的这样一句话里:"中立不是声明出来的——是测量出来的。"注册表记录谁、在什么日期、通过了多少条向量——仅此而已。没有人获得背书;数字自己说话。
这很重要,因为它回答了这个问题:"规范是正确的,还是参考实现只是在与它自己的生成器达成一致?"只有当多个不相关的实现仅从规范文本出发构建、逐字节收敛时,你才能有证据表明这个标准本身是健全的。
关于测试向量这件事——即使有 301 条——它们只是样本。向量证明的是你选择纳入的案例。它们永远无法证明你没有选择的案例。
erdl-formal 是弥合这一缺口的层。它将 ERDL 的表达式内核编译为 SMT(通过 Z3),并对所有输入证明属性——不是样本,而是整个空间。
一条断言同时证明两件事:
from erdl_formal.field_contracts import FieldContract, Schema
from erdl_formal.properties import always_denies
schema = Schema()
schema.add(FieldContract(field="file_cls", type="int"))
schema.add(FieldContract(field="op_cls", type="int"))
# when: file_cls > op_cls → DENY
rule = ["gt", ["field", "file_cls"], ["field", "op_cls"]]
assert always_denies(rule, schema,
premises=["file_cls", "op_cls"],
missing_field="op_cls")
在这一行代码背后,Z3 在所有整数的空间里搜索一个违反规则的输入。如果找到了,你会得到一个具体的反例,可以对着真实引擎重放。如果没找到——UNSAT——则该属性对所有输入都成立,证明完成。
可证明的属性包括:
never-errors —— 求值永不抛出异常
always-denies —— 命中意味着拦截(包括 fail-closed:缺失字段无法绕过规则)
always-allows / subsumption / equivalence / disjointness
override-soundness —— override 只能将 DENY→ALLOW 放松,绝不会收紧到更不安全的状态
ring-respect 和 emergency-shortcut —— ERDL 特有的语义,Cedar/OPA 甚至没有建模
三个"ERDL 特有"的属性才是有趣的部分:它们不是通用策略属性,而是关于这门语言货币、时间、聚合、量词和 Decision Object 语义的保证——正是这些部分使 ERDL 成为企业级规则内核,而不是通用策略 DSL。
关于范围的一个注记,因为诚实建立信任:erdl-formal 证明的是表达式内核——完整的 34 节点树和 E1–E12 约束。文档结构、gloss 渲染和集成模式由向量和工程验证覆盖,不由 SMT 证明。这是一个精确的主张,而精确正是它有价值的原因。
因为它们回答的是不同的问题,而且每一个让下一个变得可信:
ERDL 给语言一个规范含义——没有它,就没有东西可以证明。
erdl-vectors 证明这个含义是可复现的——即独立的实现仅从规范出发,逐字节收敛。
erdl-formal 证明这个含义是安全的——语义在所有输入上成立,而不只是采样案例。
没有向量的语言是"信任我的实现"。没有语言基础的向量只是一个没人用的东西的基准测试。没有向量的证明是对只有你实现了的语义的证明——那是对你代码的证明,而不是对标准的证明。
三者层层叠加,它们之间的差别是:"我们的规则引擎是确定性的"(一种声明)和"这是语言,这是逐字节的一致性,这是证明"(一条审计追踪)。
紧迫性不仅仅关乎单个 Agent。随着 Agent 对 Agent(A2A)协议的成长,Agent 将开始互相委托决策——一个 Agent 批准,另一个 Agent 执行,第三个 Agent 记录。在那个世界里,跨实现信任不能建立在供应商之间的双边协议上。它必须建立在任何独立方都能验证的东西上。
这就是这个技术栈所建设的标准化路径:三个独立实现,一个开放规范,没有单一所有者。每个新的独立运行器都是 Agent 经济体信任基础设施的一块砖。
ERDL engine — npm install @openoba/erdl · spec · MIT
erdl-formal — pip install erdl-formal · repo · Apache-2.0
erdl-vectors — repo · Apache-2.0 · 开放征集独立运行器:仅从规范实现 JCS + SHA-256,验证全部 78 条审计向量,并在注册表中获得记录。
223 条表达式层向量仍在等待它们的第一个独立运行器。如果你想证明一个标准而不是背书一个标准——仓库是开放的。
确定性不是声明出来的。它是被测试的。而在极限情况下,它是被证明的。