通过率alone是宣传材料,需引入cost-per-resolved-task和置信区间来公平评估不同Agent的真实效率。
你走进同一个会议——一整年都在消耗对编程 Agent 的评审。两个测试框架跑同一套四十个任务。Agent A 打出 62.5% 已解决。Agent B 打出 57.5%。有人已经把 A 放进了 PPT。
然后账单来了,或者 GPU 时长,或者队列延迟。A 把测试运行器循环了,把同一个文件重写了三遍,最后还是需要人工合并。B 失败的任务更多,但凡通过的,就是一次过。
如果你只发布通过率,你发布的只是一份宣传册。按每个已解决任务的成本来排名,置信区间重叠时就拒绝宣布赢家。下面的数字是一个工作示例,不是一份实验报告。
PPT 隐藏的排名
通过率回答的是一个问题:套件变绿了没有。它不回答 Agent 烧了多少工作量才到达那里。它不会告诉你这个三分差距在换一个 seed 后是否还能保持。
热点言论一直在声称编程 Agent 已经超越大多数开发者。在协议能同时展示成本单位和不确定性之前,把这些说法当作不可信的。你需要两者兼备,否则你只是在排名感觉。
如果钱是考量因素,就冻结一份价格表。即使账单为零,也要冻结 token 计数和墙上时间。在免费端点上,token 和秒数仍然是稀缺资源。
账本必须记录什么
不要从仪表盘开始。先从每次任务尝试的一条 JSONL 行开始。如果某个字段缺失,你就没有资格引用排名。
run_id、agent_id、task_id、seedresolved:来自冻结校验器的布尔值,而不是模型的自报告prompt_tokens、completion_tokens、tool_callswall_ms:在厂商的流之外测得price_table_id:以便后续读者看到你用了哪套费率notes:记录人工合并、跳过测试、或测试框架崩溃把货币成本作为派生列保存。把 token 乘以冻结的费率表。如果表里全是零(因为你用了免费模型),你仍然按 token 和墙上时间排名。零美元不等于零工作量。
一份无需 API key 就能打分的示例包
把这份数据标注为说明性的。八行,两个 Agent,四个任务。有意做小。重点是方法,不是排行榜。
{"run_id":"r1","agent_id":"a","task_id":"t1","seed":7,"resolved":true,"prompt_tokens":1800,"completion_tokens":900,"tool_calls":4,"wall_ms":14000,"price_table_id":"local-0"}
{"run_id":"r1","agent_id":"a","task_id":"t2","seed":7,"resolved":true,"prompt_tokens":4200,"completion_tokens":3100,"tool_calls":11,"wall_ms":41000,"price_table_id":"local-0"}
{"run_id":"r1","agent_id":"a","task_id":"t3","seed":7,"resolved":false,"prompt_tokens":5100,"completion_tokens":4400,"tool_calls":14,"wall_ms":56000,"price_table_id":"local-0"}
{"run_id":"r1","agent_id":"a","task_id":"t4","seed":7,"resolved":true,"prompt_tokens":2600,"completion_tokens":1200,"tool_calls":5,"wall_ms":18000,"price_table_id":"local-0"}
{"run_id":"r1","agent_id":"b","task_id":"t1","seed":7,"resolved":true,"prompt_tokens":900,"completion_tokens":400,"tool_calls":2,"wall_ms":8000,"price_table_id":"local-0"}
{"run_id":"r1","agent_id":"b","task_id":"t2","seed":7,"resolved":false,"prompt_tokens":1600,"completion_tokens":800,"tool_calls":3,"wall_ms":12000,"price_table_id":"local-0"}
{"run_id":"r1","agent_id":"b","task_id":"t3","seed":7,"resolved":true,"prompt_tokens":1100,"completion_tokens":500,"tool_calls":2,"wall_ms":9000,"price_table_id":"local-0"}
{"run_id":"r1","agent_id":"b","task_id":"t4","seed":7,"resolved":false,"prompt_tokens":1500,"completion_tokens":700,"tool_calls":4,"wall_ms":11000,"price_table_id":"local-0"}
保存为 sample_ledger.jsonl。如果只数绿色,Agent A 看起来是赢家。先等等,除法还没做。
第一步:派生三个关键列
已解决率仍然有用。但它不够。按 Agent 计算三个数字,然后闭嘴等置信区间出来。
已解决率:绿色数除以任务数
每个已解决的 token 数(TPR):总 token 数除以绿色数。如果绿色数为零,Agent 没有 TPR,它没过这个套件。
每个已解决的秒数(SPR):总墙上时间除以绿色数
每个已解决的费用是可选的。只有在冻结 price_table_id 之后再附上。不要把三月的费率表和九月的运行混在一起。
第二步:本地对账本打分
这个脚本是一个工作示例。它不调用模型。把它指向任何符合 schema 的 JSONL。
# ledger_stats.py — worked example, not a published benchmark
from __future__ import annotations
import argparse
import json
import random
from collections import defaultdict
from pathlib import Path
def load_rows(path: Path) -> list[dict]:
rows = []
for line in path.read_text().splitlines():
line = line.strip()
if line:
rows.append(json.loads(line))
return rows
def group(rows: list[dict]) -> dict[str, list[dict]]:
by_agent = defaultdict(list)
for row in rows:
by_agent[row["agent_id"]].append(row)
return by_agent
def point_estimates(rows: list[dict]) -> dict:
n = len(rows)
greens = [r for r in rows if r["resolved"]]
tokens = sum(r["prompt_tokens"] + r["completion_tokens"] for r in rows)
wall_s = sum(r["wall_ms"] for r in rows) / 1000.0
resolved = len(greens)
return {
"n": n,
"resolved": resolved,
"resolved_rate": resolved / n if n else 0.0,
"tpr": tokens / resolved if resolved else None,
"spr": wall_s / resolved if resolved else None,
"tokens": tokens,
}
def bootstrap_rate(rows: list[dict], rounds: int, seed: int) -> tuple[float, float]:
rng = random.Random(seed)
n = len(rows)
stats = []
for _ in range(rounds):
sample = [rows[rng.randrange(n)] for _ in range(n)]
stats.append(point_estimates(sample)["resolved_rate"])
stats.sort()
lo = stats[int(0.025 * rounds)]
hi = stats[int(0.975 * rounds)]
return lo, hi
def intervals_overlap(a: tuple[float, float], b: tuple[float, float]) -> bool:
return not (a[1] < b[0] or b[1] < a[0])
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("--path", type=Path, required=True)
p.add_argument("--bootstrap", type=int, default=2000)
p.add_argument("--seed", type=int, default=7)
args = p.parse_args()
by_agent = group(load_rows(args.path))
summary = {}
for agent, rows in sorted(by_agent.items()):
est = point_estimates(rows)
lo, hi = bootstrap_rate(rows, args.bootstrap, args.seed)
est["ci95"] = (round(lo, 3), round(hi, 3))
summary[agent] = est
print(agent, est)
ids = list(summary)
if len(ids) == 2:
a, b = ids
overlap = intervals_overlap(summary[a]["ci95"], summary[b]["ci95"])
print("ci_overlap", overlap)
if overlap:
tpr = {k: summary[k]["tpr"] for k in ids}
print("tie_break_tpr", tpr)
print("no_winner_on_rate")
if __name__ == "__main__":
main()
python ledger_stats.py --path sample_ledger.jsonl --bootstrap 2000 --seed 7
在这个玩具包上,A 解决了 3/4,B 解决了 2/4。四个任务上的置信区间很宽。它们会重叠。这就是教训,不是 bug。
第三步:区间重叠时拒绝宣布赢家
四十个任务上三分的差距可能是噪声。四个任务上十五分的差距仍然是噪声。对已解决位做 bootstrap,而不是对营销标题做。
如果任一 Agent 的绿色数为零,不计算 TPR。报告失败,不做排名。
如果已解决率的 95% 置信区间重叠,你就没有通过率赢家。
只有在这种情况下才比较 TPR。每个绿色用更少 token 的赢得平局。
如果 TPR 在 10% 以内,比较 SPR。如果两者仍然平局,公布结果为平局。
这个 10% 区间是一个策略选择。把它写进协议里。不要在看到图表后再调它。
第四步:用测试把这个规则钉死
如果平局决胜只存在于 PPT 里,它会漂移。把它锁在一个你可以在笔记本上跑的测试里。
# test_ledger_stats.py
from ledger_stats import intervals_overlap, point_estimates
def test_overlap_means_no_rate_winner():
a = (0.40, 0.80)
b = (0.35, 0.70)
assert intervals_overlap(a, b)
def test_separated_intervals_are_not_a_tie():
a = (0.60, 0.85)
b = (0.20, 0.45)
assert not intervals_overlap(a, b)
def test_tpr_uses_greens_not_attempts():
rows = [
{"resolved": True, "prompt_tokens": 100, "completion_tokens": 100, "wall_ms": 1000},
{"resolved": False, "prompt_tokens": 900, "completion_tokens": 900, "wall_ms": 9000},
]
est = point_estimates(rows)
assert est["resolved_rate"] == 0.5
assert est["tpr"] == 200 # failed tokens still sit in the numerator? change this if you prefer
把最后那个断言读两遍。示例把所有的 token 都放进了分子,包括失败尝试。这是对"折腾"的惩罚。如果你只除以成功任务的 token,吵闹的 Agent 看起来很便宜。选一种定义。把它写在协议里。不要在排名难看之后改口。
python -m pytest test_ledger_stats.py -q
失败尝试的核算就是全部战场。把浪费也包括进去,否则你排名的就是运气。
决策表:你能打印什么
把表格放在图表旁边。如果某个单元格写着"否",删掉那句话。
免费车道应该放在哪
用非生产 key 运行这个测试框架。评估流量如果和用户共享付费配额,会同时污染账单和延迟列。
披露:本文是 MonkeyCode 产品推广的一部分。MonkeyCode 的免费模型访问和免费服务器选项是一种让小账本远离付费生产流量的方式。把 Agent 指向任何你已经信任的 OpenAI 兼容端点,写 JSONL,用上面的脚本打分。这是这里唯一的产品角色:一个烧评估 token 的地方,而不会把它们混进客户流量。没有模型名,没有配额秀场,没有声称的加速。
一个免费服务器对这个规模就够用了。四十个任务,单进程,一个存 JSONL 的磁盘。如果你每个任务需要多 GB 的沙箱,这条车道是错误的硬件故事。就这么说出来,然后停。
谁不应该用这个
不要用四个任务的包来排名供应商。不要用基于任务的 bootstrap 来代替留出的人类研究。不要把 TPR 变成招聘标准。不要把本地 7 秒校验器和云端排队等待的 Agent 比较,然后声称延迟胜利。
如果你不能冻结校验器,跳过这个方法。如果工具 schema 在 Agent 之间会变化,跳过它。如果一个 Agent 可能调用网络而另一个不可能,跳过它。那些是不同的任务。它们不应该出现在同一个排行榜上。
免费服务器上的墙上时间会有抖动。把 SPR 当作粗筛子,而不是微观基准。如果你需要尾部延迟,配备专用硬件并在协议中说明。
你仍然不能声称什么
你可以声称:在这个冻结的包上、用这个 seed、用这个校验器,Agent B 每个绿色花费了更少的 token,同时通过率区间重叠。你不能声称 B 是更好的工程师。你不能声称这个模型"比大多数开发者更好。"那句话需要一个你没有跑过的人类协议。
把账本连同文章一起发出来。把脚本发出来。把重叠规则发出来。如果读者不能从 JSONL 重放排名,你就没有一个数字。你只有一张 PPT。