AI代码补全为什么能精准猜中你下一行?底层那套"中间填空"训练是关键

去年有段时间我把 Copilot 的补全提示直接拉黑了一段时间,原因是它老在我写注释的时候蹦出一大段不知所云的代码,光标还没落到下一行,半个函数已经替我"写"好了,其中一半是我根本不想要的。那种感觉,像打字时旁边坐了个特别积极但又经常跑题的同事。
不过骂归骂,有一个瞬间我确实被震到过。写一个按日期过滤数组的函数,我刚敲完函数名和第一个参数,剩下那一整块实现,它几乎一字不差地补了出来,连边界条件的处理方式都和我想的一致。那一刻我心里冒出来的不是"好厉害",而是"它到底怎么知道我接下来要写这个"。
这个问题后来花了我不少时间去翻资料。结论比我想的更直白,也更有意思:代码补全和聊天生成,看着都是"AI 生成文字",底层其实是两套不同的训练思路。搞明白这个差别,很多关于"AI 写代码行不行"的困惑也就顺带解开了。
早期的模型其实不适合补全
要理解现在的代码补全,得先回到一个略显反直觉的起点:早期的大语言模型,天生是不适合做代码补全的。
原因是这些模型在训练时学的都是同一件事——从左往右挨个预测下一个词。给你一段话,模型预测下一个字是什么,预测对了就继续,一路滚下去,就成了一篇完整的文章。这种"自回归"的方式用在聊天、写文章上很自然,因为说话写字本来就是一顺到底的。
但写代码的实际情况不是这样。你光标停在一个函数的中间,想补的是"中间那段"——前面已经写了一半,后面可能也已经有了后半段的框架,唯独缺中间这一截。这时候,"从左往右往下续写"的模型就不好使了,因为你需要的不是"接下来接着写",而是"在前后文的夹缝里填上一块"。
举个小例子。假设你正在写一个循环,开头 `for` 和结尾的 `}` 都已经在了,光标停在中间。一个只会"接着往后写"的模型,看到开头,只能猜"接下来该写什么",但它的目光看不到后面已经存在的结尾。它补出来的东西,很容易跟你后半段代码对不上。
这个局限,是代码补全工具早期体验差的根本原因之一。
关键转折:把训练方式倒过来
解决这个问题的思路,说起来简单得有点不像真的:既然要补"中间",那就训练模型专门去补中间。
这种方法有个正式名字,叫 Fill-in-the-Middle,缩写 FIM,直译过来就是"从中间填空"。它的做法是在训练的时候,不是让模型从头往后预测,而是刻意把一段代码的中间挖掉,让模型根据"前面那段"和"后面那段"两个方向,去猜被挖掉的是什么。
打个比方,学语文填空最像这个。给你一道题:"他___地走进教室,谁也不理。"前面给了"他",后面给了"走进教室,谁也不理",让你填中间。你从"谁也不理"这个后文,能推出空缺处大概率是个负面的、形容态度的词。这个"看了后文再回头填"的能力,就是 FIM 想教会模型的。
放到代码里,模型就是这样被训练的:给它看函数的前半部分,再给它看后半部分,让它把中间的逻辑补上。经过大量这样的"夹击"训练,模型学会了同时利用前后两个方向的线索。于是当你把光标停在一个函数中间、前后文都写好时,模型就能两边一起看,补出来的东西自然就贴得上了。
这个改动是 2022 年那个年代的模型搞出来的,也是从那之后,代码补全才真正从"玩具"变成了"能上生产"的东西。今天主流的代码补全工具,底层几乎都离不开这套中间填空的训练。

