返回博客

构建实录 / 2026-06-29

把 AI 测试做成一套框架:9 层分层、证据链和可复现门禁

AI Testing Lab 总览

AI Testing Lab 不是"某个项目的测试记录",是一套可复用、可复现、可讲清楚的 AI 测试框架。它把 AI 测试的分层、方法、runner、报告和证据链沉淀成一个仓库,能接到任意 AI 应用、RAG/Agent 系统、或 vibe coding 生成的项目上。

规模:aitl_lab 包约 1.13 万行 + runner 脚本约 1.79 万行(合计约 2.92 万行 Python,按非空行计)、210 份报告、9 层测试分层、7 个测试对象——从 video-parser 起步,已扩到 earthnest、boss-new-job、rgb-zh,以及 kosen-infra 和 lerobot 数据交付;第 7 个 embodied-policy-sim 目前只完成建档与 fixture 契约,尚未开跑。这篇讲它解决的核心问题:AI 系统的质量怎么测才不是"跑一下看着还行"。

一、为什么 AI 测试需要单独一套框架

传统测试测的是确定性:输入 A,断言输出 B。AI 系统的麻烦在于输出是概率的、主观的、还会被对抗输入攻破。一个 AI 应用真正的风险面比传统 Web 宽得多——生成质量、幻觉、检索引用、工具调用行为、prompt 注入、系统提示泄露、模型漂移,这些用 assert x == y 测不了。

所以框架的一句话是:先做产品主链路和服务端基线,再用规则断言 + LLM-as-judge + 红队攻击 + trace 审计覆盖 AI 特有风险,最后用 CI gate 和报告把结果沉淀成可复现证据。

二、九层测试分层

框架把一个 AI 系统的测试面拆成九层,每层有明确目标和产物:

测什么
产品 E2E用户主路径:输入、提交、状态流转、下载、失败重试
API/服务端接口契约与权限:鉴权、参数校验、幂等、越权、错误码
LLM 评测生成质量:格式、相关性、幻觉、稳定性
RAG 评测检索与引用:命中、引用覆盖、无答案拒答、向量命名空间隔离
Agent 测试工具调用:工具选择、参数安全、失败恢复、目标劫持、供应链
安全红队对抗防御:prompt 注入、系统提示泄露、PII/secret 泄露、资源耗尽
回归门禁版本不退化:模型/prompt/gate 变更后的指标漂移
代码审计AI 生成代码:架构、异常、依赖、权限、可测试性
Vibe Coding 项目AI 快速生成的项目是否真实可交付

关键在于分层意味着分工:哪些用 E2E/API 测、哪些用 eval/redteam/trace 测,在风险建模阶段就分清楚,而不是一锅炖。

三、把主观质量变成可失败的门禁

"模型答得好不好"这种主观判断,怎么变成 CI 里能红能绿的东西?框架的做法是把它转成可执行指标:格式合规率、相关性、hallucination_rate、稳定性、ASR(攻击成功率)、trace pass rate、回归差异。每个都能设阈值、能失败、能挡住合并。

但这里有个陷阱框架专门防:单次 LLM judge 的结果不能直接下结论。 用一个模型给另一个模型打分,judge 本身会漏报误报。所以框架要求 judge 结果必须可校准——用人工标注或构造正反例验证 judge 的漏报率和误报率,不只相信模型打分。这条写进了项目非目标里:不用单次 LLM judge 直接下结论,关键结论必须有校准、复测或人工复核边界。

四、证据链:每次测试都留可复现的痕迹

框架的一个执念是"可复现"——一次测试不能只留一个"通过/失败",要留完整链条:

数据集 → 命令 → 日志 → trace → metrics → 报告 → 失败明细 → 风险等级 → 复测建议

aitl evidence 把这个做成了能治理的东西:证据 API、趋势观测、owner/敏感度分级、diff --fail-on-regression 回归检测、证据关系图;每次执行落一个不可变的 Run bundle。为什么较真到这个程度?因为 AI 测试最容易变成"这次跑过了但说不清为什么"——没有证据链,一个 eval 结果就是一次性的运气,不是可信的结论。

真实性也有硬约束:接了公开 RGB benchmark 跑全量 baseline(noise@0.4 准确率 0.9467、拒答 0.4467),证明框架能接标准评测集而不是只跑自造样本;5 条真实外部证据以脱敏 JSON 导入,明确"模板样例不能当真实证据"。

五、可复现性:换台干净机器也能跑

框架级工具最容易犯的病是"只在作者机器上能跑"。所以有个 fresh_checkout_smoke.py:在一个独立的临时 Git 仓库里验证 doctor、plan、profile Run 和 dashboard 全流程——证明这套东西 clone 到干净机器上也立得住。

