Function-Aware Fill-in-the-Middle:面向 Coding Agent 基础模型的中期训练
- 关联论文:2607.12463
- 作者:spark
- 更新:2026-07-20
一句话结论
本文提出 Function-Aware Fill-in-the-Middle(FIM) 作为 Coding Agent 基础模型的中期训练(mid-training) 自监督目标:通过程序依赖图分析与"复杂度可推断性"双重标准筛选可遮蔽函数,让 Qwen2.5-Coder-Instruct(7B/14B)与 Qwen3-8B 在 SWE-Bench-Verified 上提升 +2.8 ~ +3.2、在 SWE-Bench-Lite 上提升 +3.7 ~ +5.4,且缓解了 Agentic post-training 对通用编程与工具调用能力造成的侵蚀。
解决的真问题
Coding Agent 的工作流是 "动作(Action)→ 观察(Observation)→ 继续(Continuation)" 的循环:模型发出工具调用,环境返回结果,模型接着推理。这与一个经典编程结构同构:函数调用点(call site)把实参绑定给被调函数,被调函数返回计算结果,下游代码消费这个结果。
但标准的"从左到右"的代码预训练只把这种结构以正向(forward)形式暴露给模型,反向(mask 整个函数体、给定上下文推断函数实现)从未被显式训练。结果是:
- Coding Agent 缺乏"在已有调用点处插入正确函数实现"的归纳偏置,遇到新工具、新 API 时往往需要大量 prompt 才能对齐调用格式;
- Agentic post-training 改善了 Agent 能力,却常常以"非 Agent 编程能力下降"为代价(如 LiveCodeBench、HumanEval 上的退步),这种现象被称为"能力侵蚀(capability erosion)";
- 现有的 FIM(Fill-in-the-Middle)训练通常是随机遮蔽任意一段代码,没有针对函数调用结构设计,归纳偏置弱。
FIM mid-training 直击这三个问题,把"函数调用结构"作为一等训练信号灌进基础模型。
核心方法
1. 关键观察:Action-Observation-Continuation ≡ 函数调用点
作者提出 Coding Agent 的工具调用循环与编程语言里的函数调用点在结构上同构:
| Coding Agent 工作流 | 函数调用点 |
|---|---|
| Action:模型发出工具调用 | caller:绑定实参、发起调用 |
| Observation:环境/工具返回值 | callee:在某处(定义处)实现函数 |
| Continuation:模型消费返回值继续推理 | downstream code:消费 callee 的返回值 |
这意味着互联网上普通代码里无处不在的函数调用结构,本身就是 Action-Observation-Continuation 模式的海量天然样本。无需专门构造 Agent 数据。
2. 训练目标:Function-Aware FIM
标准的 FIM 任务是把一段代码按位置切三段(前缀 prefix / 中间 suffix / 后缀 suffix,原版 FIM 是 prefix / middle / suffix),遮蔽 middle 让模型补全。Function-Aware FIM 的关键差异:遮蔽单元是函数(function),而非任意位置,且函数选择满足两个条件:
(a) 程序依赖图(program dependency graph, PDG)分析
用静态分析构建代码的 PDG,控制流与数据流依赖显式化。只有被其他代码通过函数调用依赖的函数(即"被调用的函数")才被选为遮蔽目标。这样模型学到的不是任意代码片段的补全,而是"在已有调用约束下实现函数"的归纳偏置。
(b) 复杂度可推断性(complexity-inferability)双重标准
仅靠 PDG 还不够 —— 一些被调函数的实现过于简单(如一行 return x),遮蔽它们学不到有用的归纳偏置;另一些函数实现过于复杂(如上千行),无法在自监督下可靠学习。作者引入"复杂度可推断性"作为第二道筛选:函数体长度在合理区间(原文未明确具体上下界),且函数逻辑可从调用上下文与被调函数签名推断。
两个标准叠加,最终筛选出的"可训练函数"集合在 968 个 GitHub 仓库、2.6B token 的去重/去污染语料上展开 mid-training。
3. 模型与训练流程
- 基础模型:Qwen2.5-Coder-Instruct(7B / 14B)、Qwen3-8B;
- 语料:2.6B token 去污染语料(decontaminated against benchmarks),来自 968 个 GitHub 仓库;
- 目标:self-supervised FIM 任务,遮蔽函数体、给定上下文(imports、调用点、其他函数)预测函数实现;
- post-training:在 mid-training 后直接套用既有 Agentic post-training 流程,包括 R2E-Gym、SWE-Smith、SWE-Lego(用于非 Qwen2.5 基座验证)。
4. 评估协议
论文同时评估领域内与领域外能力:
- 领域内(Coding Agent):SWE-Bench-Verified、SWE-Bench-Lite —— 看 Agent 解决真实 GitHub issue 的能力;
- 领域外(非 Agent 编程):LiveCodeBench —— 看通用代码生成是否被牺牲;
- 领域外(非编程工具调用):tau-bench、BFCL —— 看通用工具调用能力是否被牺牲。
最后两类用于量化"能力侵蚀"。
关键实验与数据
关键发现 1:领域内大幅提升
- SWE-Bench-Verified:Qwen2.5-Coder-Instruct 7B 提升 +2.8、14B 提升 +3.0;Qwen3-8B 提升 +3.2;
- SWE-Bench-Lite:三个模型分别提升 +3.7 / +4.0 / +5.4(7B / 14B / Qwen3-8B)。
SWE-Bench-Lite 的提升大于 Verified,说明 FIM mid-training 对"难度中等、需要多次工具调用"的场景帮助更大,因为这类场景正是 Action-Observation-Continuation 模式密集出现的地方。
关键发现 2:跨 post-training 流程稳定
增益在 两个不同的 post-training 流程(R2E-Gym、SWE-Smith)以及一个非 Qwen2.5 基座(Qwen3-8B 用 SWE-Lego)上都成立。这说明 FIM mid-training 带来的归纳偏置是与具体 post-training 流程解耦的,可作为通用前置训练阶段。
关键发现 3:缓解 Agentic post-training 的能力侵蚀
这是论文最有产品落地意义的发现之一:agentic post-training 通常会让非 Agent 编程(LiveCodeBench)和非编程工具调用(tau-bench、BFCL)能力下降,但前置 FIM mid-training 能部分逆转这种侵蚀 —— mid-training 学到的"函数调用归纳偏置"在 post-training 后仍然保留,且对外溢到非 Python 工具调用任务(虽然 mid-training 语料只含 Python)。
具体提升/缓解幅度(百分点)原文未在 abstract 中给出,应在正文表格中。
亮点与局限
亮点
- 洞察精准:Action-Observation-Continuation ≡ 函数调用点的同构观察,把 Agent 训练问题转化为"已有海量数据"问题;
- 零人工标注:完全 self-supervised,语料来自 968 个开源仓库,2.6B token;
- 筛选严谨:PDG + 复杂度可推断性的双重标准,比随机 FIM 更聚焦;
- 跨流程、跨基座稳定:在两个 post-training 流程与两个基座上都成立;
- 缓解能力侵蚀:这是对工程界普遍的"Agent 训练 = 通用能力下降"问题的一个非常实际的解法。
局限
- 依赖静态分析质量:PDG 分析对动态语言、宏、反射的支持有限,可能漏选一些本应被遮蔽的函数;
- 2.6B token 相对较小:相比主流预训练语料是几个数量级的差距,mid-training 的提升有多大程度来自这 2.6B token、有多大程度来自"mid-training 这一阶段本身"未做消融(原文未明确);
- 仅 Python 语料:论文观察到 Python-only 语料学到的偏置仍能外溢到非 Python 工具调用,但其他编程语言的迁移性未充分验证;
- 复杂度可推断性的具体阈值未明确:影响复现;
- SWE-Bench 仍是主要指标:但 SWE-Bench 与真实产品环境仍有差距(如依赖环境配置、复杂多文件工程);
- 未给出训练成本明细:2.6B token 的 mid-training 在 7B/14B 上需要多少 GPU 小时,原文未明确。
对工程落地的启发
- 训练 Coding Agent 基础模型时:在通用预训练与 RLHF/Agentic post-training 之间插入一个 Function-Aware FIM mid-training 阶段,可同时提升 Agent 能力与缓解通用能力侵蚀;
- 构造 FIM 数据时:不要随机遮蔽,应优先选择"被调用、复杂度适中"的函数,遮蔽单元比位置选择更重要;
- 跨语言外溢:Python-only 语料的 FIM mid-training 已能外溢到非 Python 工具调用任务,说明程序语言层面的归纳偏置是相当通用的,可以用最低成本的语料获得最广的收益;
- 能力侵蚀问题:在做 Agentic post-training 时,监控"非 Agent 编程"与"非编程工具调用"基准(LiveCodeBench、tau-bench、BFCL)的能力变化,必要时前置一个 FIM mid-training 阶段作为"防侵蚀缓冲";
- 与其他 mid-training 任务的组合:可以探索在 FIM 之外,叠加"工具描述预测""错误信息诊断"等任务,进一步强化 Agent 偏置。
与同方向工作的关系
- FIM / 填空训练:本文是 FIM 思想在 Coding Agent 场景的精细化升级,从"任意位置遮蔽"到"按函数调用结构遮蔽";
- Agentic post-training 缓解能力侵蚀:与近期"如何让 Agent RL 不伤害通用能力"的工作(如 NeMo、Open RLHF、RLHF workflow studies)直接相关;
- SWE-Bench 系列评测:与 R2E-Gym、SWE-Smith、SWE-Lego 等 Agent 训练框架互补,本文在这些框架上加了一个 mid-training 阶段;
- 代码预训练数据工程:与 StarCoder、Code Llama、Qwen2.5-Coder 等代码基础模型的训练数据筛选工作同源,但本文聚焦"对 Agent 有用的子集";
- 程序语言与工具使用的同构:与 Toolformer、ReAct 等"LLM 调用工具"工作共享"语言模型可以学会工具调用结构"的洞察,但本文把这一洞察下沉到 mid-training 阶段。
适合谁读
- Coding Agent 训练工程师 —— 关心 post-training 能力侵蚀、希望前置训练阶段的人;
- 大模型数据工程师 —— 探索 FIM 类自监督目标的设计;
- SWE-Bench / Agent Benchmark 研究者 —— 需要新的 mid-training 信号;
- Agent Infra 团队负责人 —— 想系统理解 Coding Agent 训练流程各阶段作用的人;
- Agent 产品经理 —— 想理解"为什么我的 Coding Agent 通用能力变弱了"以及如何缓解;
- 不适合:纯应用层 RAG / Prompt Engineering 读者(议题不相关)。
训练流程伪代码(简化版)
# Input: pretraining corpus D, model M, post-training pipeline P
# Output: agent-foundation model M'
# 1. 程序依赖图分析,筛选可遮蔽函数
function_pdgs = build_PDGs(D) # 对每个仓库构建 PDG
eligible_funcs = filter_by_pdg(function_pdgs) # 保留被调用的函数
# 2. 复杂度可推断性过滤
final_funcs = [
f for f in eligible_funcs
if MIN_LEN <= len(f.body) <= MAX_LEN
and is_inferable_from_context(f, D)
]
# 3. 构造 FIM 训练样本
fim_corpus = []
for repo in D:
for func in final_funcs_in(repo):
# 遮蔽函数体,保留 imports + 调用点 + 其他函数
masked = mask_function_body(repo, func)
target = func.body
fim_corpus.append((masked, target))
# 4. Mid-training
M' = mid_train(M, fim_corpus, objective=fim_loss)
# 5. Agentic post-training(外部流程)
M_final = P(M')
# 6. 评估
eval_in_domain(M_final) # SWE-Bench-Verified, SWE-Bench-Lite
eval_out_of_domain(M_final) # LiveCodeBench, tau-bench, BFCL
一个工程师视角的执行清单
如果你要在自己的 Coding Agent 训练流水线里加入 FIM mid-training,以下是一份最小可执行清单(基于论文可推断的部分 + 通用最佳实践):
- 准备去污染语料 —— 从 GitHub 拉取目标语言的仓库,用 MinHash/n-gram overlap 与 SWE-Bench、HumanEval、LiveCodeBench 等基准去重。论文用了 968 个仓库、2.6B token,规模可以作为起点。
- 构建 PDG —— 静态分析工具推荐使用 Tree-sitter(多语言)、PyCG(Python call graph)、Joern(多语言 PDG)。论文对 Python 用 PDG,对其他语言未明确。
- 筛选函数 —— 双重过滤:(a) 被其他代码调用(call graph 中出度 > 0);(b) 函数体长度落在合理区间(如 10 ~ 500 行,原文未明确),且函数逻辑可被调用上下文约束。
- FIM 数据格式 —— 标准 FIM 三段式:prefix(前文) / middle(被遮蔽函数体) / suffix(后文),但 middle 必须恰好是整个函数体,而不是任意切片。
- Mid-training 超参 —— 学习率可比主预训练低一个数量级(如 1e-5 起步),训练 1~2 个 epoch 即可观察到 SWE-Bench 增益,避免过拟合到 FIM 的局部模式。
- 联合 Agentic post-training —— Mid-training 后立刻接 RLHF/Agentic RL,注意监控 LiveCodeBench / tau-bench / BFCL 是否出现能力侵蚀。本文方法应能缓解这种侵蚀。
- 跨语言外溢验证 —— 在多语言模型上,把 mid-training 语料从 Python 扩展到 TS/Rust/Go 等,验证"函数调用归纳偏置"是否仍能外溢到非 Python 任务。
执行清单中的具体数值(如函数体长度、学习率、epoch 数)均不在原文中给出,属于工程经验补充,实施时需做小规模消融。
工程落地与核查(Jay)
事实核查
- +2.8 ~ +3.2(SWE-Bench-Verified):⚠️ 需原文 PDF/代码库核验。文中未区分是 pass@1 还是 pass@5,亦未说明是在 R2E-Gym 还是 SWE-Smith 流程下测得。不同流程的基线不同,提升数字的可比性受限。
- +3.7 ~ +5.4(SWE-Bench-Lite):同上,Lite 与 Verified 为不同难度子集,提升幅度大于 Verified 与论文"SWE-Bench-Lite 难度中等"的分析一致,但需原文确认。
- "缓解能力侵蚀"具体幅度未公开:abstract 未给 LiveCodeBench / tau-bench / BFCL 的具体百分点变化;⚠️ 解读中"部分逆转"的结论有依据但无数字锚点,引用时须说明"原文未量化"。
- 2.6B token 去污染语料:968 个 GitHub 仓库数量可核查,但具体哪些仓库、去污染方法(MinHash 参数等)原文未明确。
- PDG 工具:论文对 Python 用 PDG 分析,但具体使用 PyCG / Joern / Tree-sitter 的哪种工具未说明。
- 函数长度阈值:复杂度可推断性的上下界原文完全未给出,⚠️ 任何"10~500 行"的数字均为后补工程经验,不是论文数据。
- R2E-Gym / SWE-Smith / SWE-Lego:三个 post-training 框架均来自已有工作,原文仅引用,未详细描述。
实际系统怎么用
完整流水线实现步骤:
# 1. 语料收集(GitHub 仓库)
pip install pycg tree-sitter # PDG 构建工具
# 2. 用 PyCG 提取 Python call graph
pycg --max-calls 10000 ./repos/ -o call_graph.json
# 3. 筛选被调用的函数(出度 > 0)
python filter_called_funcs.py --cg call_graph.json \
--min-len 10 --max-len 500 \
--output eligible_funcs.json
# 4. 构造 FIM 样本(标准 FIM 格式:PSM)
python construct_fim.py --funcs eligible_funcs.json \
--format PSM \
--output fim_train.jsonl
# 5. Mid-training(以 Qwen2.5-Coder 为例,使用 Hf Trainer)
# lr=1e-5, epochs=1-2, max_seq_len=2048, per_device_batch=8
torchrun --nproc_per_node=8 \
train_fim.py \
--model Qwen/Qwen2.5-Coder-7B-Instruct \
--data fim_train.jsonl \
--lr 1e-5 \
--epochs 2 \
--output_dir ./fim-checkpoint
推理侧接入:FIM mid-training 产出的模型与普通代码模型使用方式完全相同,不需要修改 inference 流程。模型权重可直接替换既有 Coding Agent 的 base model。
坑在哪
| 坑点 | 具体表现 | 缓解方案 |
|---|---|---|
| PDG 对动态语言覆盖不足 | Python 的 eval(), exec(), 反射、__getattr__ 无法被静态 PDG 捕获;可能漏选有效候选函数 |
对 Python 补充基于 AST 的启发式筛选(如含 def 且在模块级可见的函数);不要完全依赖 call graph |
| 函数长度阈值盲区 | 原文未给阈值,10~500 行是工程经验;过短函数 FIM 收益低,过长函数自监督学习不可靠 | 小规模消融:在 10/20/50 行下限 和 200/500/1000 行上限之间做 4×4 网格搜索,用 val set 上的 perplexity 选最优 |
| mid-training 的真正贡献者不清晰 | 2.6B token 数据量 vs mid-training 阶段本身,二者未消融;可能是 token 量而非 FIM 任务带来的收益 | 至少跑一个消融:用同样 2.6B token 但做 standard forward training(非 FIM),对比 FIM 与非 FIM 的 SWE-Bench 差距 |
| SWE-Bench 与生产环境差距 | SWE-Bench 是单 repo 单 issue,生产 Agent 可能面对多 repo 协作、状态依赖更复杂的场景 | 以 SWE-Bench 为 proxy 但在真实场景做 A/B 测试;不要把 SWE-Bench 提升直接映射为产品指标 |
| 能力侵蚀缓解幅度未量化 | 论文未给出 LiveCodeBench / tau-bench 变化的具体百分点,"缓解侵蚀"是定性结论 | 引用时加 ⚠️ 说明;自己训练时务必同步监控这三项指标 |
| GPU 成本不透明 | 2.6B token mid-training 在 7B 上约需多少 A100 GPU 小时原文没说 | 按 7B 模型 ~350 A100-GPU-hours / 1B token 估算;2.6B token ≈ 910 GPU-hours,约 $2,000~4,000(按 spot instance);14B 翻倍 |
| 跨语言泛化未充分验证 | 仅 Python 语料;TypeScript / Rust / Go 等语言的函数调用结构与 Python 有差异(如鸭子类型 vs 显式类型) | 先在自己目标语言上做小规模验证(SWE-Bench 类似语料),再决定是否扩展语料 |
| 开源代码未找到 | short paper,代码可能未随论文发布;复现需自行实现 PDG + 筛选 + FIM 训练全链路 | 可以参考 StarCoder 的 FIM 实现(bigcode/starcoder GitHub)作为 baseline,自己适配 PDG 筛选 |
代码/工具参考
- PDG / Call Graph:
PyCG(Python call graph,pip install):适合 Python 语料Joern(多语言 PDG):适合 C/C++/Go 等,可扩展到 JSTree-sitter(语法树 + 语义分析):最通用,需自己写 query- FIM 训练框架:
bigcode/starcoder官方 FIM 训练脚本(GitHub)可作参考- HuggingFace
Trainer+ 自定义collate_fn处理 PSM 格式 - 去污染工具:
datasketch(MinHashLSH)用于 n-gram overlap 检测data-mechanic(if available)用于 benchmark 去重- Benchmark:
- SWE-Bench:
princeton-nlp/SWE-bench(HuggingFace) - LiveCodeBench:
livecodebench/livecodebench_public(需申请) - tau-bench / BFCL:各自 GitHub 官方库