news 2026/7/24 23:19:00

水木清华联手滑铁卢大学:让AI编程助手“看懂“工具返回值

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
水木清华联手滑铁卢大学:让AI编程助手“看懂“工具返回值

这项由加拿大滑铁卢大学、英属哥伦比亚大学、NVIDIA、Verdent AI和Vector研究院联合开展的研究,以预印本形式于2026年7月14日发布,论文编号为arXiv:2607.12463。有兴趣深入了解的读者可通过该编号在arXiv平台查询完整论文。

在AI写代码这件事上,最难的其实不是"从零开始写",而是"出错之后能不能自己修"。

当一个AI编程助手在真实的代码仓库里工作时,它的日子并不好过。它需要先查看文件,再尝试修改,然后运行测试——测试失败了,报错信息扑面而来,它得读懂这些错误,判断是哪里出了问题,然后再次修改。这个"行动→收到反馈→继续"的循环,对AI来说是个真正的挑战。主流的代码语言模型在训练时,基本上是按照从左到右、从上到下的方式阅读代码,它们对"先做一件事,收到外部结果,再继续"这种节奏本质上缺乏感知。

研究团队注意到了一个有趣的规律:AI助手在执行任务时经历的"行动→收到工具返回结果→基于结果继续"这个三步循环,和普通代码里一个函数调用的结构几乎一模一样。当你写了一行 `result = process(data)`,这里发生的事情是:调用前的代码设定了意图和参数,`process` 函数被调用,函数内部做了一些你在外部看不到的计算,把结果返回给你,然后你用这个结果继续往下写。这四个步骤——背景、行动、外部计算的返回结果、后续处理——和AI助手执行任务的四个步骤是结构上完全相同的。

既然如此,能不能利用互联网上海量存在的普通代码,来训练AI理解这种"行动-反馈-继续"的逻辑呢?这就是这项研究的核心思路。

**一、问题所在:现有训练方式留下了一个缺口**

要理解这项研究在填补什么空白,先来看看AI编程助手是怎么被训练出来的。

整个流程大致分两个阶段。第一阶段叫"预训练",是把模型暴露在海量代码面前,让它像背课文一样学会预测"下一个词是什么"。这个阶段的训练用的是互联网上能找到的所有代码,规模巨大,但方式很简单——从左到右,一个接一个地预测。

第二阶段叫"智能体后训练",是专门针对"修复真实代码bug"这类任务,准备一批AI执行任务的完整轨迹(行动记录)来训练模型。这个阶段是最近几年让AI代码修复能力大幅提升的关键。R2E-Gym、SWE-Smith、SWE-Lego这些知名的训练框架都属于这类方法。

两个阶段之间存在一个空档:模型在第一阶段培养了阅读代码的基本能力,但这种从左到右的训练方式,天然地只让模型练习了"往前看",没有好好练习"收到外部结果之后怎么继续"。第二阶段的任务轨迹数据虽然很有针对性,但数量有限,且很贵——需要大量人工或AI来生成这些轨迹。

研究团队的想法是在两个阶段中间插入一个"中间训练"阶段,利用更廉价、更大量的普通代码来建立一种惯性:让模型在真正接触任务训练之前,就已经养成了"根据前后文推断中间缺失内容"的思维方式。

这种"填空"训练方法学术上叫"填中间"(FIM,Fill-in-the-Middle)。它的基本操作是:把一段代码的中间部分遮住,让模型根据前面的代码和后面的代码,把中间这部分补出来。这和完形填空非常相似,只不过不是一个词,而是一整段代码逻辑。

问题在于,之前的代码模型虽然也用过这种填空训练,但那些训练都是随机挖一段出来填,就像随机撕掉一本书的某几页,有时撕掉的是一个完整的故事情节,有时撕掉的只是一句话中间的几个字,参差不齐,对培养"理解函数调用结构"这种具体能力帮助有限。

**二、核心创新:按照函数来挖,而不是随机挖**

