AI Tutor Engine 的驗收規則編譯檢查方法
Agent 总是验收失败?给验收规则做一编译检查”
文章為 AI Tutor Engine 的證據驗收規則提出發布前靜態檢查方法,並以 Python 檢查器從結構、部署能力及有限邏輯三層識別欄位錯誤、不可達來源和空數值區間。文案疑似與規則衝突時,檢查器會交由人工確認,不會自動修改規則。驗證包括 35 個測試方法及 288 組差分輸入,並涵蓋部署清單與設定漂移檢查。
简介: 本文介绍一种面向规则配置的静态检查方法,聚焦AI Tutor Engine中证据验收规则的可靠性验证。通过结构、能力、逻辑三层确定性检查,识别字段错误、来源不可达、区间冲突等问题;保留文案歧义供人工复核。配套Python检查器支持语义适配、部署清单绑定与可测试性设计,助力规则质量门禁落地。(239字)
目录
9. 这套方法与 Schema、Policy Testing 有什么关系
摘要:自动验收把提交材料与规则放在一起判断,但规则本身也可能写错。
本文从 AI Tutor Engine (信科院智能助手)中“全部必需”与“任选一种”的证据表达出发,检查规则声明、执行逻辑和采集能力之间的关系,并实现一个可运行的 Python 规则检查器。检查器在发布前发现字段错误、重复编号、不可达的证据入口与空数值区间,将自然语言疑似冲突交给人工审查。文章附带六张流程图、故障样例、35 个测试方法、288 组适配差分输入及原项目五个函数级核验场景,可作为验收系统、质量门禁和规则配置工具的起点。
1. 当提交者在修改错误的东西
一条验收说明写着:提供 CI 结果或本地运行材料,任一即可。提交者交了本地运行材料,系统却继续要求 CI。如果只看评审结果,下一步很容易变成补工作流、改项目、再提交。可真正需要修改的,可能是验收规则:文案表达了选择关系,配置却把两种材料都放进必需列表。
本系列的第四篇讨论 Evidence Pipeline 怎样采集证据并完成验收,第五篇追踪评审意见怎样进入下一轮指导。这一篇把检查的位置向前移动:在规则开始约束别人之前,先检查它是否能够按作者的意图执行。
上述 AND/OR 问题来自前文对项目的讨论。2026 年 11 月 2 日重新读取本地代码时,项目已经提供 evidence_any_of,并加入证据类型白名单及权重下限校验。因此,本篇从当前行为出发构造对照,不把旧问题描述成当前线上仍然存在的故障。我们最终要做一个小工具:输入规则与当前部署的能力清单,输出带位置的诊断。它回答“这份规则能否进入发布流程”;交付物是否合格,仍由后续验收完成。

图1 规则错误交给规则作者处理,提交缺口交给提交者处理。
2. 先看运行逻辑,字段名不能替代语义
当前项目的 Rubric 包含两个列表:
required_evidence: list[str] = Field(default_factory=list)
evidence_any_of: list[str] = Field(default_factory=list)
第一个表达共同要求,第二个表达可替代材料。但要知道它们实际上怎样执行,还要继续看 review.py 的 _evidence_gaps():
missing = sorted(set(rubric.required_evidence) - _OPTIONAL_EVIDENCE - present)
alternatives = set(rubric.evidence_any_of)
if alternatives and not alternatives.intersection(present):
missing.append(' / '.join(sorted(alternatives)))
return missing
假设 CI 和本地运行材料都属于正常启用的来源,可以比较两种表达:
| 配置 | 只有 runtime | ci 和 runtime 都有 |
|---|---|---|
required_evidence=["ci", "runtime"] |
缺 ci | 门槛满足 |
evidence_any_of=["ci", "runtime"] |
门槛满足 | 门槛满足 |
“任选一种”允许两种都提供。若把它误写成“恰好一种”,提交更多材料反而可能被拒绝。这正是 OR 与 XOR 的区别。JSON Schema 官方文档对 allOf、anyOf、oneOf 分别作了相应定义。[1]

