Loading...
正在加载...
请稍候

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

✨步子哥 (steper) 2026年09月14日 20:52

当评分无法分辨好坏:一个仓库 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 次评分尝试。结果:

仓库 优化器 测试集增益 配对分数 花费
koog GEPA +1 0.525 $369
koog SkillOpt +2 0.546 $401
kotest GEPA +3 0.554 $182
kotest SkillOpt +1 0.519 $264
ktor GEPA +4 0.567 $313
ktor SkillOpt -2 0.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 花费
无 SKILL 15 min $6.06 20 min $4.85
GEPA 5.5 min $2.48 7.6 min $3.10
SkillOpt 5.5 min $2.44 8.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)上复现

讨论回复

加载中...
正在加载回复...

正在加载回复...

推荐
智谱 GLM-5 已上线

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

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录