SWE-Bench 原理介绍
Intro
一个“好”的基准测试可以用来反应LMs在现实世界应用的表现,以帮助塑造他们未来的的发展。
但构建一个"好”的Benchmark也是困难的,因为这要求:
- 任务要有挑战性
- 模型预测需要是能够且容易验证的
现有的编码基准测试(如HumanEval)大多涉及自包含问题(Self-contained problem)。自包含问题指的是不依赖外部上下文或复杂依赖关系的问题,所有必要的信息和代码都已在问题本身中完整提供,可以在一个小范围内(如几行代码内)被理解和解决。
一个简单的例子如下:
def factorial(n: int) -> int:
pass这是一个典型的 self-contained problem,只需要编写一个 factorial 函数,而不需要了解其他代码文件或上下文。验证测试的方式也较为简单,即通过检查输出是否等于 factorial(5) == 120 等。
对比现实世界的 Bug 修复任务,这种基准测试往往过于简单,因为真实的软件工程场景中问题往往并不是 self-contained 的。一个 Bug 可能出现在 10000 行代码中的某一行,或者修复一个问题可能需要理解多个文件之间的交互,而有些错误与数据结构或类继承结构有关。
现有 Benchmark 还存在着其他限制,例如许多测试的基准较为陈旧(staleness),覆盖的 Repo 不充分(limited repository coverage),以及高度依赖手动构建实例和环境(heavy reliance on manual effort)。(arXiv:2505.23419v2 [cs.SE] 1 Jun 2025 SWE-bench Goes Live)
为了让评估语言模型面对真实软件工程场景的性能(而非仅仅面向于类似 leetcode 等平台),一个新的评估范式被需要提出,这引出了 SWE-Bench.
SWE-Bench 的任务来源是解决提交到热门 Github Repo 中的问题(例如 Github PR 或者 issue),每个任务都基于一个 bug 描述,并制定一个具体的 commit (base_commit)作为起点,模型被要求生成一个补丁(patch),描述对现有代码库应用的更改(使用git diff格式),然后调用仓库 的测试框架(Test Harness)来评估修订后的代码库。
![[v2-ee9dba791f4df0c80d3e9b0818831641_1440w.jpg]]
使用 SWE-Bench 对主流先进模型进行评估,令人失望的是,模型在现实世界中解决实际问题的能力仍存在较大的改进空间。使用 SWE-Bench (Full)数据集进行评估,SWE-agent + Claude 3.7 Sonnet 能解决 33.83 % 的实例,而对于 RAG + [ChatGPT]() 3.5,在同样的数据集评估下仅有 0.17 % 的实例被解决。
SWE-bench 的数据集是如何构建的?
![[v2-b5dcb551f8c3aad23e4c1f4fad8132f5_1440w.png]]
SWE-Bench 的数据集构建分为三个阶段。
首先是仓库选择和数据抓取(Repo selection and data scraping):开发人员从 GitHub 上流行的 12 个开源 Python Repo 中拉取 PRs,总共得到了 90,000 个数据。抓取流行仓库的原因是因为这样的仓库往往收到更好的维护,也有着更好的测试覆盖率。对于每个 PR 而言,相关的代码库也会被提供,这可以清晰地了解 PR 合并前后仓库的状态。
其次是基于属性的过滤(Attribute-based filtering):合格的 PR 应该符合两个属性条件(Attribute Condition)。一个是它应该解决了至少一个问题(Bug 或者 Feature request),并且它应当修改了项目的测试文件(这意味着 contributor 在 PR 中添加或修改了测试用例)。满足这两个条件的 PR 更有可能构成一个高质量、可评估的基准测试任务。
第三个阶段是基于执行的过滤(Execution-based filtering):对于每一个候选任务(即实例,instance),开发人员先只应用 PR 中的测试相关更改(即只更新 test 文件,而不变动其他代码逻辑),通过记录应用 PR 前后的 test suite 的测试结果,两项筛选被应用到候选池中。通过候选的 PR 必须至少有一个 Fail-to-pass 测试,意味着补丁至少修复了某一个问题。与此同时,安装或运行出错的任务也会在筛选中被过滤,以保证评估的鲁棒性。
下面是一个流程图:
Stage I: Repository Selection
--------------------------------
选取目标仓库:
├── 满足条件:
│ ├── 活跃维护(有定期提交)
│ ├── 有 issue / PR 活动
│ ├── 包含自动化测试框架
└── 输出:候选项目列表(如 pandas、django、scikit-learn)
↓
Stage II: Attribute-based Filtering
-------------------------------------
筛选候选 PR(Pull Request):
├── 满足两个条件:
│ ├── PR 关联并关闭了一个 issue(例如 Fixes #123)
│ └── PR 修改了测试文件(表明提交者编写了验证逻辑)
└── 输出:候选任务列表(candidate tasks)
↓
Stage III: Execution-based Filtering
---------------------------------------
对每个候选任务进行验证:
├── 步骤 1:仅应用测试代码,运行项目测试
│ └── 至少有一个测试失败
├── 步骤 2:再应用 PR 的修复代码,重新运行测试
│ └── 至少一个测试从 fail → pass
├── 步骤 3:检查运行和安装是否正常
│ └── 无构建或执行错误
└── 输出:有效任务实例(可用于模型评估)
↓
数据集的最终组成结构:
instance_id: 实例ID,通常格式为 repo_owner__name-PR-number
//代码基本信息
repo: 仓库名
base_commit: PR 提交之前的代码 commit id,定位代码基线
version: 用于运行的版本号
environment_setup_commit: commit id,安装运行环境的代码基线
created_at: PR 创建时间
//PR基本信息
problem_statement: PR 对应的 issue 内容,也就是要解决的问题
test_patch: 这个 PR 提交的测试 case patch 代码
FAIL_TO_PASS: 这是修复的最终目标,应用修复的 PR 后会通过的测试 case
PASS_TO_PASS: 应用 PR 前和应用后,都应该通过的测试 case
patch: 这个 PR 修复的 patch 代码,相当于标准答案
hints_text: PR 提交之前,github 上对这个 issue 的讨论 comment。可选,如果要上榜单,禁止使用这个数据。这是一个具体的评估筛选实例:
SWE-bench 构建流程(实际样本:pandas-dev/pandas#43124)
Stage I: Repository Selection
--------------------------------
仓库:pandas-dev/pandas
├── 活跃维护 ✔
├── 包含大量 PR 和 issue ✔
└── 使用 pytest 自动化测试 ✔
↓
Stage II: Attribute-based Filtering
-------------------------------------
PR: #43124 - Fix MultiIndex.set_levels with duplicated level names
├── 关联 issue:Fixes #43112 ✔
├── 修改测试文件:pandas/tests/indexes/multi/test_set.py ✔
├── 同时修改核心代码:pandas/core/indexes/multi.py
└── 被纳入候选任务列表(candidate task)
↓
Stage III: Execution-based Filtering
---------------------------------------
任务执行验证:
├── 步骤 1:checkout 到 base commit(PR 提交前)
│ └── 仅添加 test_set.py 中的新测试
│ └── 运行 pytest → 新测试失败 X
├── 步骤 2:应用 multi.py 的修复补丁
│ └── 再次运行 pytest → 失败测试通过 O
├── 步骤 3:确认无构建或安装错误 O
└── 通过执行筛选,生成有效任务实例
↓
最终任务格式:
---------------------------------------
- instance_id: pandas-dev__pandas-43124
- base_commit: <PR 基准提交 ID>
- problem_statement: issue #43112 内容 + 上下文
- patch: multi.py 的修复 diff
- test_patch: test_set.py 的新增测试 diff
- FAIL_TO_PASS
- PASS_TO_PASS通过三个阶段的筛选,原始的 90,000 个 PRs 被过滤为 2,294 个 Instance,这些实例构成了 SWE-bench 的完整数据集。
![[Pasted image 20250711152841.png]]
SWE-bench 评估的输入是如何构建的?
理论上说,SWE-bench 的每个任务输入应当包括:
- 一个 issue 描述 (195 words on average)
- 一个大型代码库 (438K lines on average, or million tokens)
但是大语言模型通常有上下文窗口限制:
- GPT-3.5 ≈ 4K tokens
- GPT-4 ≈ 8K–32K tokens
- Claude 2 ≈ 100K tokens
因此直接将整个代码库喂给模型是不现实的,意味着输入本身必须进行筛选,只有“重要部分”才能够被直接提供给模型。基于这个问题,论文提出了两种策略(Approach):
*(引用自blog.cnbang.net/tech/4178)下面两个检索代码的方法,只是论文中做实验的参考,实际在测试 SWE-Bench 时,各模型会有自己的方法,因为检索代码的准确性对成功率影响也很大,所以榜单上很多是 Agent + 模型 的测试结果,而不是单大模型的。
Claude 3.7 跑分的说明里提到:
SWE-bench Verified: Claude 3.7 Sonnet scores 62.3% out of 500 problems using pass@1 with bash/editor tools plus a "thinking tool" for single-attempt patches — no additional test-time compute used. 70.3% score uses internal scoring and custom scaffold on a reduced subset of problems. Scaffold Deepseek R1 results use the "Agentless" framework. OpenAI results from o3-mini system card cover a different subset of problems with a custom compute.Claude 没有具体说明怎么做检索代码的,官方blog附录里提到在运行环境上是用了极简的方案,只提供了命令和编辑文件的能力,看起来是只把代码仓库目录扔给 LLM,让它自行去做文件搜索。另外有提到 Deepseek 的跑分是基于 Agentless 框架跑的,Agentless 专门介绍说明了如何跑 SWE-Bench,以及具体是怎么做代码检索的。见 Agentless/README_swebench.md。
可以看到 SWE-Bench 测试集其实是比较局限,这里面全是 Python 代码,也基本是纯逻辑代码,不涉及 UI 渲染相关,也不涉及其他语言,很多实际的软件工程场景没有覆盖到,所以即使 benchmark 到 100%,也不代表能解决绝大多数工程问题。
稀疏检索(Sparse Retrieval)
*特别地,指 BM25 检索算法
Sparse Retrieval(稀疏检索) 是一种经典的信息检索方法,其核心思想是基于词项匹配:文档和查询都被表示为稀疏的词项向量,然后计算它们之间的相似度。
BM25 是一种基于概率检索框架的改进算法,特别是在处理长文档和短查询时表现出色。其核心思想是基于词频(TF)和逆文档频率(IDF),同时引入文档的长度信息来计算文档 $D$ 和查询 $Q$ 之间的相关性。
对于一个文档 $D$ 和查询 $Q$,BM25 的得分为:
$$ \text{BM25}(D, q) = \sum_{t \in q} \text{IDF}(t) \cdot \frac{f(t, D) \cdot (k_1 + 1)}{f(t, D) + k_1 \cdot \left(1 - b + b \cdot \frac{|D|}{\text{avgdl}} \right)} $$
- $t$:查询中的一个词项(term)
- $f(t, D)$:词项 $t$ 在文档 $D$ 中出现的次数(Term Frequency)
- $|D|$:文档 $D$ 的长度(以词数计)
- $\text{avgdl}$:语料库中所有文档的平均长度
- $k_1$:控制词频饱和的参数(推荐值 1.2 ~ 2.0)
- $b$:控制文档长度归一化的参数(推荐值 0.75)
- $\text{IDF}(t)$:词项 $t$ 的逆文档频率(Inverse Document Frequency)
其中 $\text{IDF}$ 的计算公式为
$$ \text{IDF}(t) = \log\left( \frac{N - n_t + 0.5}{n_t + 0.5} + 1 \right) $$
- $N$:语料库中的文档总数
- $n_t$:包含词项 $t$ 的文档数量
不难注意到:
- $f(t, D)$ 越大,表示词频高,模型认为该词与文档强相关;
- 然而随着频率上升,这种增益逐渐减弱,极限由 $k_1$ 控制(忽略长度归一化,取$b=0$,$|D|=\text{avgdl}$ ,当 $f(t, D) \to \infty$ 时,$\frac{f(t, D) \cdot (k_1 + 1)}{f(t, D) + k_1} \to k_1 + 1$,表示词频增益最终趋于饱和);
- 文档越长($|D|$ 越大),得分会被归一化降低;
- 稀有词的 $\text{IDF}(t)$ 越高,更具区分度(当 $n_t$ 越小,IDF 趋近于 $\log(N)$,得分也就越高)
SME-Bench 使用 BM25 检索算法,将 issue 描述(problem_statement)作为输入,在代码文件中检索相关性高的文件,然后按照模型的 context 限制,挑选能放下的最多文件作为上下文输入。
然而,与期望不同的是,在多个 context 长度(8K,16K,27K tokens)的试验下,结果发现:
短上下文窗口反而结果更好。
![[Pasted image 20250711160530.png]]
出现这种情况的可能原因包括太长的上下文会让模型难以聚焦(focus),或者 BM25 的排序误差在长窗口内更容易累计噪声。
预知式检索(Oracle Retrieval)
作为稀疏检索的对比分析,研究人员还引入了一个“理想化设置”:
- 查看这个任务的 GitHub PR **实际修改了那些文件
- 直接把这些文件作为上下文喂给模型
这种方式被称为 Oracle 式的检索,虽然在真实场景中它们并不那么 realistic,因为工程师事实上事先并不知道要更改哪些文件。
结果
结果显示,对比 BM25 和 Oracle 方法在 27K tokens 下的表现,约莫 40% 的任务中,BM25 的检索结果召回(Recall)了 Oracle 中的全部文件,但也有接近一半的任务 BM25 没有找回哪怕一个 Oracle 中的文件。
这说明:
- BM25 是一个可用但不稳定的方案,部分任务表现好,部分表现差;
- 说明上下文选择在 SWE-bench 中是一个重要影响模型表现的变量。
![[Pasted image 20250711161155.png]]
通过将模型在两种检索方式下进行评估对比,总体而言,模型在解决问题方面遇到了显著的困难,对于真实仓库场景下的问题解决率并不让人满意。
![[Pasted image 20250711161443.png]]
大致上,以下结论可以被提出:
1. 不同仓库任务难度差异
- 模型在不同仓库上表现趋势相似,但解决的问题不完全重叠。
例如,在 "oracle" 设置下:
- Claude 2 成功解决 110 个任务;
- SWE-Llama 13B 成功解决 91 个任务;
但 Claude 2 仅覆盖了 SWE-Llama 成功实例的 42%。
2. 图像嵌入对模型有干扰
- 某些 issue 使用 Markdown 形式嵌入图像,如
。 出现图像的比例:
- matplotlib:32%
- seaborn:10%
- 全体任务平均:2%
- 这类问题可能需要多模态模型或外部图像理解模块来处理。
3. 上下文长度越长,性能反而下降
- 模型虽然能处理长上下文,但长代码段会引入干扰。
- Claude 2 在上下文长度增长时性能显著下降。
- 即使 BM25 检索在更长上下文中覆盖更多 oracle 文件,模型定位问题代码仍然困难,导致效果下降。
![[Pasted image 20250711161846.png]]
4. 精简上下文可提升性能
- 对 oracle 检索到的文件进行输入消融,仅保留实际修改的行及其上下文(±15 行)。
表现提升:
- GPT-4:从 1.3% 提高到 3.4%
- Claude 2:从 4.8% 提高到 5.9%
- 精简上下文有助于模型聚焦关键位置。
![[Pasted image 20250711161906.png]]
5. 模型不会因 PR 日期作弊
- 将任务按 PR 创建时间分为 2023 年前后,比较模型表现。
- 大多数模型在新旧任务上的性能差异很小。
- GPT-4 是唯一一个在 2023 年后表现下降的模型,但因仅在子集上评估,结论不确定。
- 表明模型不是通过“记住历史代码变化”来作弊。
![[Pasted image 20250711161923.png]]
6. 微调模型对上下文变化敏感
- SWE-Llama 在 oracle 检索下微调,但在 BM25 检索下效果明显下降。
推测原因:
- 训练时模型被引导对上下文中所有文件都进行修改;
- BM25 提供的上下文中许多文件其实不需要更改,导致误操作。
7. 补丁生成优于文件重写
- 将任务设为“生成补丁”效果优于“重写整个文件”。
例如 Claude 2:
- 补丁生成得分:4.8%
- 文件重写得分:2.2%
- 即使控制输入 token 长度,补丁生成仍更有效(7.8% vs 3.9%)。
8. 模型偏好简短、局部的修改
正确生成的补丁长度比金补丁显著更短:
- 平均编辑行数:30.1 vs 74.5
- 模型通常只编辑一个文件,回避大范围改动。
- 说明模型生成风格偏向“保守编辑”,不擅长结构性重构。