图2 变化发生在规则组合方式上,增加模型调用不能修正错误的组合。
继续看第一行,还会发现另一个细节:required_evidence 并非所有条目都会成为硬门槛。当前 _OPTIONAL_EVIDENCE 包括 deployment、visual、report、issue、description,这些名称在求差集时被剔除了。
这是有意设计的兼容策略。例如,没有上传截图不应直接阻止继续评审。但规则作者仅看“required”这个名字,很容易误以为这里的所有内容都必须提供。同一份材料放进不同字段,行为也不完全对称:当前 _evidence_gaps() 没有对 evidence_any_of 做上述可选项剔除。把 report 单独放进替代组,缺少它就会产生缺口。
这不意味着要自动改动现有规则。它说明规则检查必须读取执行语义:一份适配原项目的检查器,应提示作者检查“声明为必需但运行时忽略”的条目,以及可选来源在不同字段中的不同作用。自然语言意图要由作者确认,工具负责把差异呈现出来。
3. 能执行至少要过三道确定性检查
配置能够解析成 JSON,只完成了第一步。
第一层是结构:列表是否写成了字符串、规则编号是否重复、数值是否有限、字段名有没有拼错。这些问题可以直接给出错误,不需要模型解释。
第二层是能力:规则引用的证据来源是否注册,当前部署是否启用。一个合法的 runtime 名称,并不能证明这个任务实际提供了运行材料入口。能力清单应来自当前任务和部署配置;如果只是手写一份与真实系统脱节的清单,检查器也会得出错误的可达性判断。
第三层是有限范围内的逻辑:对于同一个指标、同一种单位、同时成立的闭区间要求,区间交集是否为空。比如延迟必须在 0—100 毫秒,又必须在 200—300 毫秒,这两条要求无法同时成立。
自然语言放在第四层审查。文案出现“任一”,结构却要求全部,值得提醒作者,但 “不能漏掉任一材料” 恰恰可能是在表达全部要求。所以这类线索输出 REVIEW,不能直接当成逻辑错误。

图3 能确定的问题直接报错,文字线索保留为审查提示。
这一划分也决定了诊断级别:
| 级别 | 例子 | 默认处理 |
|---|---|---|
| ERROR | 未注册来源、必需入口未启用、区间交集为空 | 阻止发布 |
| WARN | 替代入口部分不可用,但仍有可行路径 | 显示提示,允许继续 |
| REVIEW | “任一”文案与全量要求疑似不一致 | 人工确认;严格模式阻止发布 |
WARN 与 REVIEW 的区别在于处理责任:前者已经知道问题是什么,后者仍需要确认作者意图。
4. 用一个足够小的规则语言开始
配套示例使用 Python 标准库,定义两个顶层字段:capabilities 表示当前配置声明的来源能力,rules 保存规则。下例是完整的 valid.json:
{
"capabilities": {
"code": true,
"ci": true,
"runtime": true
},
"rules": [
{
"id": "r1",
"text": "代码与一种运行材料",
"all_of": ["code"],
"any_of": ["ci", "runtime"]
}
]
}
它表达的门槛是:代码必须有,同时 CI 与运行材料至少有一种。空的 any_of 表示没有替代组要求;两组同时为空会报错,因为这份教学语言要求每条规则至少声明一个证据入口。
这里的 all_of、any_of 是本例的字段,元素为来源名称。它们借用了布尔组合的语义,但不是 JSON Schema 的子模式语法,也不直接替代原项目 Rubric。这样定义可以让读者先看清来源检查,再自行编写项目适配器。
对于能力,示例只保留两个状态:已注册且启用、已注册但未启用。名称不在清单中则是未知来源。检查器分别处理:
all_of中任一来源未启用,当前配置就无法满足整个门槛。any_of中至少一个来源启用,替代路径仍然存在;其他不可用分支产生警告。any_of所有来源都未启用,产生错误。
“已经启用”描述的是配置能力。网络暂时失败、某次没有提交材料,需要在执行阶段记录。

