论文链接:A Jagged Frontier: Evaluating Robustness of Code Agents to Semantics-Preserving Transformations 代码仓库:CSU-TrustLab/jagged-frontier 发表时间:2026年8月(arXiv:2608.18389v1,2026-08-18提交) 机构:Colorado State University(第一作者 Hasan Najib Mahmud 所在,TrustLab)+ Microsoft(共同第一作者 Shreya Gupta)+ UIUC(Gagandeep Singh 组)+ CMU(Corina Pasareanu,软件工程与形式化验证背景)。典型的「高校学术组 + 企业研究院」四方合作:高校侧贡献程序变换与验证技术,微软侧贡献 Agent 评估工程经验。 领域标签:cs.AI / 代码智能体鲁棒性 / 软件工程


一、论文背景

1.1 AI 代码 Agent 已经成了主流生产力工具

短短几年内,AI 编程工具从研究原型变成了软件开发的日常基础设施。论文开头引用了四份行业调查:Stack Overflow 2025 开发者调查、GitHub Octoverse 2025、JetBrains 开发者生态报告、Google DORA 2025——多数职业开发者已经在日常使用 AI 编程工具。而衡量这些工具进步的标尺,是 SWE-bench 这一类「仓库级问题修复」基准:给 Agent 一个真实 GitHub 仓库的某个历史 commit 状态、一段 issue 描述,让它自己定位、修改代码,最后用测试套件(FAIL_TO_PASS + PASS_TO_PASS)判定是否解决。SWE-bench Verified 是其中经 OpenAI 组织人工校验的 500 实例子集;SWE-bench Pro 则是更难、抗污染的后续版本。几乎每个新模型、每个新 Agent 发布都要在这两个榜上报个分数。

1.2 但榜单分数可能高估了部署可靠性

问题在于:榜单分数衡量的是 Agent 在某一份特定代码快照上的表现。而神经网络有一个广为人知的痼疾——学捷径(shortcut learning)(Geirhos et al. 2020):模型往往依赖表面统计规律而非任务的本质结构。如果一个 Agent 的行为会随着代码的表面写法变化而剧烈波动,那会带来两个严重后果:

  1. 基准分数夸大部署可靠性。真实世界的代码库风格千差万别——有人爱用 x < 5,有人爱用 5 > x;有人变量命名考究,有人满屏 tmp1、tmp2。如果 Agent 只在「基准里那种写法」上表现好,换个风格就翻车,榜单分数就不代表真实能力。
  2. 暗示模型依赖浅层句法模式而非程序语义理解。语义等价的代码对人类工程师来说难度应当完全相同,如果 Agent 解决率显著下降,说明它抓的是表面模式。

1.3 已有研究的空白:从「单轮」到「多轮仓库级」

此前并非没有代码模型鲁棒性研究——标识符重命名攻击(Yefet et al. 2020)、死代码插入(Srikant et al. 2021)、ReCode 基准(Wang et al. 2023)等等,但这些都停留在单轮、非 Agent 场景:扰动一段固定长度的输入(一个函数、一段代码片段),看模型一次推理的输出变不变。

而现代代码 Agent 完全是另一种生物:它要在几十轮推理中自主搜索仓库、阅读文件、编辑代码、运行测试、根据报错回溯——扰动散布在整个代码库的随机位置,Agent 每一次工具调用都会重新接触到被扰动的代码。仓库级多轮 Agent 的鲁棒性,在此文之前没有系统研究。这就是本文填的坑。

1.4 一个类比帮助理解

可以把这件事想象成考试换监考老师:原来的考题一模一样(语义等价),只是卷面排版变了——选择题选项顺序打乱、题干换个说法、插入几行无关的提示语。一个真正理解知识的学生,成绩应该纹丝不动;一个靠背题库位置记答案的学生,成绩就会抖动。本文做的就是给 AI 代码 Agent 办一场「换排版的考试」,而且考了 16 种组合(2 框架 × 4 模型 × 2 基准),看看谁的成绩单会抖。


二、论文定位和关联工作

本文处于「代码模型鲁棒性」研究的延长线上,同时借用了「LLM 语义保持扰动评测」的方法论。按谱系梳理:

2.1 谱系一:代码模型的对抗攻击线

工作核心思想与本文关键区别
Yefet, Alon & Yahav 2020(OOPSLA)对神经代码模型做标识符重命名对抗攻击(ALERT)单轮分类任务;本文是多轮 Agent + 仓库级
Bielik & Vechev 2020(ICML)形式化定义代码模型的对抗鲁棒性约束理论/白盒;本文黑盒随机采样
Yang et al. 2022(ICSE)对 CodeBERT 等预训练模型做黑盒自然攻击仍是函数级分类
Srikant et al. 2021(ICLR)用优化混淆生成对抗程序(死代码、算子替换)反馈引导的最优化攻击;本文刻意不用反馈
Ramakrishnan et al. 2022(SANER)组合重命名/死代码/操作数交换,暴露方法名预测与摘要任务的脆弱性多任务但单轮
Jha & Reddy 2023(AAAI,CodeAttack)跨缺陷检测、克隆检测的攻击框架分类任务

2.2 谱系二:防御与训练线

  • Jain et al. 2021(EMNLP):在等价程序对上做对比学习提升鲁棒性。
  • Chakraborty et al. 2022(ESEC/FSE,NatGen):把「自然化」变换作为预训练目标,让模型见过各种不自然写法后更稳。