CI 集成也讲成本纪律:offline gate(fixture harness)每次 push 跑,GitHub Actions 的 offline job 只调一次 gate catalog;full eval 手动或定时跑,避免每次提交都烧模型的钱。默认 CLI 不调真实模型,真实 smoke 单独标记为 gated。public demo 有脱敏门禁,真实密钥和生产数据一律不进仓库。

六、测试有效性:覆盖率照不出"摆设测试"

这是最近补上的一层,起因是一个 vibe coding 特有的问题:AI 不但写代码,还写测试。

一个覆盖率 100% 的测试,完全可能只是把代码"跑过一遍",却什么都没断言。它长得像测试、进得了 CI、报告一片绿,但改坏任何一行代码它都不会响。原有的两层质量信号都照不出这个:coverage 只数覆盖了多少行,CRAP 只排复杂度风险。

要判断"保护这次改动的测试到底有没有效",需要第三种指标:变异测试。方法很直接——把源码逐点改坏(变异),跑测试看抓不抓得住。存活的变异就是测试盲区,mutation score = 被杀死的变异 / 可评估的变异,这是目前最直接量化"测试断言有效性"的指标。

方法论借鉴自一批公开的变异测试工具仓库(mutate4javamutate4goclj-mutateAcceptance-Pipeline-Specification),只借鉴方法,实现全部 Python + ast 自研,不复制任何代码。同一批仓库里的重复检测、AI swarm 编排、架构依赖分析等,与当前缺口无关,明确没有纳入。

做了三轮:

第一轮,探针引擎。 scripts/mutation_probe.py,纯标准库 ast,七类算子逐点变异,内存 exec 跑测试判 killed / survived / errored 并算分。全程离线纯内存——不写文件、不碰目标仓。

第二轮,接入编排。 把孤立的探针接进 iteration_check.py,成为"一次迭代判断"的第四路信号(前三路是变更影响、代码风险、冒烟声明)。mutation score 低于阈值(默认 0.6)判 review缺省未评估的情况与 coverage N/A 同规则处理,绝不当成低风险

第三轮,首次真实项目试跑。 把带变异信号的 iteration_check 跑在 kosen-infra 上——注意:变异这一路按设计仍是 gated 未评估,真正在真实项目上跑通的是编排与影响映射。即便如此,这次试跑还是暴露了自检照不出的两个问题:迭代工具在 Windows 控制台的中文输出乱码,以及跑目标测试时子进程解释器没固定、导致一批子进程找不到依赖的假失败。两个都修了,并写进维护纪律。

最关键的是自检里那组对照实验。同一段 classify 代码,配两个测试,真实跑完整变异:

测试行为mutation score
强测试断言具体返回值0.857
弱测试只调用,不断言0.0

而这两个测试的覆盖率可能同样是 100%。 这组数字就是这层存在的全部理由。

它也兑现了这个项目一直坚持的一条原则:检测器必须先被验证。 探针的核心链路(变异 → 执行 → kill/survive → 算分)在自检里就完整真跑,不依赖任何外部环境——这正是对上一轮 CRAP 门禁"没有真实 coverage 就形同虚设"那个教训的回应。

边界必须说清楚,否则这一节就成了另一个"看着很绿"的门禁。 对真实项目的 pytest/unittest 套件跑变异需要目标环境授权,目前仍是 gated:到今天为止,还没有任何一个真实项目 case 产出过真实的 mutation_score,探针唯一的完整实跑证据来自自检。摊开说,四路信号里现在真正在真实项目上工作的只有影响映射一路,CRAP 与变异两路都还是 gated 的死信号——这是这套东西当前最大的未解问题,也已经登记在项目的 P0 阻塞清单里。

七、它是框架,不是一个项目的测试

这个仓库最核心的设计是通用性:video-parser 只是第一个测试对象,用来证明框架能落到真实项目,但它不是终点。接一个新项目的流程是标准化的——aitl intake 接入、aitl case create 建用例、按九层分层做风险建模、复用现成的 eval/redteam/rag/agent runner、出报告、接 CI gate。目前已接了 7 个不同形态的对象——从 AI 应用到基础设施仓库(kosen-infra)、机器人数据交付(lerobot),其中第 7 个 embodied-policy-sim 还停在建档与 fixture 契约阶段、未开跑。形态差异越大越能证明框架的通用性。

外部工具想接进来也有规矩:必须接入本仓库的 dataset/runner/report/gate 约定(比如 Ragas 是通过 adapter 接的),不是简单堆一个工具就算数。这条约束保证框架不会退化成"一堆各说各话的测试脚本"。

做完这套最大的体会:AI 测开的难点不在会用哪个 eval 工具,在于把一次性的、主观的、概率的模型输出,变成可复现、可校准、可挡合并的工程化结论。 分层是为了分工,指标是为了可失败,证据链是为了可信,校准是为了不被 judge 骗——这四件事凑齐,AI 测试才从"跑一下看看"变成真正的质量保障。