建立基线,用对照实验验证改动
固定协议、配对任务并重复运行,用完整观测解释分差与波动。
建立基线,用对照实验验证改动
改了一句提示词后任务成功了,只能说明这一次成功。要判断新版本是否更好,还需要知道旧版本在同一任务上会怎样、各自运行了多少次,以及有没有被漏掉的失败。
这篇建立 baseline/candidate 成对比较。先看队列是否完整,再看质量差异;不把缺失结果当成零分,也不把它悄悄删掉。
独立示例位于仓库 columns/coding-agent/examples/evaluation-optimization/,固定 Pi 相关依赖为 1.5.0。先在该目录执行 npm install,下文命令也从此处运行。离线样本不需要密钥;真实模型入口另行配置,不会由测试自动触发。
实验协议先于结果
协议记录任务、模型标识、两组配置、重复数、夹具内容和教学评分器版本,并计算 SHA-256。摘要帮助识别配置变化,不能证明运行产物可信,也不能消除远程模型服务更新与负载波动。
本例每个任务按重复序号交替执行两组:第一次 baseline 在前,第二次 candidate 在前。它降低某些顺序影响,不是严格随机化,更不能让两次模型生成共享同一个随机过程。
一个 arm 对应一条有效观测
完整实现位于 src/comparison.ts:
export interface Arm {
caseId: string
variant: "baseline" | "candidate"
repetition: number
}
export type Observation = Arm & { protocol: string } & (
| {
state: "scored"
passed: boolean
budgetExceeded?: boolean
durationMs?: number
estimatedCost?: number
}
| { state: "infra-error"; reason: string }
)
const key = (arm: Arm) => JSON.stringify([arm.caseId, arm.repetition, arm.variant])
export function plan(caseIds: string[], repetitions: number): Arm[] {
if (
caseIds.length === 0 ||
new Set(caseIds).size !== caseIds.length ||
caseIds.some((id) => !id) ||
!Number.isSafeInteger(repetitions) ||
repetitions < 1 ||
repetitions > 10
) {
throw new Error("任务标识或重复次数无效;教学入口最多重复 10 次")
}
return caseIds.flatMap((caseId) =>
Array.from({ length: repetitions }, (_, index) => {
const variants: Arm["variant"][] =
index % 2 === 0 ? ["baseline", "candidate"] : ["candidate", "baseline"]
return variants.map((variant) => ({ caseId, variant, repetition: index + 1 }))
}).flat(),
)
}
export function compare(expected: Arm[], observed: Observation[], protocol: string) {
if (!protocol || new Set(expected.map(key)).size !== expected.length) {
throw new Error("协议或计划无效")
}
const expectedKeys = new Set(expected.map(key))
const issues: string[] = []
for (const row of observed) {
if (row.protocol !== protocol || !expectedKeys.has(key(row))) {
issues.push("出现协议不匹配或计划外 observation")
}
}
const valid: Array<Extract<Observation, { state: "scored" }>> = []
for (const arm of expected) {
const rows = observed.filter((row) => key(row) === key(arm))
const row = rows[0]
if (rows.length !== 1 || row?.state !== "scored" || row.protocol !== protocol) {
issues.push(`${key(arm)} 缺失、重复、未评分或基础设施异常`)
} else if (
typeof row.passed !== "boolean" ||
[row.durationMs, row.estimatedCost].some(
(value) => value !== undefined && (!Number.isFinite(value) || value < 0),
)
) {
issues.push(`${key(arm)} 指标无效`)
} else {
valid.push(row)
}
}
const summaries = (["baseline", "candidate"] as const).map((variant) => {
const rows = valid.filter((row) => row.variant === variant)
const knownCosts = rows.flatMap((row) =>
row.estimatedCost === undefined ? [] : [row.estimatedCost],
)
return {
variant,
scored: rows.length,
passRate:
issues.length > 0 || rows.length === 0
? null
: rows.filter((row) => row.passed).length / rows.length,
costKnownTotal:
knownCosts.length === 0 ? null : knownCosts.reduce((sum, value) => sum + value, 0),
costAvailable: knownCosts.length,
}
})
const baseline = summaries[0].passRate
const candidate = summaries[1].passRate
const flakyCases = [...new Set(valid.map((row) => row.caseId))].filter((caseId) =>
["baseline", "candidate"].some(
(variant) =>
new Set(
valid
.filter((row) => row.caseId === caseId && row.variant === variant)
.map((row) => row.passed),
).size > 1,
),
)
return {
complete: issues.length === 0 && expected.length > 0,
issues,
summaries,
flakyCases,
liftPercentagePoints:
baseline === null || candidate === null ? null : (candidate - baseline) * 100,
}
}缺失、重复、协议不匹配、基础设施异常和无效指标都会阻止 headline。预算超限若属于预先定义的任务约束,则记录为有评分的失败;请求断开导致无法判断,则保持运行异常。
官方报告按评测集合处理 blocked pair。本例为单套教学队列,任意不完整 arm 都会阻止整套 headline,两者范围不同。不要将这里的 JSON 文件当作官方 vitest-evals 报告。
分差是什么,什么还不知道
liftPercentagePoints 是候选成功率减去基线成功率后的百分点差。75% 到 100% 是增加 25 个百分点,不是“提升了 25%”的相对变化;也不意味着所有任务都变好。
应同时查看逐任务的得失,以及同一变体同一任务多次成功/失败的波动。flakyCases 只说明观察到结果变化,没有给出原因。一次重复既不能证明稳定,也不足以计算可靠的置信结论。
至少成功一次与每次都成功回答不同问题。报告更多尝试时,应说清关注的是搜索机会还是可靠完成,不用“多试几次总能成功”的数字替代单次成功率。
运行与产物
npx tsx src/live.ts prompt 2 > live-report.json这会实际运行十六个 arm。没有预算时先用 offline 入口检查报告逻辑;其预设分差只属于示意数据。JSON 重定向使用直接脚本入口,避免 npm 启动日志混入报告。
官方文档实验的 treatment 不只是文档文本:without_docs 还改变了提示中的文档路由。本系列的 prompt/tool/context 试验分别只修改一个配置维度,并由测试核对,但环境仍不可能完全无噪声。小样本正差应作为继续验证的线索,而非确定因果收益。
依据与版本
官方本地快照为 8efd314fa,evals 包标记 1.3.0;本系列示例另按发布包 1.5.0 核验。官方 evals 是 private 工作区包,教程使用自定义教学协议,不冒充稳定公共评测 SDK。