这些工作证明鲁棒性可以通过训练改善,但都针对小模型单轮任务。

2.3 谱系三:代码生成评测线

  • ReCode(Wang et al. 2023,ACL):对 docstring、函数名、语法、格式做扰动来评测代码生成模型——这是与本文最接近的「评测视角」,但对象是生成而非 Agent,且扰动施加在 prompt 上而非整个仓库。
  • Mastropaolo et al. 2023(ICSE):发现 prompt 上下文里的轻微重构就能显著改变 GitHub Copilot 的补全结果。

2.4 谱系四:LLM 语义保持扰动评测线(自然语言域)

这条线给本文提供了「换汤不换药测排名」的方法论灵感:

  • Sclar et al. 2024(ICLR):仅改变 prompt 格式(空格、分隔符、选项顺序)就造成最高 76 个百分点的准确率摆动。
  • GSM-Symbolic(Mirzadeh et al. 2024)/ GSM-Plus(Li et al. 2024):换名字、换数字、换措辞,数学题性能掉最多 10%。
  • Zheng et al. 2024(ICLR):重排选择题选项就能显著改变模型排名。
  • Dziri et al. 2023(NeurIPS,Faith and Fate):给出机制解释——Transformer 倾向把组合推理「线性化」,所以表面变化容易 derail 多步任务。

2.5 本文的定位

维度之前的工作本文的突破
评估对象单轮模型(分类/短生成)多轮仓库级代码 Agent
扰动位置固定长度输入(函数/prompt)随机散布在整个代码仓库
扰动策略常为反馈引导的对抗优化非反馈随机采样,算影响下界
随机性处理大多忽略配对多次运行,把扰动效应从 Agent 内在随机性中剥离
考察维度仅准确率resolve 率 + 步数 + token 成本
核心发现单一模型脆弱锯齿前沿:鲁棒性是模型×框架×工作负载的联合属性

一句话定位:这是第一个系统评估仓库级代码 Agent 对语义保持变换鲁棒性的工作,也是第一个揭示「鲁棒性排名不迁移」这一现象的工作。它把 GSM-Symbolic 式的「换汤不换药」评测哲学搬进了 Agent 时代,并配上了严谨的统计实验设计。


三、问题定义

3.1 从具体场景到抽象问题

具体场景:一个代码 Agent 在某个仓库的 base commit 上修一个 issue。现在有人把这个仓库用各种方式「重写」了一遍——控制流换个写法、插入永远不会执行的死代码、把局部变量改成同义词——程序行为完全不变(测试全部照常通过)。Agent 在重写版上的解决率会掉吗?

这个问题看似简单,实则有一个实验设计上的深坑:Agent 本身是随机的。temperature=1.0 下,同一个任务跑 20 次可能 14 次成功 6 次失败。那么「未扰动跑成功、扰动版跑失败」这一观察,到底是扰动造成的,还是纯粹运气?不解决这个归因问题,一切结论都是空中楼阁。

3.2 语义保持变换(SPT)的形式化定义

论文先给出严格定义:变换 T 是语义保持的,当且仅当对每一个可能的程序输入,T(P) 与 P 产生相同的可观察行为—— 相同的返回值或相同异常, 相同的外部可观察副作用, 若一方停机另一方也必须停机。

在评估语境下,这个定义被操作化为功能测试套件等价:T(P) 在项目测试套件的每个测试上与 P 结果一致。注意这是有界的操作化(受测试覆盖约束),论文用三万多个测试做了差分验证(见第四节),并坦承这是证据而非等价性证明。

关键推论:观察等价具有传递性,所以有限个 SPT 的复合序列也语义保持。这允许把单点变换组合成整仓扰动。

3.3 抽象后的核心问题

论文真正要估计的量,可以写成:

  • 对任务实例 i,设 Agent 在未扰动种子上的解决概率为 p₀(i);
  • 扰动版本本身是一个随机变量 V(采样器随机选变换、文件、位置),每个变体有自己的真实解决概率 p_V,其均值为 μ = E[p_V];
  • 退化(degradation)定义为 Δ(i) = p₀(i) − μ,即「平均而言,一份随机语义等价重写会让解决概率掉多少」。

这里出现了两个独立的随机源,是全文实验设计的枢纽:

随机源定义方差贡献
变体随机性采样器随机抽取变换/文件/位置,不同变体难度不同σ² = Var(p_V)(变体间)
Agent 随机性固定变体下,单次运行是 Bernoulli(p_V) 抽样ν = E[p_V(1−p_V)](变体内)

给定什么:一个种子仓库、SPT 目录、每实例固定预算 R 次 Agent 运行。 求什么:Δ 的无偏、方差最优估计,以及跨 16 个(框架, 模型, 基准)配置的比较。 约束是什么:语义保持(测试等价)、非反馈(不用模型输出指导采样,保证结论是下界)、测试文件不可扰动(否则评分目标被移动)。

3.4 这个抽象的精妙之处

  1. 把「鲁棒性」从对抗最坏情况转为随机平均情况。对抗攻击只能找到最脆弱的点,回答「最多能坏到什么程度」;随机采样回答「一份普通的等价重写会坏到什么程度」。后者才是部署者关心的量,而且天然是下界——反馈引导的攻击者只会更糟。
  2. 把「Agent 随机性」从噪音变成可分解的量。通过两层方差分解(σ² + ν = μ(1−μ)),论文证明了「每变体跑一次、跑很多变体」是方差最优设计(第五、六节详述)。
  3. 把「扰动效应」约束在配对比较内。同实例、同配置下扰动/未扰动交替运行,任何系统级漂移都被差分抵消。

