你大概已经习惯这种用法:写下一行,AI 接着补完;选中一段,让它改成另一种写法。这类用法很少让你失望。于是很自然地会想:既然一行能写,一百行是不是也能?我说一句“做一个增删改查的待办小工具”,然后去泡杯咖啡,回来收代码。
结果往往不是这样。不是因为它这次突然变笨了,而是因为 补全和完整任务,本来就是两种性质不同的问题。把差别拆开看,你在协作里该站的位置也就清楚了。
一、补全的可靠,来自“正确性是现成的”
你让 AI 补全时,它面对的处境是:周围的代码已经在那里。变量名、类型、函数的输入输出、这段代码被谁调用、上一步算出了什么,全都摆在它眼前。它要做的只是把最合理的下一段填进去。
更关键的是:你随时能判断它写得对不对。类型不匹配、变量不存在、逻辑接不上,你扫一眼就知道,或者编辑器、编译器马上告诉你。写得不对,删掉,代价接近零。
这才是补全可靠的真正来源:正确性的标准由周围代码和你的即时判断给出,不需要谁来定义。 模型只需要做它最擅长的事——在密集的模式里预测接下来最像什么。
二、完整任务把问题换了个方向
现在你说“做一个可以增删改查的待办工具,数据存本地,命令行界面”。看起来更清楚了,但和补全相比,有一件事变了:“写对了没有”这个标准,此刻还不存在。
一项梳理 AI 软件工程挑战的综述把这段距离称为规格鸿沟(specification gap):自然语言和你真正想要的代码之间隔着一层,你说的话永远比程序需要的细节少 1。比如“数据存本地”——存 JSON 文件还是 SQLite?文件名怎么定?两个进程同时写怎么办?“可以增删改查”——id 是自增还是随机?“命令行”——用什么解析参数?
这不是刁难。真正的问题是:程序越长,这类没被说清的分叉点越多,而每一个分叉点,模型都在替你做决定。 它不会专门通知你,这些决定被默默写进代码里。等你运行起来发现“这不是我要的”,代码却完全能跑、看起来也挑不出毛病。
所以完整任务的第一个本质区别是:正确性不再由环境给出,而要你先把它造出来。 你不说的地方不是没有答案,而是答案由模型代填。
三、任务变长以后,错误不是摊平,而是滚雪球
第二个区别更硬,也最能解释“补全很顺、整活儿就翻车”。
假设 AI 每一步做对的概率是 99%——这个水平已经很高,比多数补全场景的实际表现只好不差。一个需要 100 步的任务,全程做对的概率是:
反过来读同一个式子:每一步的准确率哪怕只提高一点,能做成的任务长度就会成倍增长。这就是“每步准确率”和“任务能走多远”之间的杠杆关系。
但真实的退化比这个公式更糟。一项关于长程执行的研究发现,模型出错之后,下一步出错的概率会升高:当上下文里含有它自己先前的错误时,后续每一步的准确率都会下降,研究者称之为自我条件化(self-conditioning)2。也就是说,每步准确率不是常数,而是随任务推进往下掉——错误在污染它自己的判断。
还有一条独立的原因:上下文本身变长就会让模型变迟钝。Anthropic 的工程文章把它比作“注意力预算”——放进上下文的 token 越多,模型准确取用其中某条信息的能力越弱,他们把这个现象叫 context rot 3。所以“把整个项目塞进上下文让它一次想清楚”,并不是一条可靠的路线。
需要说清边界:自我条件化是在受控的合成任务上观察到的,不等于所有真实项目都按这个比例退化;带推理过程的新模型受它的影响明显更小,单轮能执行的步骤数也长得多。所以结论不是“长任务一定失败”,而是 长任务里的错误有系统性累积的倾向,而且这种累积不是你多给点耐心就能抵消的。
四、强弱分工因此变得具体
把上面两点合起来,哪些环节强、哪些不可靠,就不再是一句“看情况”:
强的地方,共同点是目标由外部给定、反馈即时、模式密集。
- 补完一个结构清晰的函数、写样板代码、把一段逻辑改写成另一种写法;
- 给已有函数补测试、解释一段报错、把错误信息翻译成可能的原因;
- 语法层面基本不出错。一项大规模分析发现,AI 生成代码里语法错误占比最低,功能错误占比最高 4。
弱的地方,共同点是需要跨很多步保持一致,或者需要知道“你的项目里到底是怎么回事”。
- 边角情况。同一项分析的真实项目基准上,在属于逻辑理解错误的那一类缺陷里,遗漏边界检查最常见,占 53.2% 4。
- API 误用。在多数基准上,运行时错误里最主要的一类就是它:模型会照着训练里见过的形状,编出一个你项目里并不存在的函数或参数 4。
- 跨文件的约定。命名风格、模块边界、已有的抽象该不该复用——模型倾向于再写一遍,而不是接上你原来的结构。这个毛病在补全模式下几乎看不出来,因为补全时这些约定就摆在它眼前。
- 算法效率。功能正确但超时,在复杂问题上很常见 4。
所以不能靠“它上一个函数写得不错”,推断下一个环节也可靠。
五、你的职责变成了什么
到这里,协作方式的起点就清楚了。你不是从“写代码的人”变成“提需求的人”,而是变成 定义正确、切分验证、承担判断的人。落到具体动作上是三件事。
第一,把隐含的选择显式化。 不是把需求写得更长,而是把“模型会替你决定、而你在意的那些分叉点”写出来:数据存在哪、id 怎么生成、出错时给用户看到什么。写少一点、说准一点,比写一大段模糊描述有用。
第二,把任务切成能验证的短段。 前面那个 0.99 的公式直接给出了理由:可靠性是每一步的函数,不是整个任务的函数。你能施加的最大杠杆不是换更强的模型,而是让每一步在很短的距离内就拿到验证——能编译、能跑过一个测试、能看到输出。有即时反馈时,模型可以在几步内自我纠正;没有反馈时,错误会一路带下去,还污染后面的判断。
第三,审查要挑地方,而不是逐行重读。 你不需要像读自己写的代码那样通读,而要建立“哪里可能错”的预判:边界条件处理了吗?它用的这个 API 在这个项目里真的存在吗?是不是把已有的函数又抄了一遍?这些恰好对应统计里缺陷最集中的位置。
最后一件容易忽略的事:上下文的归属权在你手上。 模型会在长对话里逐渐偏离最初的决定,也会被自己前面的错误带跑。所以要定期把“已经定下的约定”重新讲一遍,或者在进入新模块时开一段干净的上下文,而不是一路追加、指望它自己记住。
起点
所以这套协作方式真正的起点,不是“怎么让它一次写更多”,而是反过来问:这一步写对了没有,我怎么知道?
补全模式下你觉得它可靠,是因为这个问题被周围代码自动回答了。让它承担完整任务时,这个问题必须由你来回答——写清楚需求、切成小步、给每一步一个即时的验证信号。做到这些,它的能力才真正被用上;做不到,你拿到的只是一堆看起来能跑、却不知道对不对的代码。