研究团队设计的方法叫"函数感知填中间中间训练"。关键区别在于,它不随机挖一段代码,而是有针对性地挖掉一整个函数的函数体,让模型根据调用这个函数的代码、被这个函数调用的其他函数、以及函数本身的签名和文档,来推断这个函数应该怎么写。

这个挖法之所以重要,是因为一个函数就是一个完整的"外部计算单元"——它收到参数,做一些调用者看不见的内部工作,返回结果。这和AI助手调用工具(比如执行终端命令、搜索文件)时的情形完全类似:AI看到命令返回了什么,要根据这个返回结果决定下一步怎么做。

为了选出"值得挖掉"的函数,研究团队设计了一套双重评分体系。可以把它理解成在一栋楼里选哪个房间做示范单位:房间得有足够的内容展示(不能太简单),但参观者进来之前能从门口的标牌、周围房间的布局推断出里面大概是什么样子(不能完全不可推断)。

评估"值不值得挖"用的第一个指标叫"复杂度分",衡量的是这个函数本身有多少内容。计算方式综合了三个维度:代码行数(越长越复杂)、圈复杂度(代码里有多少if/for/while这样的分支,分支越多逻辑越复杂)、以及最深嵌套层数(if里面套for里面再套if这样的层叠有多深)。这三个维度各有权重,综合成一个0到2之间的分数。

第二个指标叫"可推断分",衡量的是周围代码有多少线索可以帮助推断出这个函数该怎么写。线索来源有五种:调用这个函数时传入了什么参数(越具体越好)、这个函数内部调用了哪些同文件里的其他函数(越多说明逻辑越有迹可循)、函数名和参数的类型注解有多描述性(名字越清楚越容易猜)、有没有文档字符串(有说明更好推断)、以及这个函数在类里有多少兄弟方法共享状态(兄弟越多上下文越丰富)。

最终的选取分数是把复杂度和可推断性用类似调和平均数的方式结合起来——要求两者都不能太低。一个函数如果很复杂但完全不可推断,或者很容易推断但太过简单,都不是好的训练材料。此外还有一个"难度惩罚":如果一个函数复杂度远超可推断性,说明即使给了全部上下文也很难推断出来,这种函数会被降权,因为让模型去猜一个连人类都猜不出来的答案,只会产生噪音。

除了单个函数,研究团队还设计了"多函数组合"的挖法:同时挖掉2到3个有相互调用关系或同属一个类的函数。这是因为真实的代码修复任务经常需要同时修改多个相关函数,这类训练样本能帮助模型练习跨函数的逻辑推理。两个函数的组合大约占训练数据的15%,三个函数的组合占5%,其余80%是单函数。

**三、用AI来生成思考过程**

光有"挖空填补"还不够。研究团队注意到,真实的AI助手在修代码时,通常是先思考(输出一段分析),再动手写代码。如果训练数据里只有代码本身,模型就只练了"动手",没练"先想清楚再动手"。

所以在构建训练样本时,研究团队引入了一个额外步骤:对于每一个被挖空的函数,先让另一个强大的AI(谷歌的Gemini 3 Flash)只看前后代码(不看被遮住的函数体),生成一段推理过程,说明这个函数应该做什么,然后再给出对应的实现代码。

之后用另一轮Gemini 3 Flash对这个生成的(推理过程,代码实现)配对进行质量审核,评分维度包括:这个函数从上下文推断出来是否合理可行(如果依赖完全无法从代码中感知的外部知识,就标记为不可行)、代码的正确性、可执行性、API使用是否恰当、可读性和完整性。只有通过审核的样本才进入训练数据。

被遮住的真实函数体只用于审核,不出现在训练目标中。模型学到的是:看到前缀和后缀,先输出一段推理,再写出实现。

这样构建的训练样本格式是:`[前缀代码]` + `[后缀代码]` + `[推理过程] [函数体代码]`。前两块是输入,后两块是模型需要生成的目标。

**四、数据从哪里来**