四、问题解法

论文的解法分四块:SPT 目录 → 变体采样器 → 实验协议 → 统计推断。每块都对应一个经典思想的「Agent 时代改装」。

4.1 SPT 目录:14 种语义保持变换

类比:这是给代码做的「语法同义改写词典」。分成两大类九小类(论文表格 1):

改写类(Rewrites)——把已有结构换等价写法:

变换做法设计意图
If Else Switcher交换 if/else 分支并否定条件控制流反转
For Loop Rewritingfor 循环改写成显式迭代器(iter/next/StopIteration)循环协议显式化,代码噪音大
And Condition Splitterif A and B 拆成嵌套 if复合条件分解
Comparison Swapper交换比较操作数并反转算子(x < 5 → 5 > x)操作数顺序
While Loop Unrolling展开 while 循环一次迭代循环结构变形
Double Negation Injector条件外包 not not (...)冗余否定噪音
Commutative Operand Permuter交换可交换运算的操作数(需类型推断保证安全)算术表面等价
Local Variable Renamer局部变量改为 WordNet 同义词(无同义词则随机五字母名)标识符重命名
String Literal Splitter字符串字面量拆成 + 拼接破坏 grep 整串定位

注入类(Inert Insertions)——插入永不生效的代码:

变换做法设计意图
If True Wrapper用随机生成的永真复合布尔守卫包裹代码块复杂但无意义的布尔噪音
Try Except Injector包一层 except Exception: raise(原样重抛)冗余异常结构
Dead Code Injector插入 if False: 块 + 哑赋值静态不可达代码
Dead String Assignment注入含目标关键词的未读字符串赋值诱饵:污染关键词搜索
Dead Method Injection向类追加 if False 守卫的同名死方法诱饵:污染方法名搜索

最后两种是「keyword-bound」变换,堪称设计上的点睛之笔:它们绑定到 issue 描述里提取的关键词(方法名、字符串),故意种下词法上醒目但行为上无效的诱饵。Agent 若靠关键词 grep 定位,就必须分辨真目标与诱饵。这直接模拟了真实攻击面——检索依赖。

4.2 工程难点:在真实仓库上安全地变换

论文附录 A 坦言,写函数级 SPT 容易,写仓库级 SPT 极难,因为:

  • 作用域与遮蔽:iter、next、Exception 等 builtin 可能被局部绑定遮蔽,改写就会翻车;
  • 类型不可见:+ 在数字上可交换,在字符串/列表/自定义 __add__ 上不可——所以交换操作数前必须用 Astroid 做类型推断,推不出就跳过;
  • 程序可自省:locals()、frame introspection、元类、Pydantic BaseModel/Enum 等框架基类会观察自身命名空间,连死代码注入都可能可观察——所以 Dead Method Injection 遇到这些类直接跳过;
  • 规模放大:单个变体要改上千个位置,万分之一的不安全规则也会撞上事故。

应对原则是极度保守:适用性条件无法从代码确证时跳过该位置。这一「宁缺勿错」哲学是全部结论可信的基石。

验证方法:对 SymPy(12,994 个测试)、sqlfluff(10,060)、xarray(19,917)三个领域迥异的项目做差分测试——每种变换单独在全部适用位置施加,跑全量测试套件,与基线逐测试比对。三项目全部 14 种变换下所有测试结果保持一致。由于每个变换被独立验证为保持,复合后的变体无需再逐个验证测试结果。

4.3 随机变体采样器:非反馈的下界逻辑

类比:如果说对抗攻击是「狙击手」(用模型反馈逐步逼近最脆弱点),这个采样器就是「随机炮击」——每一发都独立随机,没有任何反馈回路。这样测出的损害是下界:反馈引导的攻击者只会造成更大伤害。这个设计选择直接决定了结论的可信语义:它回答的是「普通的等价重写能造成什么」,而非「最坏情况能造成什么」。

对每个种子仓库,采样器做四个随机决策(Algorithm 1,超参 N=20 个变体、Nt=3 种变换、Nk=5 个关键词、Nf=10 个文件上限、φ=0.7 位置比例):

  1. 选哪些变换:从 14 种里均匀抽 3 种;
  2. 选哪些文件:每种变换抽一个文件包含概率 p_file ~ U(0,1),每个非测试文件以该概率纳入——不同变换覆盖仓库的不同部分,变体从「单点局部编辑」到「遍布全仓」皆有;关键豁免:gold patch 涉及的文件 F_gold 永远纳入,保证每个变体都打在解题相关区域而非碰运气;
  3. 选哪些位置:纳入文件中 70% 的候选位置被改写(φ=0.7),同一文件内保留未改写的同模式实例,增加不一致性;
  4. 选哪些关键词:用一次 LLM 调用从 issue 描述提取关键词(方法名/字符串),给诱饵类变换绑定最多 5 个目标。

Nf=10 的由来很有戏剧性:不设上限时,Agent 经常识破扰动、直接 git reset 整个仓库还原——「这测量就被摧毁了,而不是变难了」。所以上限的目的是把变体控制在 Agent 视为「正常代码」的范围内。这个细节本身就是个重要发现:Agent 能察觉到过度异常的代码。

