@x-otto/eval
v0.0.1-alpha.0
Published
otto 的自建业务基准(RFC-316 L2 层):跑一套固定任务,判分,与基线对比,报出回归。
Downloads
0
Readme
@x-otto/eval
otto 的自建业务基准(RFC-316 L2 层):跑一套固定任务,判分,与基线对比,报出回归。
为什么它长这样
上一代 @x-otto/eval 因零生产接线被整包删除(RFC-067 M115-03)——代码没错,是没人跑、
没有东西消费它的输出。所以这一版的第一原则是先建消费回路,再堆 case:
- 报告是给人看的产物,回归判定直接映射到进程退出码(
run检出回归即 exit 1,可当门禁)。 - 全部逻辑只走 otto 的公开入口(headless CLI 的 JSON 输出、trace JSONL), 引擎里没有一行「为评测而存在」的分支。
用法
# 跑整套 case,与基线对比(检出回归 → exit 1)
node packages/eval/dist/cli.js run
# 把当前表现固化为基线(必须给理由)
node packages/eval/dist/cli.js baseline update --reason "M1 initial baseline"
# 查看已有基线(按 模型 × 套件版本 分键)
node packages/eval/dist/cli.js baseline list
# 少跑几轮做冒烟
node packages/eval/dist/cli.js run --runs 1
# 从一个真实会话提炼 case 骨架(见下「case 从哪来」)
node packages/eval/dist/cli.js extract <sessionId>两条评测轴
| | Tier B(能力) | Tier A(引擎) |
| -- | -- | -- |
| 测什么 | agent 含模型的能力变强/变弱 | 引擎有没有被写坏 |
| 成本 | 真实 token | 零 |
| 确定性 | 统计性(n≥3) | 确定性 |
| 入口 | cli.js run | tier-a-cli.js run |
| 适合 | nightly / 大改动后 | pre-push 门禁 |
Tier A 把录制好的模型输出回放给真实引擎 + 真实 fs 工具,模型侧被完全固定,
引擎侧的行为漂移会落在三个断言面上:工具调用序列、文件产物 sha256、turn 终态。
合法的引擎行为变更走显式重录:tier-a-cli.js run --update(并在提交里说明理由)。
node packages/eval/dist/tier-a-cli.js run # 秒级门禁,mismatch → exit 1
OTTO_PREPUSH_EVAL=1 git push # 挂进 pre-push
bash scripts/eval-nightly.sh # Tier A + Tier B 一起跑(nightly 用)case 从哪来
三个渠道,优先级从高到低:
- 真实事故:复盘完顺手固化成 case,等于免费买了永久保险。
- 真实会话提炼:
extract <sessionId>从 trace 生成骨架(prompt 原文、工具序列、 触碰过的文件清单)。刻意不自动生成判据、不自动脱敏——自动生成的判据只会是 "输出像上次一样"(那是快照不是判据),不可靠的脱敏比明示风险更危险。骨架里带 TODO 和脱敏清单,人工补完才可用(生成出来是judge: liveness,不进质量分)。 - 手工新写。
默认 case 根目录是 <cwd>/evals,可用 --evals-root 指定。
case 怎么写
一个 case = evals/cases/<id>/ 目录:
evals/cases/my-case/
case.yaml # prompt、判分方式、轮数、超时、token 预算
fixture/ # 可选:工作区初始状态(每次运行复制到临时目录)
verify.sh # judge=script 时的判分脚本,exit 0 = passprompt: |
Fix the failing test in this directory.
judge: script # script | golden | liveness
runs: 3 # 单次运行不作结论(RFC-316 规则 3)
timeoutMs: 240000
maxTokens: 200000 # 成本护栏:超限即停后续运行
golden: # 可与 script 并存,两个判据都要满足
toolSequence: [read, edit]判分优先级(诚实边界)
可执行判据(verify.sh exit 0) > golden 断言 > liveness(跑完没崩)liveness 的 case 不进质量分——没有判据就说没有,不许用「跑完了」冒充「做对了」。
报告底部会明确列出有多少 case 属于这一档。
script 与 golden 是正交判据,可同时使用:前者判产物对不对,后者判过程守不守规矩
(例如"改文件前必须先读")。只有其一时,两类回归各有一半会蒙混过关。
三态与基线
- 每次运行落
pass/fail/infra_error三态之一。限流、网络错误、供应商 5xx 归infra_error,不进 pass 率——否则一次 429 会伪装成能力回归。infra 占比超 20% 时整次 运行标 invalid,拒绝参与基线对比。 - 基线按
(modelId, suiteVersion)分键。换模型 = 换基线键,宁可报「没有基线」, 也不拿 A 模型的分数去审判 B 模型。改 prompt 或判据会改变suiteVersion,同样另起一条曲线。 - 回归判定两个信号:套件 pass 率相对基线跌超 15 个百分点,或某个 case 从「全 pass」翻到 「全 fail」(case flip)。n=3 的统计功效只够抓这个量级的回归,抓不到细微退化—— 这是首期接受的边界,不假装有统计显著性。
测试
npx vitest run packages/eval/testsconsumption-loop.e2e.test.ts 是这套体系的自证:它用真实 case 目录、真实 verify 脚本、
真实工作区隔离,模拟「变更前 / 变更后」两次运行,验证整条链路确实能把引入的退化报出来,
并在没有退化时保持安静。