研究团队从GitHub上精心挑选了968个Python代码仓库,作为中间训练的数据来源。最初考察的候选库大约有2000个,经过手动质量筛查后缩减。所有与SWE-Bench(用于评估AI修复真实GitHub Issue能力的标准测试集)来源仓库有重叠的都被移除,每个仓库只保留了在SWE-Bench测试集基准提交时间节点之前的代码,确保不存在测试数据泄露。

最终筛出大约78000个符合条件的Python文件,从中生成了约40万条填空训练样本,合计约26亿个token(使用Qwen2.5-Coder的分词标准计算)。其中单函数样本约32万条,双函数组合约6万条,三函数组合约2万条。每个样本的函数体平均长度约为34行代码。这40万条样本全部配备了Gemini 3 Flash生成的推理过程。

这968个仓库覆盖了10个类别,从头实现的项目、领域专用工具、算法库、科学计算、小型框架、可视化与游戏、教育类项目、编译器、数据处理和网络安全都有涉及,许可证方面超过80%是MIT、Apache 2.0或BSD等宽松许可,其余也均允许至少用于非商业研究。

**五、训练流程:中间插了一个阶段**

具体的训练方式是:先取已经经过指令微调的基础模型(研究中用了Qwen2.5-Coder-7B-Instruct、Qwen2.5-Coder-14B-Instruct和Qwen3-8B),在这26亿token的填空数据上做一轮中间训练,训练目标只是推理过程加函数体这个部分,前缀和后缀不参与损失计算。训练使用模型原生的填空特殊符号,打包到模型的原生最大上下文长度,跑一个epoch。

这个中间训练阶段用的超参数:学习率1e-5,余弦学习率计划,预热比例10%,权重衰减0.05,每设备批量大小1,梯度累积16步,等效全局批量128,序列长度32768,混合精度bf16。

中间训练完成后,再正常接上智能体后训练阶段(R2E-Gym、SWE-Smith或SWE-Lego),和没有中间训练的基准对比。

之所以不直接评估只做了中间训练的模型,是因为填空训练之后模型的指令跟随能力会下降,没法公平地和经过完整指令微调的基准比较。所有公布的数字都是完整流程(中间训练加后训练)结束后的表现。

**六、测试结果:每个配置都在变好**

研究在三个维度验证了这个方法的有效性。

第一个维度是不同模型规模。在Qwen2.5-Coder-7B-Instruct上,中间训练加R2E-Gym后训练,在SWE-Bench-Verified上提升了2.8个百分点,在SWE-Bench-Lite上提升了3.67个百分点。在14B的版本上,同样的流程带来了3.0和4.0个百分点的提升。这说明更大的预训练模型并没有自然地"吸收"这种结构性偏置,中间训练带来的改进在两个规模上都实实在在地存在。

第二个维度是不同的后训练流程。在同一个7B基础模型上,换用SWE-Smith代替R2E-Gym作为后训练框架,中间训练在SWE-Bench-Verified上的提升高达5.3个百分点(虽然在Lite上只提升了0.5个百分点,说明具体数字取决于后训练流程和评测集的组合,但方向上始终是正向的)。

第三个维度是不同的基础模型家族。换成Qwen3-8B加上SWE-Lego后训练,中间训练在SWE-Bench-Verified上提升了3.2个百分点,在Lite上提升了5.4个百分点。由于这里同时换了基础模型和后训练框架,研究团队谨慎地说这只能说明方法"不局限于Qwen2.5-Coder加R2E-Gym的特定组合",而不是对所有模型家族的普适性保证。

**七、意外收获:通用能力的保留**

智能体后训练有一个代价通常不被人提起:专攻代码修复任务之后,模型在其他方面的能力往往会大幅退步。研究团队专门测试了14B模型在六个额外基准上的表现,结果令人警醒。