实测扰动幅度:变换触及源代码行数的中位数,SWE-bench Verified 上 6.9%,Pro 上 7.7%(下界,因为变换还会插入新行)。

4.4 实验协议:把两个随机源装进统计框架

实例选择(受算力约束的 54 个):从 SWE-bench Verified 按仓库-难度分层抽 28 个、SWE-bench Pro 跨仓库抽 26 个,全部先用 Claude Opus 4.5 验证过在未扰动条件下可解(避免把「本来就解不了」和「扰动导致解不了」混为一谈)。

16 个配置:2 框架(mini-SWE agent v1.17.5 / OpenCode v1.18.4)× 4 模型(Claude Opus 4.5 / Kimi K2.5 / MiniMax M2.5 / Qwen 3.6-27B)× 2 基准。全部 temperature 1.0。单配置单超参就要 54×40 = 2160 次 Agent 运行,所以超参没做网格搜索。

每实例的配对流程(Algorithm 3):

对每个实例 i:
  Phase 1  提取种子仓库、gold patch 文件清单、issue 描述
  Phase 2  未扰动基线:20 次独立运行(每次全新隔离 Docker 环境)
  Phase 3  采样 20 个语义等价变体
  Phase 4  每个变体 1 次运行(注入变体 → 跑 Agent → 测试套件判分)

为什么每变体只跑一次(K=1)是最优的? 这是附录 E 的漂亮推导。预算固定为 R 次运行时,若分成 N 个变体 × K 次运行(R = NK),估计量 μ̂ 的方差为:

Var(μ̂) = (Kσ² + ν) / R

  • 无偏性:对任何 K 都成立(包括 K=1)——单次运行的条件期望已经是 p_V,重复只是收紧一个本来就没偏的估计;
  • 方差:变体间方差项 Kσ²/R 随 K 严格递增(σ²>0 时)。花在一次新运行上的钱同时采样两个随机源,重复跑同一个变体只重采样 Agent 随机性。
  • 所以 K=1(N=R=20)是方差最优分配,且此时 μ̂ 退化为教科书二项比例,无需聚类校正。

统计推断:配置级均值用 B=20,000 次 bootstrap 百分位区间(实例内重采样运行、实例集固定——「固定总体」推断,区间量化的是运行间波动而非实例抽样);实例级退化(两个 20 次运行的比例之差)用 Newcombe 95% 区间。

4.5 两个框架的架构对比(为什么选它们)

维度mini-SWE agentOpenCode
架构单一集中式 Agent主 Agent 调用工具与专职子 Agent(Build/Plan/General/Explore)
工具仅 bashbash、edit、write、read、grep、glob、lsp、apply_patch、skill、todo_write、web_fetch、web_search、question 等 14 种
访问限制无按子 Agent 角色与用户配置决定可用工具
上下文管理连续、只追加多个子 Agent 各自独立、动态变化的上下文

一个极简(一个 bash 走天下),一个重装备(多 Agent 协作 + 丰富工具链)。这个对比轴在后文成为「简单框架更鲁棒」结论的来源。硬件上:单台 128 核 EPYC 9554 + 6 张 RTX PRO 6000,Qwen 用 vLLM 本地 2 卡部署,其余三个模型走 Amazon Bedrock;Qwen 的 token 成本按 OpenRouter 同模型定价折算,保证 δcost 是实例内自洽比较。


五、评估指标与实验证据

5.1 指标体系

指标定义衡量什么
resolve rate通过 FAIL_TO_PASS + PASS_TO_PASS 的运行比例绝对解题能力
Δ(退化)Δ(i) = r₀(i) − r_p(i),主指标扰动平均掉多少个百分点的解决率
δstep扰动 vs 基线的平均步数相对变化行为效率
δcost扰动 vs 基线的 token 成本相对变化经济开销

δstep/δcost 有两种口径:仅统计两种条件都解决的运行(更保守,排除长失败运行干扰),或全部运行。论文主文用前者。这一设计直接服务于 RQ2 的论点:只看结果会低估扰动影响。

5.2 RQ1:解决率退化——锯齿前沿

总体幅度:16 个配置中 13 个平均退化为正,6 个 95% 置信区间不含零(统计显著)。最大平均退化 6.7 个百分点(mini-SWE agent + Opus,SWE-bench Pro)。没有任何框架-模型组合在两个基准上都显著——效应是配置依赖的,不是均匀的。

核心发现:鲁棒性排名不迁移。具体数字:

模型mini-SWE agent / VerifiedOpenCode / Verifiedmini-SWE / ProOpenCode / Pro
Opus1.8 pp—6.7 pp(全场最大)四模型中最低
Kimi≤1 pp(4 格中 3 格)——Pro 上 OpenCode 最差(4.2 pp)
MiniMax2.7 pp0.5 pp——
Qwen0.2 pp(最鲁棒)5.5 pp(最脆)——
  • Qwen 在 mini-SWE agent + Verified 下是全场最鲁棒的模型(仅掉 0.2 pp),换到 OpenCode 同基准却成了最脆的(掉 5.5 pp);
  • MiniMax 正好反向:mini-SWE 下掉 2.7 pp,OpenCode 下反而只掉 0.5 pp;
  • Opus 在 mini-SWE 下从 Verified 的 1.8 pp 恶化到 Pro 的 6.7 pp,而同样这个 Opus 在 OpenCode + Pro 下却是四个模型里最不退化的;
  • 每个模型都至少在一个格子里「最鲁棒」、在另一个格子里「最脆」。