图4 来源可达、材料到齐、行为达标,是三个依次检查的问题。
5. 怎样实现检查,而不把业务问题都交给模型
参考实现的入口是 lint(doc),返回结构化诊断列表。先检查外层形状,再读取内部字段,防止配置写错后检查器自己崩溃。
诊断保留稳定代码和 JSON 路径。例如:
{
"level": "ERROR",
"code": "UNREACHABLE_ALL",
"path": "$.rules[0].all_of",
"message": "当前配置未启用:code"
}
读者在命令行里能找到出错位置,后续做管理页面时也能定位到相应字段。不要仅返回一段无法定位的“规则不合理”。
5.1 来源检查:先识别名称,再判断可达性
假设 a 是必需来源集合,b 是替代来源集合,caps 是能力映射。核心关系很简单:
for name in sorted(a & caps.keys()):
if not caps[name]:
emit('ERROR', 'UNREACHABLE_ALL', path, name)
if b and b <= caps.keys():
enabled = {
name for name in b if caps[name]}
if not enabled:
emit('ERROR', 'UNREACHABLE_ANY', path, '没有可用替代入口')
这是正文简化片段,完整实现另外处理未知名称、部分可达与重复条目。未知名称应当先报拼写或注册错误,避免同时猜测它是否可用。
还有一种不一定错误、却值得提醒的结构:某个必需项也出现在替代组里。只要必需项已经满足,替代组必然满足,它就不再提供额外约束。检查器将其标成
REDUNDANT_ANY,让作者确认这是否符合原意。
5.2 区间检查:只对明确的数值语义求交
参考实现允许一条规则附带 bounds。列表里的条件同时成立,每项声明指标、单位和包含端点的上下限:
"bounds": [
{
"metric": "latency", "unit": "ms", "min": 0, "max": 100},
{
"metric": "latency", "unit": "ms", "min": 200, "max": 300}
]
对同一个指标逐项合并:
lo = max(old_min, new_min)
hi = min(old_max, new_max)
if lo > hi:
# 当前规则中的该指标不存在满足全部条件的取值
emit('ERROR', 'EMPTY_INTERVAL', path, '闭区间交集为空')
相邻区间 [0, 1] 和 [1, 2] 可以在 1 处相交,因此不能把 lo == hi 误判成冲突。同名指标单位不一致时,先要求显式换算,不能直接比较 100 毫秒和 1 秒的数字大小。
当前实现只检查单条规则内的同指标区间,不支持条件分支、跨规则推导或任意表达式求解。例如两个区间分属不同条件分支时,不应直接求交;扩展这类能力需要先增加分支语义,再调整检查算法。
5.3 文案检查:一个有意保留误报的例子
参考实现的语义提示很窄:文字含“任一”、all_of 至少有两项、any_of 为空时,输出 TEXT_HINT。
它会提示“提交任一材料”,也会提示“不能漏掉任一材料”。后者并不矛盾,这个反例被写进测试,确保提示不会被升级为确定性错误。
如果后续引入模型,适合让它提出“哪句文案与哪个字段可能不一致”的审查建议,并由作者选择。自动把 AND 改成 OR 会放宽验收条件,工具不能代替业务方作这个决定。
6. 把“缺材料”留在运行时
静态检查完成后,还需要一个很小的证据门槛函数:
missing = sorted(set(rule['all_of']) - set(present))
alternatives_missing = (
bool(rule['any_of'])
and not (set(rule['any_of']) & set(present))
)
ready = not missing and not alternatives_missing
函数接收已经检查过的规则与本次有效来源集合。调用方负责确认材料非空、格式有效,再把来源交给它。ready 表示可以继续检查内容;不能直接映射成项目通过。
尤其是“刷新保存”这样的要求,日志存在和目标行为成立之间还有一段距离。来源门槛只能要求提供运行材料,材料是否包含刷新后的观察,需要由对应测试、人工或语义评审继续判断。
这样,用户遇到的结果也更容易解释,规则配置出错时找规则作者;材料没到齐时说明需要补什么;材料到齐但行为不满足要求时,再给出具体失败原因。
7. 先跑起来,再故意改坏它
下载发布包,进入 示例代码 目录。代码使用 Python 3.10 及以上标准库,不需要安装第三方依赖。
python -m unittest -v
python rulecheck.py valid.json
python rulecheck.py broken.json
python rulecheck.py review.json --strict
valid.json 输出空诊断列表,退出码为 0。broken.json 将必需的 code 能力关闭,输出 UNREACHABLE_ALL,退出码为 1。review.json 构造“任一”文案与全部要求的疑似冲突:普通模式保留诊断但退出码为 0,加 --strict 后退出码为 1。
接着做三个修改,比复制完整代码更容易理解设计:
- 在
valid.json里只关闭 ci。观察为什么它是警告;再关闭 runtime,观察为什么升级为错误。 - 加入前文的两个延迟区间,得到
EMPTY_INTERVAL;把第二个区间下限改成 100,检查端点相交为何能够通过。 - 将文案改为“不能漏掉任一材料”,同时要求 ci 和 runtime。工具仍提示复核,你需要判断这次是否真有冲突。
最后可以自己加一种检查:某类验收标准必须声明责任人。先写缺少责任人和已声明责任人的两组测试,再扩展结构与诊断。这个练习会迫使我们回答:责任人是全局字段,还是每条规则的字段?漏填应该阻断发布,还是仅提醒?工程规则的价值正来自这些明确决定。
8. 验证结果:检查器与原项目分别测
本次验证分成两组,结果保存在发布包中。
第一组从当前本地 review.py 提取 _evidence_gaps 与它使用的可选来源常量,执行五个确定性场景。提取后使用仅包含两个列表字段的对象作为输入,检查原函数返回值。
| 场景 | 实际缺口 |
|---|---|
| ci、runtime 全部必需,只提供 runtime | ci |
| ci、runtime 任选一种,只提供 runtime | 无 |
| ci、runtime 任选一种,均不提供 | ci / runtime |
| report 放在必需项,未提供 | 无 |
| report 单独放在替代项,未提供 | report |
这五个场景确认的是该函数的字段解释,不包含接口采集、模型判断或线上行为。核验记录保存了三个相关源码文件的 SHA-256,可在版本变化后重新比较。
第二组包含 35 个测试方法:基础检查器 20 个,原项目适配与清单治理 15 个,全部通过。新增测试覆盖权重、独立白名单、角色旁路、可选来源的字段差异、任务归属、配置漂移、清单过期和未来时间。另完成基础命令行四组退出码检查;适配器命令行的新鲜清单通过、配置变更阻断也在测试内执行。