为什么它补出来的代码风格"像你"
另一个常被讨论的点,是补全出来的代码风格,为什么会越来越贴近你自己。有人觉得这是 AI 在偷偷学习你的个人仓库,其实更主要的原因是:上下文窗口。
补全的时候,模型看到的不是只有你光标前的那几行,而是你当前文件里上下很大一段内容,甚至还包括你打开的相邻文件、你仓库里的相关代码片段。这些内容一起被塞进模型的"上下文"里,构成了模型判断"下一步该写什么"的全部依据。
这个上下文大到一定程度,就会产生一个效果:模型看到了你的变量命名习惯、你的缩进风格、你引入的库、你之前定义的工具函数。那么它补出来的代码,自然就会去"配合"这些已经存在的东西——函数名会顺着你的命名走,调用的也是你文件里已经引入的方法。它像不像你,本质上是它忠实地照着你的上下文在续写。
这也解释了另一个现象:同一个模型,给不同的人用,补出来的风格天差地别。有人用出了"老练工程师"的感觉,有人只觉得它在乱写。区别往往不在模型,而在上下文——你的代码本身写得越规整、越有规律,模型可参照的线索就越清晰,补出来的东西就越好用。
补全和聊天,是两个粒度的东西
这里想澄清一个很多人搞混的概念:代码补全,和"问 AI 让它写一个完整函数",不是一回事,两者回答问题的粒度差得很远。
补全追求的是"接下来这一小步"。它看的是你光标附近的局部上下文,输出也是一小段,几行、十几个字符,讲究快、准、短。它不追求"看得远",因为它要在你敲键盘的间隙里,几百毫秒内就给出建议,慢半拍都不行。
而"让 AI 写一个完整功能"是另一个量级的任务。你需要它理解你的需求、拆解成步骤、再产出几十上百行代码,这靠的是聊天式的大模型对话,上下文大、推理时间长,成本也高。这类能力,和补全那套中间填空,几乎是两条技术路线在执行。
把这两者分清楚,就不容易陷入那种"补全工具写不出大功能,是不是不行的"式的误解。它是分工不同的两个角色:一个是贴着光标走的"下一行预测",一个是坐下来跟你讨论方案的"结对伙伴"。它们可以同时存在于一个编辑器里,各干各的。
本地小模型补全,和云端大模型补全
如果你多留意,会发现不同代码补全工具,反应的"聪明程度"和"速度"差别很大。这里头有个工程上的取舍,就是模型大小和延迟之间的平衡。
补全对延迟的要求是很苛刻的。你手指敲下去,等个一两秒才冒出来,那还不如自己想。所以很多补全产品会走"本地部署小模型"的路子:把模型裁剪到很小,直接跑在你的编辑器甚至终端里,几亿到十几亿参数,推理快,不联网也能用,隐私也好。代价是它没那么多"知识",只能干好补全这一件事,复杂逻辑它就顾不上了。
云端大模型的补全更聪明,能理解更复杂的意图,甚至能在补全里顺带帮你推理下一步。但延迟和隐私是绕不开的成本,所以它们通常不会用来做每个键都触发的实时补全,而是用在"你明确发问"的场景里。
这也正是现在很多工具采用"分层"策略的原因:日常的实时补全交给本地小模型,速度快、随手就来;遇到难啃的问题,你再主动把光标那一段或者整个需求丢给云端大模型去处理。两条腿走路,才能在聪明和快之间找到平衡。
补全这件事,其实是一面镜子
研究到后面我有个意外收获,说出来可能有点鸡汤,但确实是我真实的感受:代码补全模型,很大程度上是照着"你给它的上下文"在发挥。你给它一个清晰、有规律的文件,它补出来的就靠谱;你给它一坨命名混乱、结构纠缠的代码,它顺着你的烂摊子补下去,只会更烂。
所以有时候你觉得 AI 补全"变笨了",不一定是模型的问题,很可能是眼前的代码本身已经乱到没有规律可循了。反过来,当你开始重视命名、拆分函数、保持文件整洁的时候,你会发现 AI 的命中率也跟着上去了。
工具越聪明,越能放大你代码里本来的样子——好的地方被放大,坏的地方也被放大。它不是你偷懒的借口,反而可能是一面能照出你代码质量的镜子。
下次再遇到补全蹦出一大段让你皱眉的代码时,先别急着怪模型。停下来看看那一块上下文里,是不是也藏着些你自己早就想整理、却一直拖着的凌乱。