这就是「锯齿前沿(jagged robustness frontier)」的含义:把 16 个配置的退化画成一张图,表面凹凸不平,且任何单轴(模型/框架/基准)的排序都无法预测整体形状。鲁棒性是(模型 × 框架 × 工作负载)的联合属性,不是模型的私有属性。实践者若根据「某模型在某框架下抗扰动」来选型,换框架或换代码库后完全可能得到相反结果。

更简单的框架一致更鲁棒:四模型平均,mini-SWE agent 在 Verified 上退化 1.34 pp vs OpenCode 1.88 pp,Pro 上 1.88 vs 3.65 pp。而且这个结论扛住了「能力翻转」的检验——基线解题率上 mini-SWE 在 Verified 赢(89.05% vs 81.20%),在 Pro 上却输(76.35% vs 79.33%),但鲁棒性优势两个基准都保持。这说明简单框架的鲁棒不是因为它更弱。

退化是集中的,不是弥散的:432 个实例-配置组合中只有 14 个 Newcombe 区间不含零(20 次运行下约需 25 pp 的摆动才能显著)。mini-SWE + Opus 在 Pro 上的 6.7 pp 平均退化,约三分之二由三个实例贡献:element-web_dae13(-50 pp)、openlibrary_f3b2640 与 openlibrary_a7b7d(各 -35 pp)。Verified 上也有单点重灾户:scikit-learn-14983 在 OpenCode+Qwen 下掉 40 pp、matplotlib-20859 在 mini-SWE+Qwen 下掉 35 pp。同时没有实例在全部 8 个配置下都退化;个别格子还大幅反向(mwaskom__seaborn-3069 在 OpenCode+MiniMax 下提升 55 pp)。脆性挂在特定的「实例-配置」对上,而非仓库或模型单独决定。有趣的是 qutebrowser_ef5ba 和 ansible_9142b 在 8 个配置中的 7 个里退化、sympy__sympy-11618 在 5 个里退化且从不改善——这些仓库似乎是「天然雷区」。

榜单排名幸存,鲁棒性排名死亡(附录 D):16 个组合里 13 个解决率下降、3 个小幅上升(不到 1 pp,在运行噪音内);但四个「框架-基准」面板中三个的模型能力排名在扰动前后完全不变(第四个仅交换了基线差距 0.2 pp 的 MiniMax 和 Qwen)。也就是说,传统排行榜式对比根本探测不到 SPT 的影响——被扰动打乱的是鲁棒性排名,而这恰是榜单盲区。

5.3 RQ2:努力成本——被结果指标掩盖的影响

这是全文最具实用价值的部分:

口径VerifiedPro
成本变化(仅解决运行)8 个配置全部上升:+4.0%(mini-SWE+Opus)到 +22.9%(OpenCode+MiniMax),区间全部不含零OpenCode 4 配置 +8.8%~12.6%(均显著);mini-SWE 4 配置近零
全部运行8 个配置成本全升(最高 +25.4%),7 个显著8 个中 7 个上升(最高 +14.7%),6 个显著

也就是说:即使解决率纹丝不动,Agent 也为对抗扰动多花了真金白银的 token。步数最多 +9.9%,成本最多 +22.9%——成本涨幅远超步数涨幅,说明每步消耗也在涨:16 个配置中 12 个每步成本上升,13 个每步输入 token 上升。Agent 不只是多跑几轮,而是每轮都在读更重的上下文(文件被加长、无关代码变多)。

Opus 的「压缩轨迹」行为是最反直觉的单点发现:mini-SWE + Opus + Verified 下,Opus 对扰动的反应是减少步数——每个实例都如此(平均 -19.8%,范围 -6% 到 -39%),但每步输出 token 翻倍以上(中位 +101%),每步输入 token 反而降 17%,净效果每步成本 +30.4%、总成本 +4.0%。它没有多轮试错,而是发出更少但更致密的动作。且该行为是基准特异的——到 Pro 上步数缩减消失(-1.29%)。

5.4 RQ3:失败模式(20 条人工分析轨迹)

四个代表性模式:

  1. 识破并回滚 SPT:MiniMax 在 Qutebrowser(mini-SWE agent)上检查 commit 历史,发现变换全在最新 commit 里,于是先还原仓库再改——扰动被完美化解。
  2. 定位能力被稀释:Opus 在 Openlibrary 上 grep issue 里的关键词,结果被多个同关键词的死字符串赋值干扰,被带偏。诱饵类变换的设计意图得到验证。
  3. 顺手「修复」扰动代码:Opus 在 Qutebrowser 上识别出被扰动的代码并在多个位置「修复」——更大的改动量也带来更大补丁失败风险。
  4. 补丁验证被污染:Kimi 在 Pytest-dev 上,一个单测失败,恰好失败处附近有 String Literal Splitter 的痕迹,Agent 误判失败与自己无关(是扰动的锅),直接提交错误补丁。

附录 F 还有补充观察:SPT 识破率本身方差很大(有的 Agent 反复读被改代码却始终没意识到);issue 描述自相矛盾会放大锯齿(NodeBB 的一例:满足 issue 就必然弄坏一个单测,三个模型全陷入 打补丁→验证→回读 issue 循环,步数和成本暴涨,而某次通过恰是因为 Agent 没有回退);验证框架的健全性也参差(有的模型/仓库组合搭了三层测试,有的测试脚本自身断言就写错了)。


