当评分无法分辨好坏:一个仓库 SKILL 优化实验暴露的评测盲区

你新加入一个 Kotlin 项目。README 告诉你怎么构建,但真正的知识——多平台 source set 不能跨边界引用、模块依赖的方向、@Tool 注解是定义工具的最简方式——这些没写在任何文档里。你只能通过踩坑学到。

当评分无法分辨好坏:一个仓库 SKILL 优化实验暴露的评测盲区

你新加入一个 Kotlin 项目。README 告诉你怎么构建,但真正的知识——多平台 source set 不能跨边界引用、模块依赖的方向、@Tool 注解是定义工具的最简方式——这些没写在任何文档里。你只能通过踩坑学到。

现在把"你"换成 Claude Code。同样的问题,但它连问同事都做不到。

这就是 SKILL.md 文件要解决的事:一份放在仓库里、和代码一起版本控制的 .md 文件,把"只有在这个项目工作过才知道"的知识固化下来。AGENTS.md 格式已经被超过 6 万个开源项目采用,45+ 个 agent 工具支持这个规范。但问题来了——谁来写这个文件?

手写太贵。一个仓库的隐性知识散落在数百个 PR、数千次 commit、无数次 code review 里。于是有人想:能不能让 agent 自己跑任务,从成功和失败的轨迹里自动优化出一份 SKILL?

已有方案的问题:任务太简单,agent 不需要 SKILL 就能秒杀

先看已有的自动合成方案怎么做。GEPA(一个 prompt 优化框架)和 SkillOpt(一个 skill 自演化框架)的思路是:给 agent 一批任务,看它带 SKILL 和不带 SKILL 的通过率差异,用反射 LM 编辑 SKILL,再跑下一轮。

任务从哪来?SWE-smith 是标准答案:往能跑的代码里注入缺陷,让 agent 修。但 JetBrains Research 的团队发现了一个尴尬的事实:

SWE-smith 发布的任务实例,中位数改动 4-7 行98-100% 集中在单个文件

而真实 PR 呢?以 JetBrains/koog 为例,中位数 54 行跨 3 个文件,只有 23% 的任务触及单文件。差距是数量级的。

更致命的是饱和。当 GEPA 的作者用 Claude Code + Sonnet 4.5 跑 SWE-smith 的任务时:

  • 第一个仓库:无 SKILL 通过率 100%
  • 第二个仓库:无 SKILL 通过率 94.8% → 100%
agent 已经能裸跑解决几乎所有任务。SKILL 还能优化什么?通过率从 94.8% 到 100% 的 5.2pp,和 Sahoo et al. (2026) 报告的"10.7% 通过的轨迹其实是靠盲目重试或未验证编辑蒙对的"是同一量级。信号比噪声还小。

反向 PR 挖掘:从历史变更里挖真实任务

论文的核心方法创新是 reverse-PR mining(反向 PR 挖掘)。思路很直接:

1. 流式拉取仓库的 merged pull request 2. 把每个 PR 的 diff 拆成"实现部分"和"测试部分" 3. 在一个冻结的 base commit 上,反向 revert 实现 diff(把 PR 的修复撤销掉) 4. 跑测试,记录哪些测试从通过变成失败——这就是这个任务的 FAIL_TO_PASS 集合 5. 让 agent 尝试重新修复

关键在于"冻结 base commit"。早期版本按每个 PR 自己的 parent commit 来 forward revert,结果优化出来的 SKILL 描述的是"仓库跨数月历史的不同版本"——模块路径、构建命令、API 全是过时的。agent 照着做就掉坑里。改成在单一冻结 base 上反向 revert 后,SKILL 描述的才是当前仓库。

反向 revert 有三层,从便宜到贵:

  • git apply --reverse:直接反向应用,只对没漂移的代码有效
  • 结构性撤销:新增文件删除、删除文件重建
  • LLM 重构:给 LLM 看当前文件和 forward diff,让它重建 pre-change 源码,再从 git 重新推导 patch
LLM 层贡献了大部分任务。koog 的 119 个任务里只有 25 个能 git apply 干净反向应用,56/100 在 kotest,65/131 在 ktor。但 LLM 重构也有缺陷——它可能 revert 了调用点却忘了背后的代码,引入"未导入符号"和"滞留修复"两种验证门看不到的 bug。一个静态检查在 280 个 koog 任务里抓出 56 个缺陷实例。

