跳到主内容
创建时间:最后更新:作者:白川

把失败案例变成回归测试

从真实失败提炼固定样本,分层检查,并用完整报告执行可解释的回归门槛。

把失败案例变成回归测试

修好一个失败后,最容易丢掉的是能复现它的输入。下次模型、提示或工具变化时,同一个问题可能再次出现,却没有证据说明它曾经解决过。

回归的目标是保护已经达到的行为。能力探索可以主动寻找新难题,回归集则需要稳定输入、版本化标准和明确的发布判断,不能混成一个永远变动的总分。

独立示例位于仓库 columns/coding-agent/examples/evaluation-optimization/,固定 Pi 相关依赖为 1.5.0。先在该目录执行 npm install,下文命令也从此处运行。离线样本不需要密钥;真实模型入口另行配置,不会由测试自动触发。

从异常轨迹提炼最小任务

保留与失败有关的文件、提示、工具条件和预期,把凭据、个人内容与无关长历史去掉。简化后要确认仍能复现,而不是删掉真正触发条件。

任务 ID 稳定,输入、参考结果和评分器版本可追溯。开发集用于反复调试,保留集用于独立检查,回归集收纳已经确认的重要行为;不要把所有线上轨迹不经筛选就自动当作测试。

分层运行,控制成本

检查层适合什么时候跑本系列入口
确定性评分器与报告
每次相关改动
npm test
离线报告演示
本地理解与排查
npm run eval:offline
小规模真实 Agent
需要验证模型行为时
eval:live
模型评委校准
评委提示或版本变化时
judge:live
更完整代表任务
定期或发布前
扩充并版本化任务 manifest

离线分数不能充当真实模型门槛。测试中构造的“全部通过报告”只是检验 gate 分支,必须与实际运行结果区分。

从原始观测重算发布条件

src/gate.ts 接受真实运行模式的教学报告,检查任务、协议和完整队列,再重新计算质量,不信任文件里预先写好的 passRate。

ts
import { createHash } from "node:crypto"
import { readFile } from "node:fs/promises"
import { type Arm, compare, type Observation, plan } from "./comparison.js"
import { tasks } from "./tasks.js"

export function regressionGate(input: unknown, minimumPassRate = 1) {
  if (!Number.isFinite(minimumPassRate) || minimumPassRate < 0 || minimumPassRate > 1) {
    throw new Error("回归阈值必须在 0 到 1 之间")
  }
  if (typeof input !== "object" || input === null) {
    throw new Error("评测报告格式无效")
  }
  const raw = input as Record<string, unknown>
  if (
    raw.mode !== "live-agent" ||
    typeof raw.protocol !== "string" ||
    !raw.protocolData ||
    !Array.isArray(raw.expected) ||
    !Array.isArray(raw.observed)
  ) {
    throw new Error("门槛只接受完整的真实运行报告,不接受离线预设分数")
  }
  if (
    createHash("sha256").update(JSON.stringify(raw.protocolData)).digest("hex") !== raw.protocol
  ) {
    throw new Error("协议摘要不匹配")
  }
  const protocol = raw.protocolData as Record<string, unknown>
  const repetitions = protocol.repetitions
  if (
    typeof repetitions !== "number" ||
    protocol.graderVersion !== "lesson-v1" ||
    JSON.stringify(protocol.tasks) !== JSON.stringify(tasks)
  ) {
    throw new Error("任务或评分器版本不匹配,请先审查更新")
  }
  const expected = plan(
    tasks.map((task) => task.id),
    repetitions,
  )
  if (JSON.stringify(expected) !== JSON.stringify(raw.expected)) {
    throw new Error("计划队列不完整或顺序变化")
  }
  const report = compare(raw.expected as Arm[], raw.observed as Observation[], raw.protocol)
  const candidate = report.summaries.find((row) => row.variant === "candidate")?.passRate
  return {
    allowed:
      report.complete &&
      candidate !== null &&
      candidate !== undefined &&
      candidate >= minimumPassRate &&
      report.liftPercentagePoints !== null &&
      report.liftPercentagePoints >= 0,
    report,
  }
}

if (process.argv[1]?.endsWith("/gate.ts")) {
  const input = process.argv[2]
  if (!input) {
    throw new Error("请传入 eval:live 产生的 JSON 报告路径")
  }
  const result = regressionGate(JSON.parse(await readFile(input, "utf8")))
  console.log(JSON.stringify(result, null, 2))
  if (!result.allowed) {
    process.exitCode = 1
  }
}
bash
npx tsx src/live.ts prompt 1 > live-report.json
npm run gate -- live-report.json

默认要求 candidate 全通过且没有总体退步。实际项目可以配置自己的门槛,但必须对关键任务、样本波动、缺失指标和人工复核作明确约定。

协议摘要检查的是内部一致性,不是签名或执行证明。能改报告的人可以重新计算摘要,因此生产系统应隔离评分器、隐藏判定材料和产物写权限;不要把报告生成权交给被评 Agent。

基础设施失败与低分分开处理

live runner 的低分是有效数据,不会单独导致进程非零;运行不完整会失败,质量门槛由 gate 执行。这个划分借鉴官方比较评测的设计,CI 应同时检查运行器和 gate 的退出状态。

缺一个 arm 后只用剩余记录生成漂亮分数,会让真实失败消失。波动任务也不能静默删除;应检查数据、评分器和环境,再记录复核或重新设计的理由,而不是无限重跑直到某次通过。

先保持一个可信的小回归集

本系列四任务、两份评委校准标签不足以覆盖完整智能体能力,但足以建立可复验的工作方式。增加真实失败时同步检查评分器是否仍合理、任务分布是否发生偏移、预算是否覆盖新场景。

发布门槛是工程策略,不是线上效果保证。没有执行真实 Agent、校准评委或验证生产环境的部分,继续列为待验证;已有单元测试通过不能替代这些证据。

依据与版本

官方本地快照为 8efd314fa,evals 包标记 1.3.0;本系列示例另按发布包 1.5.0 核验。官方 evals 是 private 工作区包,教程使用自定义教学协议,不冒充稳定公共评测 SDK。