项目:离线分层评测流水线

项目目标

使用Windows 11、PowerShell 7和Python 3标准库,完成一条不联网、不调用模型、无需密钥的发布评测链路:

  • 严格验证dataset、rubric和predictions的JSON契约;
  • 检查唯一ID、train/development/test隔离、family级泄漏和冻结测试覆盖;
  • 运行标签、工具、证据和禁止动作的确定性graders;
  • 计算混淆矩阵、precision、recall、F1、均值、p95和切片差距;
  • 用固定种子的配对bootstrap报告候选相对基线的不确定性;
  • 让关键安全/隐私case的BLOCK优先于总体均值;
  • 输出包含评测器版本、摘要编码格式的完整SHA-256证据摘要、仅供展示的短指纹和稳定CLI退出码,并用43项测试验证。

解释边界

数据只有6个冻结测试case,全部是合成教学内容。结果不能估计任何真实系统的生产表现;小样本切片差距不是公平结论,估算成本不是供应商账单。

项目文件

环境准备

脚本没有第三方依赖,可直接使用Python 3。若要练习标准隔离流程,把虚拟环境建在vault之外:

$venvDir = Join-Path $env:TEMP "ai-agent-engineer-evaluation-venv" # 把临时虚拟环境放在系统临时目录,不污染知识库。
python -m venv $venvDir # 创建隔离解释器;本例不需要联网安装依赖。
& (Join-Path $venvDir "Scripts\Activate.ps1") # 激活刚创建的环境,使后续 python 指向其中的解释器。
# 本项目无第三方依赖,因此无需 pip install。

从同时包含 docs/ 与 .website/ 的项目根目录进入示例目录:

$projectDir = (Resolve-Path -LiteralPath 'docs\评测体系\03-项目与自测\examples').Path # 解析示例目录为绝对路径,避免相对路径随终端位置变化。
Push-Location -LiteralPath $projectDir # 临时进入示例目录,最后会用 Pop-Location 恢复。

所有Python命令都使用-B,避免在知识库生成__pycache__或.pyc。

严格契约

Dataset

每个case含id、family_id、split、slice、critical、教学输入、期望二分类标签、期望工具、禁止动作和证据要求。验证器要求:

  • 顶层和case字段精确匹配,拼错或多余字段不能静默忽略;
  • ID唯一,train、development、test三类都存在;
  • 同一family_id只能位于一个split,阻断会话/文档/改写样本泄漏;
  • 发布门只能读取frozen_test: true的测试case。

Rubric

rubric固定positive label、baseline release、测试split、质量/切片/延迟/成本门和bootstrap的样本数、种子、置信水平。数值必须可表示为有限的 Python float;过大的 JSON 整数和非有限值都会成为契约错误,布尔值也不能冒充0/1数字。

Predictions

每个release必须恰好覆盖冻结测试ID。结构化记录含预测标签、工具、动作、是否有证据、时延和估算成本。缺失case、未知case、重复release/动作、数据集版本不匹配都会返回契约错误。

观测来源与被测制品边界

predictions_*.json是预先写好的合成教学fixture,不是本脚本实时运行Agent后取得的事实。这里的tool、actions和evidence_present只是供grader练习的受控观测值;验证器只能检查它们与契约是否一致,不能证明它们来自真实工具、环境状态或未被候选篡改。

生产harness应把模型可生成的文本/调用请求与受信任执行器产生的工具回执、Trace和最终环境状态分开采集;评测输入还应关联不可变的release manifest(代码、Prompt、检索索引、工具、策略和运行配置),并由受控存储、访问控制和需要时的签名/证明保护。本项目没有运行SUT、抓取这些观测或验证制品来源,因此不能把PASS当作真实发布批准。

决策证据

stdout JSON 会带evaluator_version、evidence_digest_format、64位evidence_sha256和其16位前缀evidence_fingerprint。完整摘要绑定评测器版本、dataset、rubric、predictions与candidate release;短指纹只用于人工阅读。本示例的格式是python-json-sorted-utf8-v1:它将值以json.dumps(ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)序列化,再UTF-8编码并计算SHA-256,适合相同实现复算;原始无效 UTF-8 字节、JSON 转义后的孤立 surrogate、非有限值和不能表示为有限 float 的数字都会在读取/验证阶段以受控契约错误拒绝。该字节表示及其格式标签由固定 golden vector 测试锁定,供本仓库的发布门和监控项目复核同一教学 profile。它不是跨语言的JSON规范化标准。跨发布审批、Canary或后续监控窗口必须传递完整摘要及其编码格式,并验证相同字节;真实跨语言哈希或签名需求须另行约定并测试明确规范(例如 RFC 8785 JCS),不能把本 Python 教学格式误称为 canonical JSON。摘要仍不能代替签名、来源验证或人工批准。交接边界见 离线到线上证据交接与回归闭环。

Grader与指标

每个测试case有四个确定性检查:

  1. 二分类标签是否正确;
  2. 工具是否与期望一致;
  3. 动作是否避开全部禁止项;
  4. 需要证据时是否明确提供证据。

四项全部通过才记case pass;每项也保留在报告中用于定位。标签预测单独形成TP/FP/TN/FN混淆矩阵,并计算precision、recall、F1。时延报告均值与nearest-rank p95;成本仅是fixture中的教学估算。

候选与baseline在同一批测试case上配对,逐case pass差值进入固定种子bootstrap。该区间用于演示不确定性,不会把6个手工case变成总体随机样本。

门禁优先级

决策顺序明确写在代码和测试中:

  1. 任一critical safety/privacy case失败先BLOCK;
  2. 关键切片、总体pass rate或precision/recall/F1不达硬门也BLOCK;
  3. 切片差距、均值/p95时延、估算成本或bootstrap下界触发REVIEW;
  4. 全部通过才PASS。

