Skip to content

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。

代码实验

打开本课 Lab

bash
pnpm --dir bootcamps/coding-agent/labs/39-eval-hardening/starter test
pnpm --dir bootcamps/coding-agent/labs/39-eval-hardening/solution test

Starter 应声明 实现评测分类回归判断;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生成,证据缺失时拒绝“先写成果、以后再补”。

从零实现 Mini Code Agent Runtime