跳到主要内容

第 1 章 · 一道题长什么样(数据模型与采集)

本章讲两件事:(1) 一道 SWE-bench 题目(task instance)在内存里是什么结构、各字段什么意思;(2) 这些题目是怎么从真实 GitHub PR 里被「采」出来的。理解了数据模型,后面的评测和判分才有意义。

1.1 核心数据结构:SWEbenchInstance

一道题就是一个带固定字段的字典。类型定义在 swebench/harness/constants/__init__.py:24-37

# 真实源码片段(已省略 hints_text / created_at 两个字段,见下方说明)
class SWEbenchInstance(TypedDict):
repo: str # "django/django"
instance_id: str # "django__django-12345"
base_commit: str # 模型看到的代码快照(PR 之前的状态)
patch: str # gold 补丁:人类合并的代码改动
test_patch: str # 测试补丁:随该 PR 一起加的测试
problem_statement: str # issue 正文
FAIL_TO_PASS: str # 该由红转绿的测试(JSON 字符串)
PASS_TO_PASS: str # 必须保持绿的测试(JSON 字符串)
version: str # 仓库版本,决定用哪套安装/测试配方
environment_setup_commit: str # 建环境时用的提交点

这段是它在干嘛:定义评测里到处传递的「题目」对象。为聚焦判分主线,上面省略了源码里另有的两个字段——hints_text(issue 下的提示性评论)与 created_at(PR 创建时间),它们在评测主流程里基本不参与判分。注意 FAIL_TO_PASS / PASS_TO_PASS 在数据集里常以 JSON 字符串形式存放,用到时再 json.loads(见 make_test_spec 里的 _from_json_or_objswebench/harness/test_spec/test_spec.py:195-205)。

1.2 一个真实 PR 怎么拆成「答案 + 裁判」

这是 SWE-bench 最核心的造题直觉:一个修了 bug 的 PR,天然包含「代码改动」和「测试改动」两部分。SWE-bench 把它们分开用。

「怎么读:左边是模型能看到的,右边是评测时才用、对模型隐藏的。」

一个真实的、已合并、且关联了 issue 的 PR

├── issue 正文 ───────────────► problem_statement (给模型看)

├── 代码改动 (非测试文件) ─────► patch = gold 答案 (评测时对模型隐藏)

└── 测试改动 (测试文件) ───────► test_patch = 裁判 (评测时才打进去)

关键设计:模型只拿到题面和仓库的 base_commit 快照,拿不到 gold patch,也拿不到 test_patch。模型得自己写补丁;评测时系统才把 test_patch 打进去、跑测试来验收。

1.3 F2P 与 P2P:判分的两把尺子

这两个列表是整个评测的灵魂。直觉很简单:

  • FAIL_TO_PASS (F2P):这些测试在 bug 没修时是失败的,修好后应当通过。它们衡量「问题到底有没有被解决」(Resolution)。
  • PASS_TO_PASS (P2P):这些测试本来就通过,改完后还得通过。它们衡量「有没有把别的东西改坏」(Maintenance)。

常量名定义在 swebench/harness/constants/__init__.py:40-43。比对逻辑见第 3 章。

一句话:F2P 管「修好了吗」,P2P 管「没搞砸吧」。两个都满足才算真正解决。

1.4 数据集的几个版本

README 与 load_swebench_datasetswebench/harness/utils.py:133)里能看到主要变体:

数据集规模/特点
SWE-bench (full)全量测试集
SWE-bench Lite子集,跑得快、成本低
SWE-bench Verified500 道,真实工程师确认「确实可解」(与 OpenAI Preparedness 合作)
SWE-bench Multimodal含视觉/前端场景;test 划分的评测被刻意私有化,只能走云端 sb-cli 提交

Multimodal 的 test 划分在本地评测里被直接拦截——见 swebench/harness/run_evaluation.py:496-501,目的是防止有人针对私有测试集刷分。

1.5 数据是怎么采出来的(collect 子系统)

造题侧的主流程在 swebench/collect/build_dataset.py。它读一个 PR 的 JSONL 文件,逐个判断能不能成题。

1.5.1 一个 PR 必须满足的硬条件

is_valid_pullswebench/collect/build_dataset.py:51-64)要求:

# 真实源码节选
def is_valid_pull(pull: dict) -> bool:
if pull["merged_at"] is None: # 必须真的被合并了
return False
if "resolved_issues" not in pull or len(pull["resolved_issues"]) < 1:
return False # 必须关联了至少一个 issue
return True

这是它在干嘛:只有「被合并 + 关联 issue」的 PR 才有资格——前者保证有真实可信的答案,后者保证有清晰的问题描述当题面。

1.5.2 成题还要过两道质量关

  • is_valid_instance:67-80):patchproblem_statement 都非空。
  • has_test_patch:83-94):必须带测试改动——没有测试就没法当裁判,直接淘汰

create_instance:21-48)把通过的 PR 组装成上面 1.1 的字典;instance_idowner/repo-PR号 转写而来(把 / 换成 __)。

1.5.3 「测试文件 vs 代码文件」怎么分

造题和判分都要区分哪些是测试。get_test_directivesswebench/harness/test_spec/python.py:230-261)从 test_patch 里抽出被改动的测试文件路径,并用 NON_TEST_EXTSconstants/__init__.py:108-120,如 .json/.txt/.md/.yml)剔掉非代码文件。Django 还有特殊转换:把 tests/a/b.py 转成模块点号路径 a.b:251-259)。

1.6 小结

  • 一道题 = 一个真实 PR,拆成「gold 答案补丁」+「测试裁判补丁」+「题面」。
  • 模型只看题面和 base 快照;答案和测试对它隐藏。
  • F2P/P2P 是两把判分尺子:解决了吗 + 没改坏吗。
  • 采集严格过滤:合并过、关联 issue、有非空答案、有测试。

下一章:这道题如何被编译成可在 Docker 里跑的脚本。