回归fixture故意让candidate的总体pass rate为5/6≈0.833、F1为1.0,都不低于冻结质量门,但它执行了禁止的refund_order,因此首要理由仍是关键安全case失败,结果必须BLOCK。

运行正常候选

python -B .\evaluate_agent_outputs.py # 运行正常候选;-B 禁止写入 __pycache__。
if ($LASTEXITCODE -ne 0) { throw "PASS fixture exit code mismatch" } # PASS 场景必须以 0 成功退出。

预期:action=PASS,baseline pass rate约0.667,candidate为1.0,配对差约0.333,95%教学bootstrap区间为[0.0, 0.667],candidate p95为1010 ms。输出是stdout JSON,不写文件。

运行关键回归

# 反引号必须是行尾最后一个字符,故说明单列在命令前,不能在续行后追加注释。
python -B .\evaluate_agent_outputs.py `
  --predictions .\predictions_regression.json `
  --candidate candidate-regression # 注入关键安全回归 fixture,并给候选一个可读的发布标识。
if ($LASTEXITCODE -ne 1) { throw "regression fixture exit code mismatch" } # BLOCK/REVIEW 是受控拒绝,因此退出码应为 1。

预期:action=BLOCK,首要理由包含test-safety-negative。退出码1表示评测成功执行并拒绝发布,不是脚本崩溃。

运行非法契约

# 这里故意换成非法数据集,验证输入契约错误不会被误判为普通指标失败。
python -B .\evaluate_agent_outputs.py `
  --dataset .\eval_dataset_contract_error.json # 指向含跨 split family 泄漏的 fixture。
if ($LASTEXITCODE -ne 2) { throw "contract-error fixture exit code mismatch" } # schema/数据错误必须稳定返回 2。

预期stderr指出split leakage。CLI退出码契约:

  • 0:PASS;
  • 1:REVIEW或BLOCK;
  • 2:文件、JSON、schema、版本、覆盖或参数契约错误。

四模式测试

普通、禁用assert优化模式、warnings-as-errors及二者组合必须分别通过:

python -B -m unittest -v test_evaluate_agent_outputs # 在普通解释器模式发现并详细运行全部单元测试。
if ($LASTEXITCODE -ne 0) { throw "normal tests failed" } # 任一测试失败都立即中止该验证脚本。
 
python -B -O -m unittest -v test_evaluate_agent_outputs # 用 -O 复跑,确认生产校验不依赖会被移除的 bare assert。
if ($LASTEXITCODE -ne 0) { throw "optimized tests failed" } # 优化模式也必须保持同样的测试结果。
 
python -B -W error -m unittest -v test_evaluate_agent_outputs # 将警告升级为错误,暴露弃用或资源问题。
if ($LASTEXITCODE -ne 0) { throw "warnings-as-errors tests failed" } # warning 不能被静默带过。
 
python -B -O -W error -m unittest -v test_evaluate_agent_outputs # 组合最严格的解释器与告警条件。
if ($LASTEXITCODE -ne 0) { throw "optimized warnings-as-errors tests failed" } # 四种模式都通过才说明基础回归可靠。

再做不生成缓存的语法检查:

python -B -c "from pathlib import Path; [compile(p.read_text(encoding='utf-8'), str(p), 'exec') for p in map(Path, ('evaluate_agent_outputs.py', 'test_evaluate_agent_outputs.py'))]" # 只在内存编译两个文件,做不产出 .pyc 的语法检查。
if ($LASTEXITCODE -ne 0) { throw "syntax check failed" } # 语法错误必须在离开示例目录前失败。
Pop-Location # 恢复执行前的工作目录,避免污染后续终端命令。

动手练习

  1. 新增一个candidate-review fixture,只让p95超过软门,验证结果为REVIEW和退出码1;
  2. 将同一family_id放进development和test,解释为何即使文本不同也属于泄漏;
  3. 新增一个critical privacy case,并先写“总体均值仍高但必须BLOCK”的失败测试;
  4. 让某release缺一个测试case,确认验证器拒绝,而不是用剩余样本计算更好看的分数;
  5. 将按行bootstrap改成按family_id抽样,并说明在当前每个测试family只有一个case时结果为何相同。

自测题

  1. 为什么candidate F1为1.0仍可能被安全门BLOCK?
  2. 为什么训练、开发和测试之间要按family而不是随机行隔离?
  3. 分母为零时precision为何应标为未定义而非0?
  4. 固定随机种子保证了什么,又没有保证什么?
  5. 证据指纹为何不能代替数据来源真实性或访问控制?

掌握检查

  • 能解释dataset、rubric、predictions三个版本化契约;
  • 能从混淆矩阵手算precision、recall和F1;
  • 能说明均值、p95、切片差距与bootstrap区间的证据边界;
  • 能证明关键安全/隐私失败优先于总体质量均值;
  • 能在四种解释器/告警模式下运行43项测试与三类CLI场景,并解释完整摘要、短指纹、摘要编码格式和退出码的不同用途;
  • 能把真实项目仍需补充的人工校准、在线灰度、隐私与治理列出。

项目边界与下一步

本项目验证的是离线契约、确定性grader、指标和决策优先级。它没有运行被测Agent,亦未从可信执行器抓取工具回执或最终环境状态;没有调用人工或模型judge,没有连接真实Trace、用户、监控后端、账单或线上实验,也不能给出公平/合规结论。它的本地JSON排序序列化也不是跨语言规范化或签名协议。真实发布还需结合 评测报告、审计与治理、证据交接与回归闭环 与 运行监控 的灰度guardrail。

参考资料