六、效果优势的根源解释(机制因果链)

本节从机制层面回答:为什么这些现象结构上必然发生,而不是巧合。

6.1 为什么随机等价重写能伤到 Agent:攻击面从「单次推理」变成「每次工具调用」

先看 baseline 的根本局限:单轮扰动研究假设扰动作用于一次推理的输入。而多轮 Agent 的信息流是:每一轮推理的输入 = 系统 prompt + 完整历史轨迹 + 新工具输出。仓库级扰动不只在第 0 轮注入一次,而是每次 grep/read/glob 都会重新把被扰动的代码打进上下文——扰动在轨迹中被反复放大。因果链:

SPT 散布全仓 → Agent 的每次检索交互都携带扰动信号 → 上下文噪音累积 + 定位信号被稀释 → 更多探索步/更重的上下文(对应 RQ2 的 δstep、δcost、每步输入 token 上升) → 部分配置下定位或验证失败(对应 RQ1 的 Δ)

两个具体传导路径有直接证据:

  • 定位稀释路径:诱饵变换把关键词塞进无关位置 → grep 结果信噪比下降(RQ3 模式 2)→ 定位耗时上升;
  • 验证污染路径:被扰动代码与测试失败在词法上共现 → Agent 的失败归因出错(RQ3 模式 4)→ 错误补丁提交。

6.2 为什么更简单的框架更鲁棒:上下文传播链的长短

mini-SWE agent 只有一个 bash 工具、单一连续上下文;OpenCode 有子 Agent、LSP、专属检索工具。为什么前者更抗扰动?

关键在扰动信号在系统中的传播路径长度。OpenCode 的架构里有更多「解释层」:检索工具返回结构化结果、子 Agent 各自维护上下文再向主 Agent 汇报。每一层处理都建立在未被扰动的先验假设上(比如 LSP 符号导航假设代码结构是「正常」的、子 Agent 汇总时假设原始输出可信)。扰动让这些中间层的输入退化,且退化会在层间传递并叠加——子 Agent 在被稀释的检索结果上做摘要,主 Agent 又在失真摘要上做决策,噪音经过两级放大。mini-SWE agent 没有中间层,Agent 直接面对原始 bash 输出,噪音不经过任何再加工——它能看到原始真相,也就更有机会识破并绕过扰动(RQ3 模式 1 的成功回滚正发生在 mini-SWE 上)。

同时,工具丰富度本身是个双刃剑:OpenCode 的更多专用工具(LSP、结构化检索)意味着更多「依赖代码表面规整性」的通道,每条通道都是一个扰动入口。因果链:

框架越复杂 →(a)中间解释层越多,噪音放大链越长;(b)依赖代码表面规整性的工具通道越多 → 扰动信号的暴露面与放大率都更大 → 平均退化更高(Verified 1.88 vs 1.34;Pro 3.65 vs 1.88)

这也解释了 Pro 上的 усилия asymmetry:OpenCode 四配置成本全涨 8.8~12.6% 而 mini-SWE 近零——重框架为扰动付出的是真金白银。

6.3 为什么更强的基线模型受冲击更大:置信度与捷径的耦合

论文观察到(在框架内比较):未扰动下更强的模型(如 Opus)可能被扰动伤得更重(mini-SWE 下 Opus 在 Pro 掉 6.7 pp 为全场之最)。机制解释:

强模型在干净代码上解题时,其高效恰恰来自对表面统计模式的强利用——它能「一眼看出」该改哪里,依赖的是从海量训练数据学到的「什么样的代码长什么样、issue 关键词对应什么位置」的先验。这些先验在语义等价但表面被改写的代码上系统性地失准:关键词被诱饵稀释、惯用结构被非惯用重写替代。模型越依赖先验,失准越狠。因果链:

基线能力越强 → 越依赖习得的表面先验快速定位 → 扰动使先验失准的代价越大 → 退化幅度越大

弱模型本来就靠多轮笨拙试错,先验失准对它的边际伤害小。这与shortcut learning的经典论断(Geirhos et al. 2020)及 Dziri et al. 2023 的「Transformer 线性化组合推理」相呼应。

6.4 为什么是「锯齿」:三体交互与重尾

为什么没有任何单轴排序能预测鲁棒性?因为退化由三层因素相乘:

实例是否脆弱(仓库写作风格、issue 描述质量、验证框架健全性) × 模型的先验匹配度 × 框架的噪音放大系数

每层都不是常数:同一仓库在不同模型下退化不同(Qwen 0.2↔5.5 pp 的框架翻转);同一模型在不同仓库上表现迥异(element-web_dae13 掉 50 pp 而 seaborn 反升 55 pp);issue 描述自相矛盾时(NodeBB 例)三个模型集体陷入循环。三层交互产生重尾分布——6.7 pp 的平均值背后是三个实例贡献三分之二。平均值掩盖了「多数组合毫发无损、少数组合当场毙命」的真实结构,这正是「锯齿」的统计本质。

