从终端输出到分数:解析、判定、指标、续跑
30 秒导读: Agent 跑完之后,屏幕上只是一屏乱糟糟的终端文字。这一章讲清楚三件事—— 怎么把那屏文字解析成"哪个测试过了"、怎么判定这个任务算不算解决、怎么把成百上千 次尝试聚合成一个 benchmark 分数(accuracy / pass@k),以及怎么用锁文件让一次中断的 大跑能安全续上、不被偷偷改过的配置污染。
这一章接在 02-harness-loop(一次 trial 的主线)之后。02 讲"怎么把 agent 跑起来、
怎么跑测试";本章讲"跑完之后那半段"——从终端 pane 文字到 results.json 到排行榜。
1. 先看全景:一次 trial 的"后半段"
一次 trial 到了尾声,harness 手里只有一样东西:测试脚本跑完后终端窗口的整屏文字
(post_test_pane)。从这坨文字到最终分数,要过四道关:
终端整屏文字 post_test_pane
│
▼
① 解析 Parser.parse(pane) ─────► {测试名: PASSED/FAILED} (抽状态)
│ │
│ 解析抛异常 ▼
│ ② 判定 _is_resolved
└──► failure_mode=PARSE_ERROR (全 PASSED 才 True)
│
▼
③ 落盘 TrialResults → results.json
(每个 trial 一份,单一真相源)
│
many trials ▼
④ 聚合 BenchmarkResults
accuracy / n_resolved / pass@k
│
▼
⑤ 上传 S3 整目录 + Postgres 结构化
怎么读这张图: 从上到下是一次结果的生命周期。左边那条岔路是"解析失败"——
测试根本没跑出预期格式时,直接记一个失败原因(FailureMode),不进后面的判定。
各部件一句话职责:
| 部件 | 干什么 | 在哪 |
|---|---|---|
Parser(如 PytestParser) | 把终端文字抽成 {测试名: 状态} | parsers/pytest_parser.py |
Harness._is_resolved | "全部测试 PASSED"才算解决 | harness/harness.py:536 |
TrialResults | 一次尝试的完整档案(含失败原因、token、各阶段时间戳) | harness/models.py:43 |
BenchmarkResults | 把 N 次 trial 聚合成 accuracy / pass@k | harness/models.py:62 |
RunLock 一族 | 锁住配置,保证续跑不漂移 | utils/run_lock.py:185 |
results.json(每 trial 一份) | 单一真相源,续跑时重建全局 | harness/harness.py:1013 |