只做R2E-Gym后训练(不加中间训练),模型在LiveCodeBench(纯代码生成能力测试)上比原始指令模型下降了13.1个百分点,在BFCL(函数调用能力测试)上下降了7.4个百分点,在FullStackBench-EN上下降了6.08个百分点,在τ-bench(模拟真实场景的工具使用测试)上下降了2.3个百分点。六个基准平均下来,后训练之后模型失去了4.81个百分点的综合能力。这是为了SWE-Bench成绩支付的隐性代价。

加入中间训练之后,情况发生了显著变化。LiveCodeBench回升了11.1个百分点,OJBench(竞赛编程测试)回升了1.94个百分点(仅距原始指令模型0.46个百分点),FullStackBench-EN回升了0.53个百分点,τ-bench回升了3.9个百分点,BFCL回升了2.4个百分点,Terminal-Bench 2.0回升了1.25个百分点。六个基准的综合平均从16.04上升到19.56,同时SWE-Bench的增益也得到了保留。

更有意思的是τ-bench和BFCL的提升。这两个测试里没有任何Python代码编辑相关的内容,而中间训练的语料库里也完全没有工具使用相关的训练数据。两者的提升只能解释为:函数调用结构和工具调用结构在某种深层次上是等价的,中间训练建立的"接收外部返回结果并继续"的认知惯性,通用地改善了模型处理任何"行动-反馈-继续"循环的能力。

**八、逐一拆解:每个设计决策贡献了多少**

为了验证每个设计决策是否真的有用,研究团队在7B模型上做了三组对照实验,每组控制其他变量只改变一个因素,使用20万条样本的固定预算以确保可比性。这些实验的绝对数字低于主实验(因为数据量更少),但组内的相对大小是可比较的。

第一组对照是"加不加推理过程有多大用"。完全不加推理过程,只做填空结构训练,平均分比基准提升1.18个百分点。换成让被训练的模型自己生成推理过程(而非用Gemini),提升增加到1.68个百分点的回收。用Gemini生成的推理过程,总提升是2.43个百分点。也就是说,填空结构本身贡献了大约一半的改进,推理过程提供了额外的增益,但Gemini相比模型自身推理的额外贡献只有0.75个百分点。这说明这个方法不仅仅是一个"蒸馏Gemini"的方案,填空结构本身就在干实事。

第二组对照是"怎么选函数有多重要"。随机挖函数只比基准提升了0.78个百分点;让Gemini来判断挖哪个函数,提升到了1.88个百分点;只用程序依赖图关系来筛选(有调用关系的函数更倾向被选),提升到了1.68个百分点;加上复杂度过滤或可推断性过滤各自有额外改进,两者都用效果最好,达到2.43个百分点。这说明函数选择的质量是决定中间训练效果的关键变量,而且复杂度和可推断性提供了互补的不同维度信息。

第三组对照是"同时挖多个函数有没有用"。只挖单个函数:提升2.43个百分点。加入15%的双函数组合样本:提升到2.73个百分点。加入5%的三函数组合(替换同等比例的单函数):提升到2.53个百分点。同时加入双函数和三函数(80%/15%/5%的组合,也就是主实验中用的配方):提升到2.93个百分点。多函数组合在帮助那些金标答案需要同时修改多个函数的任务上效果更明显,但三个函数同时挖的边际收益因为可推断性大幅下降而受到限制。

**九、深入轨迹:模型到底学到了什么**

为了理解改进从哪里来,研究团队分析了14B模型在SWE-Bench-Verified上的行为轨迹。

研究团队定义了一个叫"从错误中恢复"的指标:一条轨迹被认为包含"负面观察",如果在执行过程中任何工具的输出匹配了错误模式(Python异常回溯、"未执行替换"、shell错误等);"恢复率"是指,在包含负面观察的轨迹中,最终仍然成功提交了有效修复的比例。

基准模型(只有R2E-Gym后训练)有88.8%的轨迹碰到了负面观察,加了中间训练的模型这个比例是91.8%——也就是说两者看到的错误数量基本相当。但基准模型的恢复率是24.8%,加了中间训练的是28.8%,高出了整整4个百分点(相对提升16%)。

