把 SWE-bench 仓库变成薛定谔的猫:模型到底学会了修 bug,还是背下了仓库?
前几天刷到一个让人后背发凉的实验:把 SWE-bench Verified 的 issue 描述一段一段地喂给大模型——不给仓库、不给代码、不给测试,只给文字。结果超过 65% 的实例里,模型能"回忆"出任务特有的内容;更有 18% 以上的实例,模型直接背出了补丁内容或者测试信息。
也就是说,我们引以为傲的"模型在 SWE-bench Verified 上刷到 70 分",其中有多少是真的在推理仓库,有多少只是"这个 django 仓库我训练时见过"?
这篇来自上海交通大学的论文把这个灵魂拷问变成了一个可测量的实验。思路非常漂亮:既然怀疑模型背了仓库,那就把仓库"换一张脸"——同样的代码、同样的行为、同样的 bug,但路径名、类名、函数顺序全部换掉。如果模型是真懂,换个马甲照样修;如果是背答案,分数就得掉。
结果掉了,而且掉得很诚实。
📖 核心摘要
仓库级编码基准(以 SWE-bench 为代表)都建在热门开源项目上,这些项目早就被训练数据反复"吃"过,数据泄漏是结构性的。这篇论文提出 SchrodingerRepo——把测试仓库当成一个"评测时才实例化的潜变量":智能体进环境之前,仓库以什么面目出现是不确定的;进去之后,通过一个带随机种子、可逆的映射生成一个语义等价但面目全非的仓库视图。四级变换(问题陈述重构、命名空间映射、文件内布局重排、功能保持重写)逐级剥离模型熟悉的表层线索。在 SWE-bench Verified 上,全变换让四个主流模型的 Pass@1 掉了 6.0–14.4 个百分点,额外动作的八成以上花在重新探索仓库上;而在模型发布之后产生的新实例上,Pass@1 纹丝不动。结论很扎心:当前编码智能体的高分,有相当一部分是"熟悉感"贡献的。
这篇论文的价值不在于提出更强的智能体,而在于给整个评测体系装了一个"测谎仪"。值得细读。
论文信息
- 标题:Schrödinger's Code Repository: Have LLMs Learned SWE-bench or Memorized It?
- 作者:Silin Chen、Yufei Yang(共同一作)、Xiaodong Gu(通讯作者)、Yuling Shi、Chengcheng Wan、Haibing Guan
- 机构:上海交通大学、西安交通大学、华东师范大学 & 上海创新研究院
- 链接:https://arxiv.org/abs/2609.27891 (2026 年 8 月 21 日,cs.SE)
- 代码:https://github.com/cslsolow/Schrodinger-Repo
🎯 问题动机:静态基准的"原罪"
SWE-bench 现在是仓库级编码评测的事实标准:2,294 个真实 GitHub issue,可执行环境,测试驱动判定。它的升级版 SWE-bench Verified 人工筛出 500 个高质量实例,成了各家前沿智能体的必争之地。
但有个绕不过去的问题:这些 issue 全来自 django、sympy、scikit-learn 这种顶流开源仓库。它们的代码、issue、PR 讨论、Stack Overflow 问答,在模型训练语料里出现了不知道多少遍。OpenAI 自己都发过分析,承认 SWE-bench Verified 已经不能可靠衡量前沿编码能力了。
社区的对策是"追新"——SWE-bench Live、SWE-rebench、SWE-bench Pro 都在不断纳入模型发布之后的新 issue。这条路没错,但代价是规模和覆盖面:新实例数量有限,长尾的错误模式覆盖不全。说实话,这就像为了防止泄题不停换考卷,题是新的,但题量和题型丰富度都缩水了。
SchrodingerRepo 走了另一条路:不换题,换"卷子印刷方式"。同一个实例,每次评测用一个由随机种子决定的、语义完全等价的仓库视图。仓库还是那道题,但模型记忆里的"标准长相"对不上了。
先感受一下泄漏有多严重。论文做了个动机实验:把每个实例的 issue 描述拆成从宽泛到具体的语义单元,一轮一轮揭示给模型(不给仓库访问权),人类专家逐轮判断模型输出里是否出现了"提示中没给过、但属于这个任务特有"的内容。证据分四档:无有效回忆、文件/符号级回忆、修复逻辑级回忆、补丁/测试级回忆。