图5 确定性测试验证规则行为;测试数量不代表真实配置中的缺陷检出率。
测试里保留了几种不应报错的输入:替代组有一条路径可用、两个闭区间端点相交、同时提供两种替代材料。这些正向对照很重要,否则只让错误样例变红,很容易做出一个拒绝所有配置的“严格检查器”。
35 个测试方法中包含参数化子场景,其中一个差分测试枚举 6 类证据组合 × 3 种角色 × 16 种材料集合,共 288 组输入。acceptance 分支与保存的原项目 _evidence_gaps() 比较,theory/reflection 按原预检的角色旁路行为比较;288 不再重复计入测试方法总数。该测试检查证据门槛,不评价后续模型输出和分数聚合。
9. 这套方法与 Schema、Policy Testing 有什么关系
JSON Schema 适合表达字段形状和组合约束,其官方文档也展示了永远不能满足的模式组合。[1] 本文借用了显式组合的思路,再加入“来源在当前部署中是否可用”的能力检查。生产配置较复杂时,可以先用 JSON Schema 完成结构校验,避免不断扩张手写分支。
Open Policy Agent 提供策略测试框架,允许围绕策略编写通过与拒绝案例。[2] 它给本文的直接启发是:规则应像代码一样接受测试,而不只由提交样例被动触发。本文的工具保留为标准库脚本,便于看清检查过程;已有 OPA 等策略基础设施的项目,可以将相同的行为案例放进现有测试流程。
这种对照也帮助确定实现边界:小型声明规则可以先做结构、能力与简单逻辑检查;出现跨对象关联、复杂条件分支和大量规则复用时,再考虑成熟的策略语言或约束求解工具。仅为了检查两个证据列表,没有必要先引入一整套规则平台。
10. 接入原项目:先做语义适配,再运行检查
基础 DSL 的目标是讲清检查过程。原项目已经有角色、权重和兼容行为,接入时需要单独的适配层。配套 integration.py 提供 adapt_rubrics():输入经过原模型规范化的字典、已验证的能力清单和任务编号,返回适配记录与诊断。
| 原字段或行为 | 适配结果 | 保留的语义 |
|---|---|---|
id / task_id |
记录编号与任务校验 | 重复编号、跨任务混入均报错 |
required_evidence |
去除原 _OPTIONAL_EVIDENCE 后进入 all_of |
被忽略的声明输出 IGNORED_REQUIRED 警告 |
evidence_any_of |
原样去重后进入 any_of |
不剔除可选来源;若包含它们,提示人工复核 |
evaluation_role |
role 与 blocks_delivery |
acceptance 检查门槛;theory/reflection 保留但不阻断交付 |
weight |
保留在适配记录 | 规范化后的值必须是大于等于 1 的整数;不在此计算分数 |
criterion / pass_condition |
作为门槛的说明文字 | 其余文案及视觉字段保留在 original 中 |
| 原项目允许空门槛 | 适配层允许 | 不套用基础 DSL 的 EMPTY_GATE 限制 |
核心转换可以写成下面的伪代码:
validate_id_task_role_weight_and_whitelist(rubric)
record = preserve_original_fields(rubric)
record.gate.all_of = set(rubric.required_evidence) - OPTIONAL
record.gate.any_of = set(rubric.evidence_any_of)
record.blocks_delivery = rubric.evaluation_role == "acceptance"
if record.blocks_delivery:
lint_gate(record.gate, allow_empty=True)
else:
retain_for_existing_learning_or_reflection_path(record)
这里的权重校验有明确输入前提:调用方先执行原项目的 Rubric(...).model_dump()。适配器要求得到整数,拒绝 0、负数、布尔值、浮点数和字符串;它不复刻 Pydantic 对原始输入可能执行的类型转换。
白名单也与能力清单分开。前者回答某个名称是否属于当前源代码契约,后者回答当前配置是否启用它。拼错的 runtmie 不会因为被塞进能力清单就自动变成合法来源。完整名单及可选来源集合按本次源码版本固定在适配器中。
对于 theory/reflection,仍检查编号、权重、字段和证据名称是否合法,但不把来源未启用变成提交门槛错误。这与原预检的非阻断分支一致。original 保留其说明和视觉配置,便于原有学习反馈路径继续读取;后续分数聚合仍使用原有逻辑。
项目升级后,这份适配关系可能过期。verify_contract.py 对 Rubric、角色枚举、可选来源常量和预检函数计算不含位置属性的 AST 摘要,与随包保存的契约比较:
python verify_contract.py D:/path/to/project/ai_engine
匹配时输出 SOURCE_CONTRACT_MATCH;变化则返回非零,要求复核适配器。这个检查故意偏保守,源码结构或 Python AST 版本变化也可能触发复核。首次接入建议使用本次核验的 Python 3.12。不要在每次发布时自动重写基准摘要,否则“记录变化”会替代“审查变化”。
11. 从部署配置生成能力清单,并拒绝过期版本
一份长期手填的 capabilities 容易与系统脱节:
{
"revision": "demo-v1",
"task_id": "task-demo",
"collectors": [
{
"source": "code", "enabled": true},
{
"source": "ci", "enabled": true},
{
"source": "runtime", "enabled": true}
]
}
这里的 collectors 包括自动采集和人工材料入口。生成器遍历已注册名称,未出现在配置中的来源默认为关闭;未知来源、重复来源或非布尔开关直接报错。用户无需另外维护第二份能力布尔表。
这份 JSON 是示例定义的部署快照格式。实际接入时,应由应用加载采集器和提交入口的同一份任务配置导出它,并将这份配置随应用发布。示例没有自动发现现有网站的部署能力;只有“规则门禁”和“应用启动”读取同一份有效配置,摘要绑定才有意义。
在示例代码目录运行:
python integration.py generate --config deployment.example.json --manifest manifest.json
python integration.py check --config deployment.example.json --manifest manifest.json --rubrics rubrics.example.json --strict
生成的清单同时记录:任务编号、部署修订号、部署配置内容 SHA-256、适配器版本、两份检查器源码摘要、生成时间和派生能力表。
检查阶段重新读取当前配置,而不是相信清单自报的版本号:
caps = verify_manifest(manifest, current_deployment_config)
records, diagnostics = adapt_rubrics(rubrics, caps, task_id)
现在修改 deployment.example.json 的任意采集器开关,即使忘记更新 revision,旧清单也会因内容摘要不同被拒绝。修改检查器代码、换任务、修改派生能力表,同样会触发不一致。默认有效期为 24 小时,时间来自检查进程;未来时间也会被拒绝。有效期可由调用方按发布节奏配置,不接受清单自己延长寿命。
发布时,应在同一份构建产物内生成、核验和使用配置,避免“检查完旧配置,随后替换成新配置”。应用启动时再比较一次,有助于发现发布包与目标实例的错配。对于运行中热更新,需重新生成和核验清单,再切换配置版本。
摘要绑定解决的是漂移与错配,不能证明采集器实现确实正常
一个声明启用但无法执行的采集器,仍需启动自检或运行时健康检查发现
网络失败也不能靠静态清单预测。