加了中间训练的模型还表现出更倾向"迭代验证"的工作风格:在成功解决的任务上,平均编辑操作次数从3.3次上升到7.4次,平均轨迹步骤数从15.1步增加到23.6步。代价是碰到步骤上限的轨迹比例从7%上升到45%,但这些"超步"主要发生在未解决的任务上;在已解决的任务上,额外的步骤都转化成了更正确的修复。

按照金标答案的补丁类型来分层分析,结果进一步验证了研究的核心假设。在341个只需要修改单个函数的任务上,中间训练的提升是2.1个百分点;而在88个需要同时修改同一文件里多个函数的任务上,提升高达9.1个百分点,是单函数任务的4倍多。这正好对应了中间训练的内容——多函数组合填空训练让模型更擅长跨函数的逻辑推理。

在71个需要跨文件修改的任务上,两个模型的表现几乎一样(约11.3%),没有差异。研究团队解释说,他们的填空训练是在单个文件内部进行的,跨文件协调能力没有被直接训练,所以在这类任务上没有体现出优势。

失败模式的分布也有明显变化。基准模型平均每轮评估有约11条轨迹以"空补丁"结束(AI完全没有提交任何修改就放弃了);加了中间训练之后,这个数字下降到约1条,基本消失。定位错误(提交了修改但没修改对文件)轻微减少(131条降到约126条),补丁错误(改了对文件但测试还是没过)基本不变(约227条)。所以增加的15个成功解决的任务,主要来源是那些"AI放弃了但其实有希望"的案例。

研究团队的解释是:在填空训练中,模型总是被要求在前缀和后缀之间生成一个非空的内容。这种"必须生成内容"的惯性在后训练之后存活了下来,让模型不容易放弃、不容易直接交空卷。

**十、说说局限性**

研究团队在论文中明确指出了四个边界。

首先,整个训练语料库和测试基准都是Python。跨语言的迁移能力没有被直接测试,Java、C++、Rust能不能受益,还不知道。FullStackBench-EN的间接证据显示对多语言编码有一定帮助,但不够直接。

其次,默认配方依赖Gemini 3 Flash生成推理过程。虽然消融实验表明模型自身生成的推理过程可以回收大部分收益,但对于想要完全开源复现的团队来说,需要一个同等能力的开源教师模型,这不是随手可得的。

第三,在非Qwen2.5-Coder模型上的验证只有一个配置(Qwen3-8B加SWE-Lego),而且这个配置同时换了基础模型和后训练框架。所以只能说方法不局限于特定组合,但不能说它在所有模型家族上都保证有效。

第四,整个方法建立在代码有良好模块化结构的假设上。如果是单体脚本、自动生成的代码或Jupyter Notebook这种没有清晰函数边界的代码,选函数的流程就找不到足够的候选目标,这个场景没有被系统研究。

说到底,这项研究提供的是一个可以在现有训练流水线里"插入"的额外步骤,不需要修改后训练框架本身,只需要在进入后训练之前多跑一轮中间训练。代价是额外的计算(研究团队完整复现整套实验大约需要5760个GPU小时,在8张H100上跑约30天),换来的是在代码修复任务上稳定的3到5个百分点的提升,以及对通用代码和工具使用能力的大幅保留——这种"修了一件事,顺带修了另外几件事"的结果,在AI训练里并不常见。

归根结底,这项研究的出发点来自一个朴素的观察:AI修代码时需要的那种"看懂工具返回了什么,然后继续"的能力,其实在普通代码里到处都是,只是之前的训练方式没有把这个结构显式地暴露给模型。通过按照函数边界来挖空填补,配合推理过程的训练,再放在任务训练之前的正确时间节点上,这种现成的信号就被有效地利用起来了。

有兴趣深入了解细节的读者,可以通过arXiv编号2607.12463查阅原始论文,研究团队的代码和数据集也已在GitHub的TIGER-AI-Lab/FIM-Midtraining仓库公开发布。

---

Q&A