6.5 反事实验证:如果去掉某个设计会怎样

  • 若去掉 F_gold 豁免(gold patch 文件不强制纳入):多数变体将打不中解题相关区域,扰动效应被稀释成接近零——测出来会是假鲁棒。
  • 若去掉 Nf 上限:Agent 大量 git reset 直接还原仓库,测量被摧毁(论文附录 C 实录)。论文用一个超参把「测不到」和「测得准」区分开来,这是实验设计成熟度的体现。
  • 若不用配对设计、每条件只跑一次:Agent 随机性(ν)与扰动效应(μ 差异)完全混叠——20 次运行下方差 σ²+ν 与 μ(1−μ) 同量级,任何单次观察都无归因力。
  • 若只看解决率不看成本:会得出「多数配置影响很小」的乐观结论,漏掉 8/8 配置成本显著上升的事实。

七、必要知识反推

假设让一个零基础的人重做这项研究,最少需要哪些知识?

7.1 领域知识层

  • 程序语义与观察等价:必须理解「语义保持」的严格定义(任意输入下行为一致)以及为什么测试套件等价只是它的有界操作化——否则无法解释验证的局限性;
  • Python 语言机制深水区:作用域遮蔽(builtin shadowing)、运算符重载与交换律的类型依赖、locals()/frame 自省、元类与框架基类(Pydantic/Enum)如何观察命名空间——不懂这些写出的 SPT 会破坏语义,全部结论作废;
  • SWE-bench 任务结构:base commit / issue / FAIL_TO_PASS / PASS_TO_PASS / gold patch 的含义,以及测试文件不可扰动的理由(评分 oracle 必须不动);
  • Agent 框架架构:mini-SWE 与 OpenCode 在工具面、上下文管理上的差异——这是「锯齿」结论的解释轴。

7.2 方法论知识层

  • 差分测试(differential testing):McKeeman 1998 的经典方法,用于逐变换验证语义保持;
  • 对抗鲁棒性研究的两条路线:反馈引导优化(Srikant et al. 2021)vs 随机采样——必须理解「非反馈 = 下界」的论证逻辑才能做出这个设计选择;
  • shortcut learning 与 LLM 扰动敏感性文献(Geirhos 2020、Sclar 2024、GSM-Symbolic)——为「为什么要做这件事」提供动机和「换汤不换药测排名」的方法论模板;
  • 统计推断工具箱:二项比例、全方差定律、bootstrap 百分位区间、Newcombe 区间、固定总体推断 vs 实例抽样推断的区别——K=1 最优性的推导全靠这些。

7.3 工程知识层

  • AST 级代码改写:LibCST 做结构改写、Astroid 做类型推断、NLTK/WordNet 提供同义词——工具链选型背后是「保守跳过」哲学;
  • Agent 评测基础设施:Docker 隔离环境供给、vLLM 本地部署、Bedrock API 计费与 token 成本核算(跨模型可比性);
  • 轨迹定性分析方法:论文用 LLM 辅助从原始轨迹抽取事件序列(定位/调试/规划/打补丁/验证/回退),生成 HTML 可视化再人工复核——「AI 辅助筛选 + 人工确认」的定性分析流水线本身是个可复用的工程模式。

7.4 知识融合的关键节点

  • 节点一:程序变换 × 统计实验设计。懂程序变换的人很多,懂配对方差分解的人也不少,但把「SPT 采样」翻译成「两层随机源(σ², ν)的估计问题」并推出 K=1 最优——这个化学反应产生了方法论上的核心贡献;
  • 节点二:软件工程保守性 × 攻击者思维。SPT 的实现要像保守编译器优化一样宁跳勿错,而诱饵设计(keyword-bound decoys)又要像攻击者一样思考「Agent 靠什么定位就污染什么」——两种对立思维在同一目录里共存;
  • 节点三:定性观察反哺超参。发现 Agent 会 git reset 识破扰动(定性)→ 设置 Nf 上限把变体拉回「正常代码」范围(定量设计修正)。没有轨迹人工分析,就不会有这个修正,测量会被系统性摧毁。

八、论文中可以提取的通用性灵感

8.1 评估系统可靠性时,用「随机非反馈采样」测下界,而非只测最坏情况

  • 核心思想:对随机性系统做压力评估时,无反馈的随机扰动给出的是影响下界——「普通的一天能坏到哪」,比对抗最坏情况(「最糟的一天能坏到哪」)更贴近部署者决策需求,且实现便宜得多。
  • 论文证据:本文用随机采样器而非反馈引导优化(Srikant et al. 2021 式),明确声明「A feedback-guided adversary can only do more damage」,把全部结论定位为下界。
  • 推广场景:(1)评估 RAG 系统对文档格式变化的容忍度;(2)评估推荐系统对物品元数据改写的稳定性;(3)混沌工程里随机故障注入 vs 精心构造的故障场景;(4)评测自动驾驶感知栈对传感器噪声的鲁棒下界。

8.2 「配对多次运行 + 两层方差分解」是分离「干预效应」与「内在随机性」的通用解法

  • 核心思想:任何随机性系统(LLM Agent、自动驾驶、交易算法)在评估干预效果时,必须先量化内在波动,再把干预条件与基线配对比较;在固定预算下,「每变体一次 × 多变体」优于「少变体 × 多重复」。
  • 论文证据:Var(μ̂) = (Kσ²+ν)/R 的推导 + 20 基线 × 20 变体各 1 次的设计;若不做配对,单次成功/失败的差异毫无归因力。
  • 推广场景:(1)A/B 测试中处理多层级随机性(用户 × 会话);(2)药物临床试验的配对交叉设计;(3)评估 prompt 工程改动对 Agent 的真实增益(区分 prompt 效应和采样运气);(4)强化学习策略评估中的环境变体采样。