图6 规则、部署配置与检查器版本一起进入发布记录;旧清单不能跨配置复用。
12. 保持工具边界清楚,扩展时先写语义
这份实现包括一个有限 DSL 和一个固定版本的原项目证据预检适配器。它们的能力范围如下:
| 问题 | 当前实现 |
|---|---|
| 来源全部必需、至少一个替代入口 | 支持;空组语义由 DSL 和原项目适配分别定义 |
| 权重下限、角色、来源白名单 | 适配层校验并保留;不计算总分 |
| 同规则、同指标、同单位闭区间冲突 | 基础 DSL 支持;不执行测量值验收 |
| 清单生成、版本绑定、有效期与漂移阻断 | 支持;配置快照由接入方从有效部署导出 |
| 跨规则推理、条件分支、否定、优先级 | 不支持;未知字段报错,不能悄悄忽略 |
| 自然语言逻辑正确性、功能是否达标 | 不自动判定;文字线索进入人工审查 |
比如未来要增加“满足 A 时必须提供 B”,先定义 A 如何观测、未知状态怎么处理、它与现有 all/any 如何组合,再增加对照测试。直接添加一个 condition 字段而不定义执行语义,只会把这篇文章讨论的问题再制造一次。
同样的方法可以迁移到质量门禁与审批流程:先找出规则引用了哪些能力,明确组合关系,再让规则本身接受测试。回到开头那个提交者已经提供运行材料却仍被追问 CI 的案例,第一步就是确认规则要求的究竟是什么,以及当前部署是否提供了满足它的路径。
参考与延伸
- JSON Schema:Boolean JSON Schema combination。用于核对组合语义与不可满足模式示例。
- Open Policy Agent:Policy Testing。用于对照规则行为测试的工程方法。
项目案例:AI Tutor Engine(信科院智能助手)。本篇于 2026 年 11 月 2 日核对本地源码;配套检查器为独立教学实现,五个原函数场景与三十五个参考实现测试分别记录。配套文件包含源代码、三份配置样例、测试输出、源码摘要,
资源仓库链接github于此W1413580489/agent-engineering-blog: 技术博客配套工程:文章、架构图、可运行示例与回归测试。
來源:阿里雲開發者社區 AI · developer.aliyun.com