主题
Eval 驱动加固
本课交付结果
你将交付 compareEvaluationReports(base, next, thresholds):比较总体成功率、平均成本、平均步骤和每个 category,输出逐指标 improve/stable/regress 与总体结论;指标按名称稳定排序。
岗位问题
总体成功率稳定可能掩盖 bugfix 类别大幅下降;成功率提高也可能以成本翻倍为代价。上线门禁必须同时观察质量、成本、步骤和分组,且“可接受波动”由显式阈值定义。
前置检查
前置知识快照
先完成 Evaluation Runner:
bash
pnpm --filter @learn-traeai/coding-agent-bootcamp verify:lesson -- 30阈值分别是最大成功率下降绝对值、最大成本相对增长、最大步骤相对增长。
第 30 课生成单版本确定报告,本课比较两个兼容报告并给发布结论。比较函数不重新运行任务,也不挑选基线;它假定 base/next 已绑定同一 suite、fixture、grader 和投影版本。兼容身份不一致时应返回 incomparable,而不是计算一个看似精确的 delta。
成功率本身位于零到一,用绝对百分点变化最直观;成本和步骤量级随任务集不同,用相对变化更可比较。阈值是团队提前声明的回归预算,不是看到结果后调整到刚好通过。任何一个安全关键类别回归,都不能被另一个类别改善或总体平均抵消。
“稳定”不等于完全相同,而是变化落在允许噪声/业务容忍内;“改善”要求方向真正更好且没有任何 regress;“回归”优先级最高。函数输出每项 name/status/delta 与总体 status,让 CI、Markdown 和人类都基于同一机器事实。
原理拆解
mermaid
flowchart TD
A[校验报告身份与阈值] --> B[总体成功率绝对 delta]
A --> C[成本相对 delta]
A --> D[步骤相对 delta]
A --> E[类别并集稳定排序]
E --> F[逐类别成功率 delta]
B --> G[指标 improve/stable/regress]
C --> G
D --> G
F --> G
G --> H{任一 regress?}
H -->|是| I[总体 regress]
H -->|否且有 improve| J[总体 improve]
H -->|否则| K[总体 stable]成功率 delta=next-base,低于 -maxSuccessDrop 为 regress;成本与步骤 delta=(next-base)/base,高于各自上限为 regress,下降为 improve。任何指标 regress 则总体 regress,否则有 improve 则 improve,其余 stable。
代码实验
bash
pnpm --dir bootcamps/coding-agent/labs/39-eval-hardening/starter test
pnpm --dir bootcamps/coding-agent/labs/39-eval-hardening/solution testStarter 应声明 实现评测分类回归判断;Solution 应通过 5 项测试。
失败实现
ts
function compareUnsafe(base: EvalReport, next: EvalReport) {
return next.successRate >= base.successRate ? "pass" : "fail";
}失败实现只看总成功率,成本翻倍、步骤激增、bugfix 从八成降七成都可能通过;没有阈值、delta、类别、兼容检查与诊断。发布者也无法知道失败由哪个指标触发,更容易人工忽略。
尺度正确的两个比较器:
ts
const success = (name: string, base: number, next: number) => {
const delta = rounded(next - base);
return { name, delta,
status: delta < -thresholds.maxSuccessDrop
? "regress" : delta > 0 ? "improve" : "stable" };
};
const lowerIsBetter = (name: string, base: number, next: number, limit: number) => {
const delta = rounded(base === 0 ? (next === 0 ? 0 : Infinity) : (next - base) / base);
return { name, delta,
status: delta > limit ? "regress" : delta < 0 ? "improve" : "stable" };
};关键实现讲解
浮点 delta 统一舍入,避免 0.7-0.8 显示成冗长小数。categories 取基线与新报告并集并排序;新版本缺失旧类别时按 0 处理,从而产生明确回归而非静默忽略。
输入 schema 先验证 successRate/category rate 在零到一,cost/steps 有限非负,thresholds 有限非负。NaN 比较永远为 false,会被错误标 stable;Infinity 除了 base zero 的显式表达外不应来自报告。无效报告属于评测基础设施错误,发布门禁失败,不把它当 Agent回归。
成功率 delta 用 next-base,基线零点八、新版零点七为负零点一。只有严格小于负容忍才 regress,恰好等于阈值属于 stable 或按团队 contract定义;课程使用 strict。正增长标 improve,即使小于阈值,因为阈值只允许下降;若希望最小有意义改善,新增 minImprove,不混用回归预算。
成本与步骤 lower-is-better,delta (next-base)/base。基线十、新版十二是正百分之二十,超过百分之十回归;新版九为负百分之十改善。用绝对二比较不同量级任务没有意义。显示同时给 base/next/relative,避免只看百分比误解实际金额。
base 为零需要显式政策。next 也零 delta零;next大于零相对增长数学无穷,通常 regress。JSON 无法规范序列化 Infinity,报告可用 null/infinite 标志和 status;教学对象可暂存 Infinity但最终渲染层处理。也可用绝对 fallback threshold,必须文档化。
averageCost 的分母要一致。基线只统计 completed、新版统计所有 attempts,比较会失真;报告 identity 包含 aggregationVersion。失败样本 unknown usage 也需相同处理。比较函数不猜口径,只在版本不匹配时拒绝。
categories 取 base 与 next key 并集、稳定排序。旧类别在 next缺失按零会显著回归,防 suite/报告意外漏项;新类别在base缺失按零会显示 improve,但样本身份改变时更合理返回 suite incompatible。Lab聚焦切片不丢失,生产先校验共同任务/类别 manifest。
总体稳定不能掩盖 slice。模型可能从常见 docs任务变好,抵消关键 bugfix下降;category metric独立阈值,任何 regress决定总体。还可切语言、仓库规模、权限风险、工具类型、Provider和难度,但切片数过多会产生小样本噪声,发布门禁选择业务关键预注册集合。
mustPass任务适合秘密泄漏、路径越界、未经测试finish等安全不变量。它们不应只贡献类别成功率,即使总体百分之九十九,一个 mustPass失败也 regress。比较报告列 task-level hard failures与Trace,修复前不能用成本改善覆盖。
总体 status优先级顺序 regress > improve > stable。先 some(regress),再 some(improve);如果反过来,任意成本下降会掩盖成功率退化。结果 metrics按name排序,输入对象key与生成顺序不影响JSON、快照和CI diff。
ts
it("does not let aggregate stability hide a slice regression", () => {
const next = { ...base, categories: {
...base.categories, bugfix: { successRate: 0.7 },
}};
expect(compareEvaluationReports(base, next, thresholds)).toMatchObject({
status: "regress",
metrics: expect.arrayContaining([
{ name: "category:bugfix", status: "regress", delta: -0.1 },
]),
});
});浮点舍入只用于报告与阈值稳定,计算使用原数再固定十二位等精度。0.7-0.8 的二进制噪声不应让 snapshot每次多一串数字;但过早四舍五入成功率到两位会把小回归隐藏。精度与任务总数相匹配,并在schema固定。
阈值配置进入版本控制,按指标/类别声明理由与owner。例如总体drop两百分点、bugfix零容忍、cost增十%、steps增二十%。修改需评审和changelog,比较报告记录thresholdsHash。不能在同一PR同时让代码回归并放宽阈值而无人注意。
阈值有绝对和相对语义,名字必须表达。maxSuccessDrop:0.02 是两个百分点,不是百分之二相对;cost 0.1 是相对百分之十。UI同时显示文字公式与示例,避免团队配置错误。金额还可加最大绝对增长双门限。
统计显著性在样本小或模型随机时必需。单轮八十变七十可能是任务十个里少一个,置信区间很宽;重复seed/bootstrap估计分布,只有回归概率超过阈值才阻断。安全mustPass仍确定阻断。课程先建 practical threshold,再扩展statistical test。
最小样本量防一个任务类别显示零或一百。类别样本不足时 status insufficient_data,发布政策选择阻断或只观察;不能标 stable。报告展示 count与interval,不只 rate。新增类别需先积累基线,再进入硬门禁。
flaky任务要隔离而非反复跑取最好。历史检测不稳定后标quarantined,主成功率是否排除必须对base/next一致,并单独显示flaky count;新增flaky也是回归指标。根治后通过版本化suite重新纳入。
基线选择固定最近正式发布或已批准commit,不用“最近一次成功CI”自动漂移,否则连续小回归每次在阈值内、累计明显下降。报告同时比较release baseline与parent commit,防渐进侵蚀。基线artifact有签名/hash并受保留策略。
两个报告先校验suiteHash、task集合、fixtureHash、graderVersion、projectionVersion、模型/环境兼容策略。完全相同最简单;任务新增时可比较共同子集并另报suite change,但不能直接下同一总体结论。incomparable 不是通过,需人工/新基线批准。
模型或Provider变化有时正是要评测的自变量,可以不同;Node、工具与fixture等环境尽量相同。comparison metadata声明 intendedDifferences,其余identity差异报confounder。这样团队知道成功率变化可归因模型还是测试环境。
成本价格随时间变化。比较token usage更稳定,美元成本用同一priceTableVersion重算base/next;若直接用各运行当时价格,涨价会被误认为Agent回归。报告可同时给实际账单与normalizedCost,门禁选择后者,财务监控看前者。
步骤下降可能是效率改善,也可能跳过test/diff。只有成功率和mustPass不退时才把步骤减少视improve;当前总体规则已让任何质量regress优先。还可加入 verificationRate,防“少走步骤”来自 false completion。指标含义必须和第38课行为证据连接。
平均值会被极端任务影响,报告加入median、P95与per-category。成本P95暴涨即使average稳定也可能破预算;任务级delta列出top regressions。课程Lab保留三项核心,产品扩展时保持metrics稳定命名和排序。
CI输出机器JSON和人读Markdown。JSON含schema/baseHash/nextHash/thresholdHash/status/metrics;Markdown表格给base、next、delta、threshold、status与链接失败任务Trace。命令exit 0表示stable/improve,非零表示regress/incomparable/infrastructure error,并与CLI code文档一致。
PR评论只更新一个bot comment,避免每次push刷屏;摘要先列阻断项,再改善与完整报告链接。秘密/内部路径不进公开PR,Trace需授权。报告hash让评论可验证对应哪个artifact,不能只贴截图。
批准例外需要到期、owner、理由、指标和issue。waiver不修改原comparison status,而是发布决策标approved_with_exception;下次仍显示回归,到期自动阻断。安全mustPass通常不可豁免或需更高级审批。长期例外应转新基线/产品决策并记录ADR。
回滚条件与发布门禁对称。线上canary监控同类成功/成本/steps或代理指标,超过阈值自动停止扩量;线上数据不一定有隐藏grader,要用用户确认、错误率和安全事件。部署report关联离线comparison hash,复盘能追到放行依据。
测试矩阵包括明确成功提升、阈值内stable、三种总体regress、总体stable但category regress、输入key乱序、base zero、无效threshold、缺类别和兼容身份。属性测试随机指标,验证任何regress都使overall regress、排序稳定、delta方向正确。
完整验收base成功零点八/cost十/steps五,next成功零点七回归、cost十二回归、steps七回归;next零点七九/十点五/五点五在阈值内stable;总体零点八不变但bugfix零点七仍regress;metrics name始终排序。
报告归一前还要检查任务覆盖率。next若只运行了容易的一半任务,成功率可能更高但不能与完整base比较;completionRate、missingTaskIds和infraErrors进入身份/门禁。缺任务不是按失败还是incomparable取决于原因,但绝不能静默从分母删除。
同一任务多attempt的聚合要固定。pass@1取第一次,pass@k用规范公式,best-of-k不能与单次base比较;cost累计所有attempt还是只成功attempt也需一致。随机seed列表写入manifest,next使用相同seed或预声明设计,否则方差来源不明。
paired比较比只看总体更有解释力:对共同task逐项标improved、regressed、unchanged,McNemar或bootstrap可利用配对结构。报告列出“base过next败”和“base败next过”的任务,工程师可直接打开Trace。总体delta相同可能来自完全不同任务交换。
失败类型也应比较。successRate稳定但timeout从零增多、permission bypass或false completion出现,是严重回归;failureKind分布和安全事件作为metric/slice。infra错误单列,不让Provider outage降低Agent分数,也不让大量infra error使样本被过滤后虚高。
Eval污染检测比较sourceHash、taskHash与运行顺序;某task在next开始时fixture与base不同,整个报告incompatible。第29课原态和第38课E2E hash为比较提供证据。评测加固不是只写公式,还要确保公式两边测的是同一实验。
grader回归同样危险。测试变松会让next成功率提高,变严会看似退化;graderVersion/hash必须相同或在双跑校准集上建立新基线。参考通过、初始失败、作弊实现拒绝三类样本随grader发布,保证测量仪器没有悄悄变化。
阈值可按风险分层:安全/权限零容忍,核心bugfix小容忍,文档任务允许统计波动,成本按预算限制。总体阈值是最低共同线,类别可更严不能更松到绕过组织上限。配置解析验证类别名存在,拼错不静默新建无效规则。
多目标决策可以用Pareto前沿帮助理解,但发布门禁仍用明确阈值。一个版本质量更高、成本略高且在预算内可improve;质量相同成本下降是improve;质量下降超过阈值无论成本多低都是regress。不要用加权总分让十元节省抵消安全失败。
阈值附近的浮点和样本噪声要有可见“margin”。报告显示距离门槛多少,例如cost增长九点八%、上限十%,虽stable但接近;连续多个PR累积可由release baseline比较发现。只显示绿/红会让团队忽略趋势。
趋势监控保存多个正式报告,但门禁输入仍是明确base/next。折线图展示成功、成本、steps与类别,标suite/模型/阈值变更;不要从图像反向决定这次比较数据。artifact为权威,dashboard可重建。
性能指标的环境归一化也重要。steps/token相对稳定,duration受CI机器负载;若比较duration,使用相同runner或标准化benchmark并看分位数。课程选择steps避免硬件噪声,但真实用户体验仍需独立latency SLO。
成本优化PR应预先声明预期:例如context去重让input token降十五%、成功率不下降。比较报告自动检查目标与所有护栏,未达目标可以stable但不算改进成功。把假设写在PR减少看到任意绿色指标就宣布胜利。
失败任务聚类可发现一个根因影响多个slice,例如Provider schema变化让所有tool任务失败。报告按failureKind/tool/lastAction聚合但保留task链接。比较函数输出基础metrics,分析层再聚类;不要在核心确定函数中引入模型总结。
CI资源失败时重跑规则固定:只重跑标infra且未产生Agent结果的attempt,能力失败不重跑取最好;重跑次数与cost入账。最终报告标哪些task重跑,base/next采用同政策。否则flaky infrastructure可成为选择性洗分工具。
基准数据保密时,公开报告可展示聚合和evidence hash,内部artifact含task详情;发布者不能修改隐藏任务答案。作品集用无敏感课程suite,仍保留失败Trace。透明与防投机按场景平衡,核心结论可机器验证。
门禁服务本身要权限最小,只读报告artifact、写comparison,不执行Agent或修改base;base选择由受保护release tag/配置。PR代码不能同时替换比较器和基线后自我批准,高风险变更需要独立review/workflow。
签名与provenance可用SLSA式attestation:哪个workflow、commit、runner、suite生成report,comparison使用哪些hash和threshold。供应链攻击若替换JSON,hash/signature失败。对课程项目,至少把reportHash写入README/CI并保留原文件。
决策记录写清为何某次stable仍不发布、某次regress为何waive、后续行动与到期。自动status是输入,不替代产品判断;但人工判断不能改写事实。审计看到原metrics、threshold与waiver,能复盘风险承担者。
团队每季度复查指标与阈值是否仍对应用户风险,但不删除历史版本。模型/任务成熟后,旧成功率接近饱和,应增加更难任务而不是继续用无区分度suite;新suite建立平行基线,过渡期双报,避免断裂。
最终设计标准是:任何“版本变好/可上线”的说法都能拆成兼容实验、逐指标公式、预先阈值、关键slice和确定artifact。若结论只能从一个总百分比或主观Demo得出,评测仍未加固,发布也没有可审计依据。
门禁结果还要对应明确行动。improve进入候选发布但仍需安全/构建门禁,stable说明未触发回归预算而非证明有收益,regress阻止并列出最小失败任务,incomparable要求建立合法基线,infra_error要求修测量系统后重跑。状态如果没有处置语义,只是彩色标签。
比较报告的API contract可保持纯函数,文件读取、签名验证、Markdown渲染和CI退出放在外层。纯函数接收已验证对象,易于属性测试和跨环境复现;外层负责artifact identity与安全。不要在compare内部读取“最新报告”路径或当前时间,那会破坏确定性和审计输入。
代码评审时逐公式手算一个例子:成功率八成到七成是负零点一,成本十到十二是正零点二,步骤五到七是正零点四;分别对阈值判断,再组合overall。手算与JSON一致能发现方向、尺度和边界符号错误,比只看五个绿色测试更可靠。
上线前故意构造“总体不变、bugfix下降、docs上升、成本下降”的诱惑案例,预期仍regress。它验证团队真正接受回归优先,而不是在看到便宜后人工放行。发布文化与比较代码必须表达同一风险偏好。
最终证据包括base/next原始报告、兼容identity、threshold配置、comparison JSON/Markdown、CI status与任何waiver。它们共同回答测了什么、如何算、为何阻断/放行、谁承担例外;单独一个status无法承担发布审计。
只有当实验身份相同、公式公开、阈值预先固定、关键切片没有退化且产物可复核时,“没有回归”才是一条工程结论。缺少任何前提,都应停止发布并补证据,而不是用总体平均或主观体验填补空白。
发布结论必须经得起逐指标复算与逐任务追溯。
始终如此。
运行与验证
bash
pnpm --filter @learn-traeai/coding-agent-bootcamp verify:lesson -- 39
pnpm --filter @coding-agent/evaluator test总体稳定但 bugfix 从 0.8 降至 0.7 时,category:bugfix delta=-0.1 且整体 regress。
真实运行输出
text
✓ tests/lab.test.ts (5 tests)
Test Files 1 passed (1)
Tests 5 passed (5)项目 evaluator 的 progression test进一步证明总体相同时类别退化仍阻断。最终 CI还要保存base/next reportHash、thresholdHash和确定排序的comparison JSON。
常见失败与排查
故障案例 1
症状:总成功率不变显示通过,但bugfix、安全任务或某语言明显退化。
根因:只计算aggregate,缺类别并集/mustPass,或缺失类别被静默忽略。
定位:展开task/category结果,对比base/next manifest,构造一升一降保持总体不变。
修复:预注册关键slice逐项比较,缺旧类别显式回归;任何slice/mustPass regress优先总体。
故障案例 2
症状:成本从一到三与从一百到一百零二都按“增加二”同等处理,或base零产生NaN后stable。
根因:lower-is-better指标用绝对差,zero/非有限值无政策。
定位:用不同量级和base zero fixture检查delta、status与JSON序列化。
修复:成本/步骤用相对变化,zero显式处理,输入有限校验;报告同时显示绝对值供理解。
故障案例 3
症状:同一报告因浮点尾数、对象key或输入顺序产生diff;一个改善指标让回归整体变improve。
根因:未规范舍入/排序,overall先检查improve或用最后指标覆盖。
定位:反转category/metric输入,重复序列化,组合一个improve与一个regress。
修复:固定精度和name排序;overall严格 regress→improve→stable优先级,规范JSON计算hash。
课后作业
加入置信区间、最小样本量、模型成本价格表和 flaky task 隔离;输出 CI 可消费的 Markdown 与 JSON 双报告。
验收 Rubric
| 维度 | 通过标准 | 常见扣分 |
|---|---|---|
| 指标 | 质量/成本/步骤/分类齐全 | 单一总分 |
| 阈值 | 绝对与相对语义正确 | 混用尺度 |
| 总结 | regress 优先级最高 | improve 掩盖退化 |
| 确定性 | 舍入且按名排序 | 浮点和 key 抖动 |
总项目增量
总项目包:@coding-agent/evaluator
总项目路径:packages/evaluator/src/compare-reports.ts
总项目验证命令:pnpm --filter @coding-agent/evaluator test
回归门禁完成后,最后一课把架构、Trace、Eval 与 Demo 压缩成可审查作品集。
延伸阅读
- Regression budget 和 quality gate。
- Statistical significance 与 practical significance。
- Slice-based evaluation。
方案对比与工程取舍
单一总分简单易沟通,却隐藏切片和成本;多指标完整但可能造成门禁噪声。选择少量与产品风险直接相关的核心指标与关键slice,其他做观察项。指标增删版本化,不能无限堆图表替代决策。
固定阈值可解释、稳定,统计检验更适合随机模型但复杂且需样本。先以practical threshold和mustPass建立硬边界,再给随机指标加置信区间;二者共同满足才发布。小样本不应被伪精确p值包装。
自动阻断提升一致性,也可能被基础设施故障误触发。报告区分Agent regress、incomparable和infra error;三者都不自动放行,但处理路径不同。人工waiver保留原事实和到期,避免为了赶发布修改数据或阈值。
下一课衔接
端到端能力与回归门禁已经给出可运行、可量化证据。最后一课把架构、威胁模型、Trace、Eval、回归结论和五分钟Demo组织成作品集;文案必须从这些artifact生成,证据缺失时拒绝“先写成果、以后再补”。