从0打造编码Agent 阅读 + 实验 ≈ 35 分钟 页内实验 ×4
A9 评测:怎么知道它好不好? 第 1 课

修好 test_parse_date 一次,算它会修 bug 吗?

跑一次不算数
同一套配置只换 seed,贯穿任务跑三次:一次 ✗、两次 ✓。只跑一次,结论取决于碰上哪一次。重复 k 次后有两个问法:k 次里至少成功一次(pass@k,讲能力)、k 次全部成功(pass^k,讲可靠);合并样本 6 / 7 算出 pass@3 = 99.7%、pass^3 = 63.0%。可 3 次全过时 p 的 95% 区间下界只有 0.29;拿全榜平均成功率套公式,TB 4.0 榜首行的 pass@5 算出 98.7%,榜单实测 71.2%——每个任务的成功率不一样,公式要先按任务算、再平均,Harbor 的 pass_at_k.py 就是这么写的。
❓

本课一问:本课的 agent 修好了一次 test_parse_date。这能说明它「会修这个 bug」吗?要跑几次、怎么算,才算说清楚?

这一课先不碰:分数里哪部分是模型、哪部分是 harness(第 2 课);失败在哪一步(第 3 课);怎么接真实评测(第 4 课);pass@k 无偏估计的推导(LLM 课 C12 岔路 B1 已经推过,这里直接用)。

从已有出发

从已有出发 · 累计数据流 点已学环节看它回答什么
每一轮循环都经过

两个伏笔在这里兑现。A0 第 3 课看过「孪生运行」:同配置、同 seed 跑两次,4 个任务里 2 个在中途分叉——工具输出里差了几个 token(工作区目录名、耗时),后面整条路就变了;那一课的结尾说「想让两次运行可比,要么固定这些东西,要么用多次运行的统计来说话」。模型这一侧本来也在抽样:本课项目的 temperature 是 0.6、top_p 0.95,采样怎么把概率变成一个字,见 LLM 课 C13 的采样岔路。这一课就是「用统计说话」。

先自己设计:跑一次,看成败

最省事的评测:把任务交给 agent 跑一次,测试过了就说「会修」,没过就说「不会」。

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案每个任务跑一次,看测试过没过

同一套配置、只换 seed 的三次运行,并排按轮对齐(A1 的真实录制):

同一套配置,只换 seed:三次运行并排 请求模型回复工具调用工具结果harness 注入