Q1:SWE-Bench是什么,用它来测试AI修代码能力靠谱吗?

A:SWE-Bench是一个用真实GitHub Issue来测试AI代码修复能力的标准测试集,分为Verified(500个经人工验证的问题)和Lite(300个问题)两个版本。测试题目都是从真实开源项目里抽取的,有标准的参考修复方案,评判方式是提交的修改能不能让原来失败的测试通过。这是目前业界公认的衡量AI代码智能体能力的主要基准之一,被普遍认为比合成测试更能反映真实能力。

Q2:函数感知填中间中间训练需要多少计算资源,普通研究团队能复现吗?

A:研究团队使用8张NVIDIA H100 80GB显卡组成的单节点服务器,完整复现所有实验(三个基础模型的中间训练、三套后训练框架及其对应的中间训练版本、消融实验和多轮评估)大约需要5760个GPU小时,折合约30天。数据生成阶段还调用了Gemini 3 Flash的API来生成推理过程,这部分有额外的API费用。代码和数据集已在GitHub开放,感兴趣的团队可以选择性地复现部分实验而非全套,计算成本会相应降低。

Q3:为什么只做SWE-Bench成绩好的后训练模型,在普通代码生成测试上反而变差了?

A:这是专项后训练的常见副作用,本质是"过度专业化"。后训练用的数据都是AI修复GitHub Issue的完整行动轨迹,模型学习的是"在真实代码仓库里查文件、定位问题、做修改、看测试结果"这套特定工作流。这种分布的数据反复训练,会让模型的参数逐渐向这个窄分布倾斜,挤压掉一部分通用代码能力和工具调用能力。中间训练建立的函数调用结构惯性在某种程度上充当了通用能力的"锚点",所以加入中间训练之后,专项能力提升的同时通用能力的退化也得到了部分抑制。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/24 23:17:09

计算机基础·计算机组成原理

文章目录体系结构通识冯诺依曼架构:计算单元存储器输入/输出设备控制器程序的编译过程:预处理-编译-汇编-链接预处理器:替换宏定义和头文件为具体的函数或者内容编译器:将高级程序语言(C语言)替换为低级通用的程序语言(汇编语言)汇…

作者头像 李华
网站建设 2026/7/24 23:15:07

TLV320ADC3101低功耗音频ADC:集成miniDSP的硬件设计与配置实战

1. 项目概述与核心价值在便携式音频设备、无线通信终端和主动降噪系统等应用中,如何以极低的功耗实现高质量的音频采集与处理,一直是硬件工程师面临的核心挑战。传统的方案往往需要将高性能的模数转换器(ADC)与独立的数字信号处理…

作者头像 李华
网站建设 2026/7/24 23:15:02

AI应用开发四层技术栈:MCP协议与模块化实践

1. AI应用开发四层技术栈全景解读当我在2023年首次接触金融大模型问答机器人项目时,面对琳琅满目的技术方案曾一度陷入选择困难。直到梳理出MCP-Skills-Tool Calling-SubAgent这套分层架构,才真正打通了AI应用开发的任督二脉。这套架构就像建造摩天大楼的…

作者头像 李华
网站建设 2026/7/24 23:14:59

Linear Loops功能详解:简化循环工程操作与自动化工作流

Linear 最近发布了 Loops 功能,这是一个专门为简化循环工程操作而设计的新工具。如果你经常需要处理重复性任务、批量操作或者需要自动化的工作流程,Loops 可能会成为你的新利器。这次我们重点看看它的核心功能、使用门槛、实际效果以及如何快速上手。Lo…

作者头像 李华
网站建设 2026/7/24 23:11:22

Claude Code v2.1.216性能优化:解决长会话卡顿与Agent行为异常

如果你正在使用 Claude Code 进行 AI 辅助编程,最近是否遇到过这样的困扰:代码编写到一半,编辑器响应越来越慢,甚至出现卡顿?或者你的 Agent 在执行复杂任务时行为异常,无法按预期完成工作?这正…

作者头像 李华