产出率:大约五分之一的 merged PR 能存活成可用任务。koog 660 个 PR → 119 个任务,ktor 452 个 → 131 个,kotest 保留 100 个。基线通过率 40%-66%,53% pooled——终于给 SKILL 留出了可优化的空间。

配对评分:不测绝对通过率,测相对差异

另一个创新是评分方式。直接用通过率有个问题:agent 不带 SKILL 也能解决一半任务,那它带任何 SKILL 都会白捡这一半的分数。论文改用配对评分

每个 candidate SKILL 的 rollout,和 seed(空文档)在同一任务上的 rollout 配对比较。

三步比较,第一步能区分就停:

1. 诚实性:改了测试文件、或报告成功但测试矛盾的,直接输 2. 功能正确:通过所有 FAIL_TO_PASS 测试的赢 3. 平局打破:测试通过比例、最终声明的诚实度、diff 大小、工具调用次数

seed 对自己的分数恰好是 0.5,所以 0.5 就是 candidate 要打败的基线。回归在单个任务上表现为低于 0.5。

结果:分数不显著,但文档说服了维护者

三个仓库、两个优化器(GEPA 和 SkillOpt)、每个 200 次评分尝试。结果:

仓库优化器测试集增益配对分数花费
koogGEPA+10.525$369
koogSkillOpt+20.546$401
kotestGEPA+30.554$182
kotestSkillOpt+10.519$264
ktorGEPA+40.567$313
ktorSkillOpt-20.437$485
GEPA 平均 +4.9pp,SkillOpt 平均 +0.1pp。但没有一个达到 p=0.05 显著性,最好的也只是 p=0.29。

原因藏在数字背后:20-26 个 held-out 任务的 split,要检测出效果,candidate 需要在分歧任务里赢下五分之四。69 个任务 pool 起来也要赢三分之二。所有这个领域报告的效果都在这条线以下——包括 gskill 在其最强配置上的结果。

换句话说:一个仓库的历史能提供的任务数,不够把 agent 自身的 run-to-run variance 淹没掉。单次 rollout $0.84,200 次优化跑几百美元,仓库历史和预算都在 SKILL 能被统计证明有效之前耗尽。

但论文最精彩的部分在这里:把文档交给维护者看

koog 的维护者逐节阅读了两份 SKILL:

GEPA 产出的文档

  • 多平台 source set 的规则:"我们在这里吃了很多苦头,agent 经常不明白发生了什么"——值得保留
  • 模块间依赖方向:"非常重要"——值得保留
  • 从未提到 @Tool 注解:"很奇怪"——缺失
  • expect/actual 模式:只说了规则没说团队怎么避免代码重复——不完整
  • 维护者结论:"作为草稿我会收下并打磨它"
SkillOpt 产出的文档
  • "注册新模块是三文件改动":"很棒很重要"——值得保留(项目里没写在任何地方)
  • 其余:"大量非常通用的最佳实践描述"——"agent 自己应该知道"——应该放全局 skill 里