数据:本课项目 miniagent 的真实运行(src/data/project/agent-traces.json,2026-09-26 导出;模型 Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数取自服务端 usage;思考截取约 600 字、正文与工具输出约 900 字(保留头尾)。耗时受本机同时运行的其他任务影响,只能粗看;token 与轮数才可比。

🎯

试三件事:① 停在第 2 轮:三列第 1 轮都调了 list_dir、拿到同样的结果,第 2 轮的请求都是 990 token;分叉出在第 2 轮的回复——s1 先跑测试,s2、s3 先读源文件;② 拖到 s1 的第 6、7 轮:第 6 轮改了第二处 bug,第 7 轮又把同一行改了一遍(+ 1 加了两次),第 8 轮没有重跑测试就收工;③ 看 s2、s3 的倒数第二轮:都是跑过测试、看到通过才停。

改进一:重复 k 次,算两个数

本课项目里,配置与 pd-v2-unlimited 相同、只是轮数上限不同(不设 / 20 / 30)且每次都没碰到上限的运行共有 7 次:A1 的 4 次加上 T6 的 3 次,通过 6 次。把它们当作同一个 agent 的 7 次独立试验,单次成功率的点估计 p̂ = 6 / 7 = 0.857:

同一个 p̂ = 6 / 7,重复 3 次
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
两个公式都假设:各次试验相互独立、每次的成功率相同。Anthropic 原文的例子是 p = 0.75、k = 3:pass^3 = 0.75³ ≈ 42%。
算法模拟器 · 两条曲线由公式现算,空心点由真实 (n, c) 直接算(无偏估计)
同一个 p,两个故事:pass@k(k 次里至少一次成功)与 pass^k(k 次全部成功)

数据:本课项目 T6(2026-09-26 录制,本机 Qwen3-8B-MLX-4bit)与 A1 同配置运行。两个公式都假设各次试验相互独立、成功率相同;无偏估计的写法与 Harbor 0.23.0 的 pass_at_k.py 相同(本页 JS 与 Harbor 自己的函数在 440 组 (n, c, k) 上逐一比对一致)。

🎯

试三件事:① 默认是合并样本 6 / 7:把 k 从 1 拖到 10,蓝线升、紫线降;② 点「Anthropic 的例子」,k 拖到 3,核对 42%;③ 点「贯穿任务 · T6 3/3」:p̂ = 1,两条线都贴在 1 上——三次全过,公式就说「永远都会过」。这显然说多了,下面的「区间」回答它到底说明了多少。

空心点是另一种算法:不先估 p,而是直接从「n 次里成功 c 次」算——从 n 次结果里不放回地抽 k 次,至少一次成功的概率是 1 − C(n−c, k) / C(n, k),全部成功的概率是 C(c, k) / C(n, k)。前者就是 LLM 课 C12 岔路 B1 推过的无偏估计 ;k 不能超过 n。

改进二:k 次也只是 k 次——看区间

3 次全过,p 最低可能是多少?用二项分布反过来问:p 取多小时,「3 次全过」这件事的概率只剩 2.5%?这就是 95% 的精确区间(Clopper–Pearson)的下界:

算法模拟器 · 95% Clopper–Pearson 精确区间(二项分布累积概率上二分求解)
只跑了 n 次、成功 c 次:p 能落在多大的范围里?

数据:本课项目 T6(2026-09-26 录制,本机 Qwen3-8B-MLX-4bit)与 A1 同配置运行。两个公式都假设各次试验相互独立、成功率相同;无偏估计的写法与 Harbor 0.23.0 的 pass_at_k.py 相同(本页 JS 与 Harbor 自己的函数在 440 组 (n, c, k) 上逐一比对一致)。

🎯

试三件事:① 默认 3 / 3:p ∈ [0.292, 1],推出的 pass^3 最低只有 2.5%;② 点「TB 单个任务 0/3」:p 最高仍可能是 0.708——三次都没过,不能说明「这个任务它做不了」;③ 点「TB 合计 0/15」:上界降到 0.218,但这是把 5 个不同任务当成同一个 p 算的,只能当粗略的上界。

⚠
常见误解:「3 次全过,就是每次都行。」

3 / 3 的点估计是 1,可 p 的 95% 区间是 [0.292, 1]。要让「连续全过」把区间下界推过 0.5,至少要连续 6 次。本课项目的合并样本 6 / 7,区间是 [0.421, 0.996]——比 3 / 3 更能说明问题,因为多了 4 次,还包含一次失败。

改进三:不同任务不能共用一个 p

有了 k 次重复,自然想在一个任务集上报一个数:先算全体的平均成功率,再套公式。Terminal-Bench 4.0 官方榜每一行都报了 pass@1 到 pass@5(66 个任务 × 5 次),正好拿来检验这个做法:

公开数据 · tbench.ai 官方榜内嵌 JSON(更新于 2026-09-21,2026-09-27 抓取)
TB 4.0 官方榜:拿平均成功率套公式,和榜单实测的 pass@k 并排

数据:tbench.ai 的 Terminal-Bench 4.0 官方榜(页面内嵌 JSON,更新于 2026-09-21,2026-09-27 抓取);每行 330 个 trial = 66 个任务 × 5 次,pass@k 为榜单自报。每行由谁跑页面没有写(自跑性质未核实)。公式与榜单都由本组件现算 / 转录,没有手写的数。

榜首行 Codex + GPT-6 Astra(max)的 pass@1 是 58.18%:套公式,pass@5 = 1 − (1 − 0.5818)⁵ = 98.7%;榜单实测 71.2%。全榜 27 行,套公式都比实测高,差 13.6 到 35.8 个百分点。

原因就是公式的前提「每次成功率相同」在任务之间不成立:有的任务几乎每次都过,有的几乎从不过。前者再多试几次也不会更好,后者试 5 次也碰不上;用全体平均的 p 去算,等于假设每个任务都是「一半一半」,于是大大高估了多试几次的收益。Anthropic 的原文也写着:「每个任务有自己的成功率——一个 90%,另一个 50%」 。

正确的顺序是先在每个任务上算,再对任务平均。Harbor 0.23.0 就是这么写的——也就是本课项目在第 4 课用的那一版:

Harbor 0.23.0:pass@k 先按任务分组算,再对任务平均 harbor@v0.23.0:src/harbor/utils/pass_at_k.py:32-94需联网 Apache-2.0
32def _compute_pass_at_k_for_trials(trial_results: list[TrialResult]) -> dict[int, float]:
33 task_successes: DefaultDict[str, list[int]] = defaultdict(list)
34
35 for trial_result in trial_results:
36 rewards = (
37 trial_result.verifier_result.rewards
38 if trial_result.verifier_result is not None
39 else None
40 )
41 if rewards is None:
42 task_successes[trial_result.task_name].append(0)
43 continue
44 if len(rewards) != 1:
45 return {}
46
47 reward_value = next(iter(rewards.values()))
48 if not isinstance(reward_value, int | float):
49 return {}
50 if reward_value not in (0, 1):
51 return {}
52
53 task_successes[trial_result.task_name].append(int(reward_value))
54
55 if not task_successes:
56 return {}
57
58 min_trials_per_task = min(len(successes) for successes in task_successes.values())
59 k_values = _eligible_k_values(min_trials_per_task)
60
61 return {
62 k: sum(
63 _pass_at_k_for_task(len(successes), sum(successes), k)
64 for successes in task_successes.values()
65 )
66 / len(task_successes)
67 for k in k_values
68 }
69
70
71def _eligible_k_values(max_k: int) -> list[int]:
72 k_values: set[int] = set()
73
74 k = 2
75 while k <= max_k:
76 k_values.add(k)
77 k *= 2
78
79 k = 5
80 while k <= max_k:
81 k_values.add(k)
82 k += 5
83
84 return sorted(k_values)
85
86
87def _pass_at_k_for_task(n: int, c: int, k: int) -> float:
88 if n - c < k:
89 return 1.0
90
91 product = 1.0
92 for i in range(k):
93 product *= (n - c - i) / (n - i)
94 return 1.0 - product
👆 带下划线的行可以点:点哪行,讲哪行。

本章组件里的 JS 是这几行的逐行移植,和 Harbor 自己的函数在 440 组 (n, c, k) 上逐一比对一致(误差 2×10⁻¹⁶,见 test_a09_pure.mjs)。

极小真数字:0 / 15 与 3 / 3 各说明了什么

本课 agent 在第 4 课的 Terminal-Bench 2.0 上是 0 / 15(5 个任务各 3 次),在贯穿任务上 T6 是 3 / 3。同一个 harness、同一个模型,为什么差这么远?三个常被拿来解释的因素,各自能说明什么、不能说明什么:

因素能说明的不能说明的
k 次重复两边都不是「碰巧一次」:每个 TB 任务 3 次全失败,p 的 95% 上界 0.71;贯穿任务合并样本 6 / 7,下界 0.42为什么失败;k = 3 时单个任务的区间仍很宽,TB 上「完全做不了」与「三成能做」分不开
任务难度parse_date 是本课自己设计的小任务(约 4 步能修好);TB 任务是真实的终端活,作者估专家要 5 / 20 / 20 / 45 / 75 分钟难度标签是给人定的:fix-git 标 easy、专家 5 分钟,本课 agent 3 次都没过
模型规模本机是 8B、4bit 量化的模型;regex-log 三次都在思考里写满 4,096 个输出 token,一次工具都没调本章没有同一 harness 下更大模型的对照,「8B 太小」只是假设;4,096 这个上限是 harness 定的,也在起作用(第 2、3 课)
⚠
常见误解:「pass@k 高,说明它可靠。」

pass@k 只要求 k 次里有一次成功。合并样本的 pass@3 已经是 99.7%,可 pass^3 只有 63.0%——每 3 次无人值守的运行里,约有 37% 的机会至少错一次。要回答「能不能放心交给它」,看 pass^k。

证据三问

问本课的回答
① 它解决什么问题?一次运行只是一次抽样;重复 k 次,把「会不会」拆成能力(pass@k)与可靠(pass^k)两个可以回答的问题
② 数学上为什么成立?两个公式在「独立、同一个 p」的前提下直接推出 ;精确区间由二项分布直接反解
③ 实验上呢?本课项目:同配置换 seed 一次 ✗ 两次 ✓,合并样本 6 / 7 ;TB 4.0 榜 27 行,套公式全部高估

演进地图:你在这里

评测的演进:每一步都被上一步的真数字逼出来
  1. 最直觉的做法:一个任务、一次运行、一个布尔值
    解决了最省事:修好了就说「会修」,没修好就说「不会」。
    卡在同一套配置只换 seed:pd-v2-unlimited 的 s1 / s2 / s3 是 ✗ ✓ ✓;同为 seed 1、只多了一个没碰到的轮数上限的 pd-v2-cap20-s1 又是 ✓。只跑一次,结论取决于你碰上了哪一次。
  2. Anthropic《Demystifying evals for AI agents》· Harbor 的 -k 与 pass_at_k.py
    解决了同配置的 7 次运行过了 6 次:p̂ = 0.857,pass@3 = 99.7%,pass^3 = 63.0%;T6 的 3 次重复全过。
    卡在拿全体平均成功率套公式:TB 4.0 官方榜第一行(Codex + GPT-6 Astra,max)pass@1 为 58.18%,公式给 pass@5 = 98.7%,榜单实测 71.2%:每个任务的成功率不一样。而且重复再多次,也分不出分数里哪部分是 harness。
  3. Vals 统一用 Terminus 2 复测 TB 4.0 · SWE-bench 官方 bash-only 榜统一用 mini-SWE-agent
    解决了同一个模型换 harness 的差距摆上桌:Fable 5.1 在 Claude Code 上 57.88%、在 Terminus 2 上 49.49%;GLM-5.3 41.82% → 25.25%。
    卡在分数只告诉你成没成:本课 agent 在 TB 2.0 上 0 / 15,15 次失败各是各的样子,一个数看不出为什么。
  4. Harbor 的 ATIF 轨迹 · Anthropic 工程博客的四类失败 · 本章写明的标签规则
    解决了15 次失败都落到了带步号的标签上:改完 / 跑完不回头查 7 次,撞上输出上限 4 次,明确声称完成 5 次、5 次没过。
    卡在标签分不清是 agent 错还是环境错:第一次 oracle 冒烟 0 / 5,原因是容器里的 DNS;arm64 探测里 qemu-startup 连参考解也拿 0 分(verifier 现装 curl 时 404)。
  5. Harbor v0.23.0:-a oracle 冒烟 → -a module:Class -k
    解决了参考解 5 / 5 通过、环境就绪之后,再跑自家 agent:5 个任务 × 3 次。
    卡在k = 3、单一 8B 本地模型、5 个任务、amd64 镜像靠 qemu 模拟:结论只在这个规模成立。(→ 课程终点:回到 A0,哪些任务值得用 agent?)

数字口径:本课项目 T6(Harbor 0.23.0 + Terminal-Bench 2.0,本机 Qwen3-8B-MLX-4bit,录制于 2026-09-26)与 A1 的同配置运行;TB 4.0 官方榜(更新于 2026-09-21)与 Vals(更新于 9/22/2026),均于 2026-09-27 抓取;由 a09-eval.json 与 a09-public-2026-09-27.json 构建时现算。

一句话

📌
一次成功说明不了能力,k 次全成功才说明可靠;公式要按任务算,再平均。
🚦

先停在这里:记住三件事——pass@k 升、pass^k 降;3 / 3 的区间下界只有 0.29;任务集上先按任务算再平均。
再深一层:去岔路 B1:能力在按什么速度增长——METR 把「成功率」换成「能做多长的活」,拟合用的也是这一课的思路:成功率随任务变化,要按任务一个个算。

下一个缺口

分数高,是模型强还是 harness 强? → 第 2 课:分数高,是模型强还是 harness 强?

重复够多次,分数稳了;可这个分数属于谁?本课的 agent = Qwen3-8B × miniagent × 一组配置。换一个 harness,分数会变多少?

你可能会问