8.3 复杂系统的「鲁棒性」是联合属性,单轴排名会系统性误导选型

  • 核心思想:当系统的某个品质(鲁棒性/安全/延迟)由多组件联合决定时,任何单轴排行榜(只按模型、只按框架)都不具备跨场景迁移力——必须声明并测试「配置空间」。
  • 论文证据:Qwen 在框架轴上从最鲁棒(0.2 pp)翻到最脆(5.5 pp);Opus 在基准轴上从 1.8 pp 到 6.7 pp;每个模型都当过最优也当过最差。同时榜单式能力排名在扰动前后竟然纹丝不动——说明榜单恰恰测不到鲁棒性。
  • 推广场景:(1)采购决策中「最优模型」结论必须附带测试环境描述;(2)数据库选型:同一引擎在不同负载模式下排名翻转;(3)安全评估:一个模型在一种对齐框架下安全、另一种下失守;(4)招聘/评估中的「情境依赖绩效」——脱离上下文的能力分数意义有限。

8.4 只看结果指标会系统性低估干预成本——把「努力/开销」列为一等指标

  • 核心思想:结果不变 ≠ 影响不存在。系统可能靠燃烧更多资源维持结果,评估必须同时记录结果与成本两个维度。
  • 论文证据:多数配置解决率只有小幅退化,但 SWE-bench Verified 全部 8 个配置 token 成本显著上升(4.0%~22.9%);步数涨幅(≤9.9%)远小于成本涨幅(≤22.9%),暴露「每步变贵」这一隐藏维度。
  • 推广场景:(1)模型服务监控:准确率持平但延迟/成本上升是劣化的前兆信号;(2)员工绩效:产出不变但加班时长飙升的隐性损耗;(3)数据库查询优化:结果相同但扫描量暴增;(4)心理认知负荷任务的表现-努力分离。

8.5 「诱饵注入」揭示了一切检索依赖系统的通用攻击面

  • 核心思想:任何依赖「表面签名检索」(关键词、名称、格式)来定位目标的系统,都可以被廉价的诱饵注入稀释信噪比——不需要改变任何实质内容。
  • 论文证据:Dead String Assignment / Dead Method Injection 绑定 issue 关键词后,Opus 在 Openlibrary 上被多个同关键词噪音带偏;Agent 必须分辨「词法醒目」与「行为相关」。
  • 推广场景:(1)安全领域:蜜罐与日志投毒防御;(2)SEO 垃圾站点对搜索引擎的信号稀释;(3)RAG 系统防注入:知识库中的干扰文档会稀释检索质量;(4)对 Agent 的提示注入防御——本文的诱饵本质上是一种无害化的注入攻击实验。

8.6 中间层越多的系统,噪音放大链越长——「简单性即鲁棒性」

  • 核心思想:处理链条上每个中间层(子 Agent、摘要器、结构化工具)都在对上游输入做再解释,输入退化会沿链放大;直接暴露原始信息的最短链路反而更抗噪。
  • 论文证据:单一 bash 的 mini-SWE agent 在两个基准上平均退化都低于多子 Agent 的 OpenCode(1.34 vs 1.88、1.88 vs 3.65 pp),且该结论在基线能力翻转的情况下依然成立;成本上 OpenCode 在 Pro 全线 +8.8~12.6% 而 mini-SWE 近零。
  • 推广场景:(1)微服务架构:链路越长,单点异常放大越严重;(2)组织信息传递:层级越多,原始信号失真越大;(3)多 Agent 系统设计:编排层的收益要与噪音放大成本权衡;(4)数据管道:每一步 ETL 都是潜在的信号损失点。

附录:关键数字速查

项目数值
SPT 目录规模14 种(9 改写 + 5 注入,其中 2 种 keyword-bound)
差分验证SymPy 12,994 + sqlfluff 10,060 + xarray 19,917 个测试,全保持
采样超参N=20, Nt=3, Nk=5, Nf=10, φ=0.7
扰动幅度(中位)Verified 6.9% 源码行 / Pro 7.7%
配置矩阵2 框架 × 4 模型 × 2 基准 = 16;实例 28+26=54;每配置 54×40=2160 次运行
平均退化13/16 为正;6/16 显著;最大 6.7 pp(mini-SWE+Opus/Pro)
框架均值对比mini-SWE 1.34 vs OpenCode 1.88(Verified);1.88 vs 3.65(Pro)
Qwen 框架翻转0.2 pp(最鲁棒)↔ 5.5 pp(最脆)
努力开销步数 ≤+9.9%;成本 ≤+22.9%(Verified 8/8 配置显著上升)
每步成本12/16 配置上升;Opus 压缩轨迹:步数 -19.8%、每步输出 token 中位 +101%
退化集中度432 个实例-配置中 14 个显著;3 个实例贡献 6.7 pp 的约 2/3
能力排名稳定性4 个面板中 3 个扰动前后排名不变(榜单测不到鲁棒性)
统计工具bootstrap B=20,000 / Newcombe 95% / temperature 1.0

一句话总结:这篇论文用一套严谨到近乎苛刻的实验设计告诉我们——代码 Agent 的鲁棒性不是模型的属性,而是「模型 × 框架 × 代码库」三元组的属性;它锯齿状地分布在这个配置空间里,榜单看不见它,但它决定着你换一个代码库之后,Agent 是稳如老狗还是当场翻车;而且哪怕它稳住了,你的 token 账单也已经悄悄涨了两成。