图1:四个模型在 SWE-bench Verified 500 个实例上的人工判定结果。以 GPT 5.1 为例,256 个实例能回忆到文件/符号,90 个能回忆到具体补丁或测试内容,只有 152 个"无有效回忆"。每个模型都有超过 65% 的实例存在明确的泄漏证据。
看到 Patch/Test Recall 那一层深蓝色的时候,我确实愣了一下——最夸张的模型有 146 个实例能背到补丁级别。这不是"可能见过",这是"背过答案"。
🏗️ 方法核心:四个级别的"换脸手术"
SchrodingerRepo 的名字起得很妙。薛定谔的猫在箱子打开前处于叠加态;这里的仓库在智能体进入环境前也处于"表征叠加态"——具体以哪张脸出现,由随机种子在评测时刻才坍缩确定。而且每个实例用三个不同种子生成三个视图,结果取平均,连"记住某个变换结果"这条路也堵死了。

图2:框架总览。左侧是标准的仓库级 issue 解决任务;中间四个级别逐级变换:Level 1 重写问题陈述、Level 2 重映射命名空间、Level 3 重排文件内布局、Level 4 重写局部实现;关键约束是所有变换保持可执行行为不变,最终补丁可以逆映射回原始仓库坐标做标准评测。
四级变换由浅入深,每一级针对一类"熟悉线索":
Level 1:问题陈述重构
最轻的一级,只动自然语言。一个 LLM 生成器对 issue 描述做重排、改写、删去不影响语义的细枝末节(比如无关标识符);另一个验证器 LLM 检查任务约束是否完整保留,缺了啥就触发修复。目标是洗掉"benchmark 腔"——模型可能对 SWE-bench 实例的经典措辞有记忆。
Level 2:命名空间映射(杀伤力最大的一级)
这一级只改"观测层",物理仓库一个字节都不动,但智能体看到的所有路径、模块名、符号名全部换成一套虚拟命名空间。
技术上挺讲究:先从 AST 里抽出所有带符号的节点(类、函数、模块级变量、import),严格过滤掉 Python 内建、保留字、第三方库符号——只留仓库内部的标识符。然后把标识符拆成子词,在种子控制下映射成语义上说得通的替代词,并且保持命名习惯:CamelCase 还是 CamelCase,snake_case 还是 snake_case。
论文给的例子很传神:QuerySet 拆成 Query + Set,映射为 Ledger + Suite,得到 LedgerSuite;其他所有含 Query 的标识符共享同一个映射,保证一致性。
执行时有一个双向翻译器:观测(代码上下文、执行轨迹)翻译成虚拟命名空间给智能体看,智能体的动作翻译回真实命名空间再执行。django 在智能体眼里可能变成 working_repository/storage_engine,但它敲的每条命令最终都落在真仓库上跑。
Level 3:文件内布局重排
这一级开始动真实仓库状态了,但只改"顺序"。在同一个文件或类体内,找出可重排的连续定义块(顶层函数、类、方法),构建定义时依赖图——装饰器、默认参数、类型注解、基类这些会引入"必须先定义"的约束边。然后在偏序上做随机拓扑排序,采样一个合法的新顺序。
直觉上说:你习惯在 base.py 第 300 行找到 Model.save(),现在它在第 1200 行。语义没变,"位置记忆"失效了。
Level 4:功能保持重写
最深的一级,直接改局部实现的写法。两阶段:离线阶段,一个受约束的重写智能体对与 issue 相关的代码区域生成统一 diff——注意,目的不是修 bug,而是把现有实现换成"行为等价但长得不一样"的写法;在线阶段,验证过的重写物化成工作仓库,交给下游解题智能体。
这里有个很关键的兜底设计。Level 3 和 4 改变了仓库状态,怎么保证没把题改坏?答案是直接跑官方测试套件验证:
变换后的仓库 \(V\) 必须保持原本通过的测试全通过,且目标 bug 依然没被修——变换前后是"同一个没解决的题"。此外每个级别还抽 100 个实例人工复查语义等价性。
补丁回收也设计得很干净:不信任智能体输出的 patch 文本,而是从仓库状态反推。设原始仓库为 \(B\),变换加编辑后的最终状态为 \(R'\),最终提交是
不管智能体在哪个变换视图里折腾,评测永远锚定在原始 SWE-bench 仓库坐标上。这套"可逆映射 + 状态反推"的组合拳,让整个变换在工程上是闭环的,不是花架子。
还有个务实细节:Level 3 和 4 只做全仓库变换太贵,所以限定在 golden patch 相关的文件和代码区域内。坦白讲这是个妥协——理论上仓库级变换应该铺满全库,但 500 个实例 × 3 个种子的规模下,这个取舍可以理解。
🧪 实验:分数掉了,动作多了,而且掉得很有规律
实验配置:mini-swe-agent 作为脚手架,温度 0,单实例最多 250 个动作。四个模型:GPT-5.4-mini、GPT 5.1、DeepSeek-v4-Flash、Gemini-3.1-Flash-Lite(Gemini 因成本只在泄漏证据最强的 300 个实例上跑全变换)。
RQ1:变换到底伤不伤成绩?
SWE-bench Verified 主表,我整理了关键数字:
| 模型 | Baseline Pass@1 | 全变换 Pass@1 | 跌幅 | 动作数变化 | 输入 token 变化 |
|---|---|---|---|---|---|
| GPT 5.1 | 44.6% | 36.2% | 降 8.4 个点 | +61.5% | +161.3% |
| GPT-5.4-mini | 46.8% | 35.6% | 降 11.2 个点 | +76.2% | +255.4% |
| DeepSeek-v4-Flash | 72.8% | 66.8% | 降 6.0 个点 | +116.0% | +253.6% |
| Gemini-3.1-Flash-Lite | 56.7% | 42.3% | 降 14.4 个点 | +81.1% | +136.1% |
全部 p \lt 0.05,不是随机波动。输入 token 涨 2.5 倍——熟悉的脸没了,智能体只能花真金白银的 token 重新认识这个仓库。
分级消融更有意思,"凶手"是谁一目了然:
| 级别(GPT 5.1) | Pass@1 变化 | 动作数变化 | 输入 token 变化 |
|---|---|---|---|
| Level 1 陈述重构 | 0.0 个点 | -0.8% | -0.6% |
| Level 2 命名空间映射 | 降 7.4 个点 | +63.3% | +162.3% |
| Level 3 布局重排 | 降 1.6 个点 | +6.5% | +14.4% |
| Level 4 功能重写 | 降 1.2 个点 | +5.8% | +14.2% |
Level 1 几乎无效——光改 issue 措辞洗不掉熟悉感。真正的命门是 Level 2 命名空间:路径和符号名才是模型记忆里最强的锚点。说实话这很符合直觉,你想想自己做项目的时候,是不是也是靠"这个项目的目录结构和命名风格"建立心智地图的?模型也一样,只不过它的"心智地图"是从训练数据里背来的。
RQ2:省下的分数去哪了?看动作分布
Pass@1 掉了,但更有意思的问题是:智能体的行为模式怎么变?论文把动作分成六类——navigate、search、read、probe、edit、test——统计全变换下比 baseline 多出来的动作花在了哪。

图3:两个模型的额外动作分布饼图。DeepSeek-v4-Flash 的新增动作中 83.6% 花在探索类(navigate/search/read/probe),其中 probe 占 31.4%——它喜欢用轻量级运行时执行主动"戳"仓库;GPT-5.4-mini 的 81.6% 同样集中在探索,但主要靠 read(41.8%)和 search(33.1%),probe 只有 3.7%,是典型的"静态阅读派"。
两个模型的风格差异挺耐人寻味的。DeepSeek 在 baseline 下就是"动作大户"(平均 46.19 个动作 vs GPT-5.4-mini 的 11.25),变换后继续疯狂探索,保住了更多分数(只掉 6.0 个点)但 token 成本爆炸(输入涨 253.6%)。GPT-5.4-mini 走效率路线,动作少、token 省,但熟悉线索一消失,成绩掉得最狠。
探索强度和鲁棒性之间存在 trade-off——这是我觉得全文最有工程价值的发现之一。敢探索的智能体在陌生环境里更能打,但账单也更好看;效率型智能体在熟悉环境里漂亮,换个皮就露怯。
RQ3:换个任务还灵吗?
作者把同样的变换搬到 SWE-QA(仓库级问答,3 个仓库共 144 个实例)上验证迁移性:
| 模型 | Baseline 分数 | 全变换分数 | 动作数变化 | 输入 token 变化 |
|---|---|---|---|---|
| GPT-5.4-mini | 70.35 | 65.71(降 4.64 分) | +18.15% | +34.65% |
| DeepSeek-v4-Flash | 72.97 | 72.42(降 0.75 分) | +43.02% | +59.10% |
模式完全一致:Level 2 依然是大头,Level 1 依然无效。不过 Level 3 和 4 在问答任务上几乎没影响——问答只需要定位和理解上下文,不需要修代码跑测试,局部实现写法变了无所谓。这个负结果其实信息量很大:不同任务依赖的"表层线索"是不一样的。
RQ4:是题变难了,还是熟悉感没了?
这是全文最关键的对照实验。批评者可以说:你 Pass@1 掉了,说不定只是变换把题变难了呢?
作者用 SWE-rebench 2026 年 3 月榜单的 110 个实例——全部是 GPT-5.4-mini 发布之后才创建的 issue,模型不可能在训练时见过。结果:
- Pass@1:17.27% → 17.27%,一分没掉
- 动作数:15.82 → 17.11(+8.15%)
- 输入 token:+22.01%
分数不动,成本照涨。这个实验设计得很干净:对没见过的实例,变换不改变可解性,只是让探索过程更费力。反过来说,SWE-bench Verified 上掉的那些分,不是"题难了",而是"熟悉感没了"。
论证链到这里闭环了。不得不说,这个 RQ4 是全文的点睛之笔——没有它,前面所有降幅都可以被"难度混淆变量"质疑;有了它,"记忆依赖"这个结论才真正站住。
案例研究:同一个 bug,37 步 vs 217 步
论文用 django__django-11999 做了个直观对比。这个 issue 是用户自定义的 get_FOO_display() 方法被 Django 自动生成的 display 方法覆盖。

图4:同一个 issue 的两种解题轨迹。上方(熟悉视图):智能体顺着熟悉的 Django 术语直接搜 get_.display,几步就定位到 django/db/models/fields/__init__.py,看到 setattr 无条件安装后立刻给出修复。下方(变换视图):django 变成了 working_repository/storage_engine/object_models,智能体只能从头列目录、枚举文件、全库搜 display 相关方法,确认 render%s_label 的生成逻辑后还要检查周边注册代码和测试才敢动手。*
最终两个轨迹收敛到同一个补丁,但动作数是 37 → 217。
快 6 倍的效率差,全部来自"我认得这张脸"。这就是记忆红利的具象化。
💡 我的判断
这篇论文最值钱的地方,是把"数据泄漏"从一个定性担忧变成了可定量分解的实验变量。以前大家说 SWE-bench 有泄漏,只能举孤例;现在你可以精确地说:命名空间熟悉感贡献多少分、布局熟悉感贡献多少分、实现模式熟悉感贡献多少分。这种"分级消融"的实验设计思路,我觉得比结论本身更有方法论价值。
但也要泼几盆冷水。
一、6.0–14.4 个点的跌幅,说明"大部分是会的"。 反过来看,即使把熟悉线索全洗掉,DeepSeek 还保有 66.8% 的 Pass@1。这说明当前模型的仓库推理能力是真材实料为主、记忆为辅,不是"全是背的"。论文的措辞"partially rely on memorized cues"是克制的,但标题"Memorized It?"的冲击力容易让人高估泄漏的贡献比例。
二、和同期工作的关系要说清楚。 静态扰动这条路并不新——SWE-bench Illusion 早就证明模型会利用 issue 描述和仓库位置的稳定关联,PoorCodeSumEval 做过标识符混淆,RepoMirage 做过仓库级扰动。SchrodingerRepo 真正的增量在于两点:变换是评测时动态实例化的(种子化、每次不同、可逆),以及有严格的功能等价验证(测试套件兜底 + 人工抽检)。前者防住了"变换结果本身被背下来"的二次泄漏,后者防住了"变换引入伪难度"。这是工程完成度上的代差,但不是从 0 到 1 的原创概念。
三、局限也实在。 只覆盖 Python 仓库、只支持命令行交互(用 IDE API 或 language server 的智能体还没法测)、Level 3/4 只作用于 golden patch 相关文件——这几个约束意味着结论的外推要谨慎。另外 RQ4 只有 110 个实例且只跑了 GPT-5.4-mini 一个模型,样本量偏小,要是能覆盖更多模型会更有说服力。
对从业者的启发很直接:
- 如果你在用 SWE-bench 分数做模型选型或对外宣传,打个八折再看,至少对老牌热门仓库的实例要这样
- 如果你在训练编码智能体,"探索强度 vs 交互成本"的 trade-off 值得专门优化——DeepSeek 那种敢 probe 的风格在陌生仓库上明显更抗揍
- 如果你在设计评测,SchrodingerRepo 的种子化动态变换范式可以直接抄作业,它比静态变换 benchmark 更抗"二次泄漏"
往大了说,这篇论文预示了一个趋势:未来的基准评测可能会从"固定考题"演化成"评测时生成的等价考题族"。分数不再是"对单一表征的表现",而是"对一族语义等价表征的鲁棒性分布"。这对所有依赖公共数据的评测领域都有借鉴意义——不只是代码。
觉得有启发的话,欢迎点赞、在看、转发。跟进最新AI前沿,关注我