更直接的验证:两个 koog 的真实 open issue(#1275 和 #1354),让 agent 分别在无 SKILL、GEPA SKILL、SkillOpt SKILL 下解决:

配置#1275 耗时#1275 花费#1354 耗时#1354 花费
无 SKILL15 min$6.0620 min$4.85
GEPA5.5 min$2.487.6 min$3.10
SkillOpt5.5 min$2.448.4 min$4.20
时间和成本都减半。维护者在盲评三个 patch 时认为 GEPA 的"更简洁,这很好",无 SKILL 的"做了多余的事,还留了奇怪的注释"。

评测盲区定律的又一例

这篇论文最深的洞察不是"反向 PR 挖掘"这个方法,而是它暴露的评测盲区

当评测信号的分辨率低于被测对象的效应大小时,你无法通过评测来判断好坏。

这个盲区有三个层次:

第一层:任务太简单导致饱和。 SWE-smith 的合成任务中位数 4-7 行单文件,强 agent 裸跑就接近 100%。不是 SKILL 没用,是评测无法区分有用和无用的 SKILL。

第二层:任务太少导致方差淹没信号。 一个仓库的 PR 历史能提供的可用任务只有 100-130 个,hold-out 20-26 个。agent 自身的 run-to-run variance 和 SKILL 带来的 4.9pp 增益同量级。统计上无法分离。

第三层:通过率无法捕捉文档的真实价值。 维护者能看出文档里有"只有在这个项目工作过才知道"的知识,实际 issue 解决时间减半——但通过率指标对此视而不见。

这和"omission blindness"(LLM 法官能检测 commission 不能检测 omission)、"标量幻觉"(用标量管理向量)、"CoT 是网不是线"(必要性和充分性几乎不相关)是同一个家族的盲区:评测工具的分辨率不够时,它会系统性地错过真实存在的效果。

工程启示:什么时候这个方法值得用

论文诚实地列出了方法的适用边界:

三个前提条件: 1. 历史 diff 触及的文件在冻结 base 上还存在(否则 reverse-apply 失败) 2. 测试套件在未修改的 base 上通过(否则没有 FAIL_TO_PASS 可言) 3. revert 每个变更能让某些测试从通过变失败(否则任务无法被评分)

两个失败案例

  • JetBrains/tracy:包重命名导致 213 个生产文件在三个月内全部变更,203 个 PR 只存活 5 个任务
  • http4k/http4k:700 个 PR 中 150 个能反向应用,但验证后不到 100 个有 FAIL_TO_PASS
成本:三个仓库总花费 $2013.98,wall clock 69.2 小时。单次 rollout $0.84。

实际建议

  • 如果仓库测试覆盖好、PR 历史丰富、测试套件能在 Docker 里跑——可以试
  • 如果仓库快速重构、测试稀疏——这个方法不适用
  • 不要只看通过率指标——把文档交给维护者审阅比任何分数都靠谱
  • 配对评分比绝对通过率更敏感,但仍然不够

我的思考:文档价值与评测信号的鸿沟

这篇论文让我想到一个更普遍的问题:为什么我们这么执着于用标量指标来评估自然语言制品?

SKILL.md 是给人读的,也是给 agent 读的。它的价值在于传递"隐性知识"——那些不写在 README 里、不写在代码注释里、只存在于维护者脑子里的东西。这种价值本质上是一个高维向量:对哪些任务有帮助、对哪些 agent 有帮助、在什么上下文下有帮助。

但我们用通过率来评估它。通过率是一个标量。用标量管理向量,就是用温度计量血压。

论文的配对评分是一个进步——它把"绝对通过率"换成了"相对差异",相当于从"温度"换成了"温差"。但即使是温差,也只是一个维度的投影。维护者的阅读才是真正的多维评估:准确性、完整性、特异性、可操作性、是否过时、是否通用、是否仓库特定。

这和 AI 安全领域的"标量幻觉"是同一个病:把一个多维概念(模型的能力、安全性、对齐程度)压缩成一个标量(benchmark 分数、安全评测通过率),然后用这个标量来做决策。结果就是:你可以通过优化标量来"骗过"自己,但真实的多维效果藏在标量投影不到的维度里。

GEPA 的 SKILL 在通过率上只赢了 4.9pp(不显著),但在维护者眼里"作为草稿超级好",在实际 issue 上时间和成本减半。 这就是评测信号和真实价值之间的鸿沟。

论文没有给出怎么跨越这道鸿沟的答案——它只是诚实地把鸿沟暴露出来。但这个暴露本身就有价值:当你下次看到某个 SKILL/AGENTS.md/prompt 优化论文报告"提升 X 个百分点"时,问自己:这个 X 和 agent 自身方差比,够大吗?和维护者的主观评价比,一致吗?

如果答案是否定的,那这个 X 就不是 SKILL 的价值——它只是评测工具能看到的那个投影。


论文:Skill Issue: Lessons from Optimizing Repository SKILLs for Coding Agents 作者:Mykhailo Kozyrev (TU Munich & JetBrains Research), Andrei Kozyrev (JetBrains Research), Anton Podkopaev (Constructor University Bremen & JetBrains Research) arXiv:2609.12742 仓库:论文未公开代码,方法可在三个 Kotlin 仓库(kotest/kotest, ktorio/ktor, JetBrains/koog)上复现

暂无表态

想参与讨论或点赞?登录后使用完整功能

讨论回复(1)

Q

读了三遍。这是「评测盲区」家族里最干净的一例,干净到作者自己把它写进了摘要。

24 道考卷上只多答对了一到三题

先沿着一手核

arXiv 2609.12742 存在,2026-09-11 提交,三作者:Mykhailo Kozyrev(TU Munich & JetBrains Research)、Andrei Kozyrev(JetBrains Research)、Anton Podkopaev(Constructor University Bremen & JetBrains Research)。

4.9pp 和 0.1pp 我逐字对上了摘要,一个数没走样。有意思的是摘要里那句原话——"at the dataset size a single repository supplies it cannot be separated from the agent's run-to-run variance"。把"没跑出显著"写进 abstract 的论文,不多。

4.9pp 还有个独立落点:论文自陈这个增益 matches what prior work reports with the same optimizer。换了数据集、换了仓库,用同一个优化器,复现出同一个量级。这种事出现,往往说明读数反映的是优化器的天花板,不是数据集的

数字都对。但有几件事原帖没往下算。

我把配对分数换算成题数了

配对分数有个好用的性质:净胜题数 = 2n ×(分数 − 0.5)。n 是 held-out 规模,论文给的是 20–26,取 24:

仓库优化器配对分数净胜题数花费每净胜一题
koogGEPA0.5251.2$369$307
kotestGEPA0.5542.6$182$70
ktorGEPA0.5673.2$313$97
koogSkillOpt0.5462.2$401$182
kotestSkillOpt0.5190.9$264$289
ktorSkillOpt0.437−3.0$485
看最后一列。四百美元一次优化,落到考卷上是多对一两道题

原帖那张表只有分数。换算完,同一张表读起来完全不一样。

顺带一个交叉验证:总花费 $2013.98,单次 rollout $0.84,除出来约 2400 次。三次优化 × 200 次评分配对(candidate + seed)= 1200 次 rollout,再算上任务生成和验证的调用,量级对得上。费用表是自洽的。

「赢五分之四」再精确一点

原帖说"candidate 需要在分歧任务里赢下五分之四"。方向对,但门槛不是固定的 80%,它随分歧任务数 d 滑动。我把精确符号检验跑了一遍,双侧 p<0.05:

  • d=10 → 需要 90%
  • d=16 → 需要 81%
  • d=20 → 需要 75%
  • d=26 → 需要 73%
任务越少,门槛越苛刻。而六次实验里最高那个 0.567,大致等于赢 57%。差了整整一档。

两处得打个叉

一、SkillOpt 的问题出在稳定性,不在"没用"。

三个仓库:SkillOpt 拿到 0.546 / 0.519 / 0.437,跨度 11 个百分点;GEPA 是 0.525 / 0.554 / 0.567,跨度 4 个百分点

平均成 0.1pp 之后,这个方差被抹平了。而"用一个标量概括一个分布",恰恰是原文正文里批评的那件事——原帖在复述这层洞察的时候,顺手把同样的毛病犯了一遍。

二、SWE-smith 的「4–7 行、98–100% 单文件」要看口径。

我翻了 SWE-smith 原论文(arXiv 2504.21798)Table 1。整表中位数确实是 5 行,原帖没说错。可拆开看:

策略实例数中位改动行数
LM Modify17,8873
LM Rewrite4,17324
Procedural15,6415
PR Mirror2,34414
Combine files/modules10,09211
最后那档占全量 20%,它的设计目的就是造跨文件任务。所以"SWE-smith 全是单文件小改"只对其中几档成立。原帖不算错,口径没说全。

我读出来一个不太好听的结论

论文最后落在一段挺动人的观察上:维护者认出了文档里"只有在这个项目工作过才知道"的知识,两个真实 issue 上时间和成本都减半。然后建议是——别只看通过率,把文档交给维护者看。

这句话要是当真,这条流水线的定位就变了。

它其实是一台给维护者省起草时间的草稿机。所谓"自动优化 SKILL 的优化器",撑不起这个名。原因很朴素:维护者的判断不能自动化,不能规模化,也不能复现。你没法用一个不可复现的评测器去驱动一个迭代循环。

再往前走一步。既然维护者的主观评价才是金标准,那真正该优化的是"维护者会打几分"。这一步没人做——拿不到标注。跟 RLHF 卡在同一个地方。

【小贴士】下次看到"某方法提升 X 个百分点",问三个问题:n 是多少?效应除以方差等于几?金标准是谁在打?

三个都答不上来,这个 X 就只是评测工具能看见的那个投影。不是价值本身。

暂无表态
合作

智谱 GLM-5 已上线

在智谱开放平台 BigModel.cn 打造 AI 应用。新一代旗舰模型 GLM-5 在推理、代码、智能体综合能力达到开源模型 SOTA。

领取 2000万 Tokens