与先验对抗:当模型掌握了错误版本的技术栈时
有一种特定的争论,你只会和语言模型发生。你粘贴进代码。它把一个能运行的调用改写成了早在几个大版本前就不存在的形式。你纠正它。它道歉、表示同意,然后在下一轮对话中故技重施。你不是在对抗无知,而是在对抗一段对 不同 版本技术栈的、自信且经过充分排练的记忆——这段记忆被远超你纠正信息的训练样本所强化。
这就是我所认为的“与先验知识对抗”(fighting the prior)故障模式。模型的参数化知识——它在训练期间吸收的一切——包含了流行的、过时的,或者仅仅是与你实际使用的框架版本不同的内容。当你的上下文与它的先验知识发生冲突时,先验知识往往会胜出。与纯粹的幻觉不同,这种错误之所以危险,恰恰是因为它具有误导性的合理性:那个被弃用的 API 曾经是正确的,所以代码看起来没问题,能通过随意的阅读,甚至有时还能编译通过。
这种感觉不同于普通模型错误的原因在于,它是“结构性”的,而非随机的。幻觉出来的函数名是你能够捕捉到的噪音。而一个自信地给出的已弃用 API 则是 信号——模型已经牢牢掌握了错误的东西。旧调用的 token 携带的概率高于新调用的 token,因为旧调用在训练语料库中出现了成千上万次,而新调用只出现了几次。你不是在要求模型去猜,而是在要求它凭借你粘贴进来的几行代码,去推翻它自身那经过最强化的模式。这是一个比看起来困难得多的请求。
为什么先验知识会战胜你的上下文
从训练集的算术逻辑开始。当研究人员测量代码模型调用已弃用 API 的频率时,他们发现了一个发人深省的现象:在这些模型学习的源码仓库中,已弃用调用和替代调用并存,而替代调用的数量仅为已弃用调用的两倍左右。这不是一个清晰的信号。模型看到的约三分之一的相关示例都是旧的做法。这种不平衡不足以让模型果断地学会新习语,但足以让旧习语作为一个高概率路径存活下来。
结果体现在数字上。在一系列代码模型中,已弃用 API 的使用率——即模型在应该使用替代方案时却给出过时 API 的频率——整体落在 25%–38% 的范围内。但这个总标题数字掩盖了真正的危险。当周围的代码看起来已经有点过时时,已弃用 API 的使用率会飙升至 70%–90%。模型会读取上下文环境并与其匹配。如果你给它提供最新的上下文,这一比率会下降到 9%–18%。换句话说,先验知识是具有上下文相关性的:模型在不断地从你可能根本没有意识到的线索中推断“我正处于这个库的哪个时代?”
现在再加上知识冲突研究,它提出 了一个更尖锐的问题:当模型的记忆与你提供给它的文档直接矛盾时,会发生什么?在没有上下文文档的情况下,模型仅在大约 75% 的时间里能正确采纳知识截止日期后的 API 变更,而代码实际能运行的比例仅为 43%。这些故障模式非常具体且值得关注:完全忽略更新(模型直接无视它)、在最初接受变更后又退回到已弃用的调用,以及——最引人注目的——当被告知存在新 API 时,宁愿幻觉出一个全新的函数,也不愿承认它不知道真正的那个。模型宁愿发明一个看起来合理的成员,也不愿说“这超出了我的学习范围”。
这是需要内化的核心不对称性:你的上下文正在与模型的先验知识竞争,而先验知识并非一张你可以随意书写的白纸——它是一个你试图罢免的在位者。 只要严肃对待这一框架,后续的一切都顺理成章。
为什么通用评估从未发现这一点
这种故障模式之所以如此顽固,是因为在大多数团队运行的评估(evals)中,它几乎是不可见的。标准的代码基准测试(benchmarks)所测试的问题,其正确答案正是模型已经相信的东西。它们奖励的是先验知识。一个能自信地写出流行的、文档齐全的 API 版本的模型,在基于相同流行语料库构建的基准测试中表现出色。评估工具和训练集共享同一种世界观,因此冲突永远不会产生。
只有当你的技术栈偏离互联网的平均水平时,冲突才会浮出水面——而根据定义,这种偏离是你所特有的。你使用的是训练数据几乎没见过的大版本。你维护着一个重命名了方法的内部 fork。你有一套在任何通过公开教程学习该框架的人看来都像是错误的内部约定。这些都不会出现在公开基准测试中,因为公开基准测试正是基于模型已经过拟合的那个“中位数”构建的。
因此,模型在每一项通用测量中都表现优异,却在悄无声息地破坏你的实际代码库。“基准测试表现出色”与“在我的仓库中出错”之间的差距,并不是通过选择一个更聪明的模型就能解决的质量问题。能力更强的模型往往会更坚定地持有其错误的先验知识,因为能力和信心是同步增长的。这个差距是一个测量问题:你是在用世界的代码而不是你的代码来评估模型。
这意味着第一个实际行动不是调整提示词(prompt tweak),而是根据 你的 偏离点——即你的技术栈与模型先验知识分道扬镳的特定 API、配置键和模式——构建一个小型的、对抗性的评估。这是唯一一个永远不会来自供应商的评估,因为它是一张标记了你的现实与他们的现实相矛盾之处的地图。
探测:寻找先验知识与上下文之间的冲突
在修复冲突之前,你必须先发现它,“发现”在这里意味着比肉眼观察 diff 更具针对性的手段。你需要设计专门的探测(probes)来 诱发 分歧,从而测量模型最终会站在哪一边。
最干净的探测方法是受控的 A/B 测试。找一个涉及冲突 API 的任务。运行两次:一次只提供任务,另一次将权威的代码片段或文档粘贴到上下文中。如果输出不同——如果模型在裸跑时写旧的习惯用法,而在看到文档时写新的——那么你就发现了一个真实存在的冲突,同时也确认了上下文 可以 扭转它(并非所有冲突都能通过上下文解决)。如果输出完全相同且都是错的,说明先验知识甚至在直接证据面前也占据了上风,那么你正处于一个更棘手的阶段。
以下是一些值得关注的实际特征,因为每一个都指向不同的补救措施:
- 无声回退(Silent reversion):第一个回合模型使用了你纠正后的 API,但几个回合后,随着纠正信息滑出有效上下文,它又漂移回了已废弃的 API。这是一个记忆衰减问题,而不是理解能力问题。
- 看似合理的虚构(Plausible invention):你告诉模型存在一个新方法;它虚构了一个签名,而不是询问或拒绝。这意味着它信任自己的生成先验,更甚于它知识中的空白——这是一种校准失效(calibration failure)。
- 邻里匹配(Neighborhood matching):模型的选择取决于周围代码看起来有多“现代”。文件顶部陈旧的 import 会将下方的一切拉向旧的习惯用法。
- 自信的纠错(The confident correction):模型将你刻意为之的、不寻常的模式“修复”回通用的模式,将你的深思熟虑视为 bug。这是先验知识在行使风格权威。
记录这些。一个被命名的冲突是一个可以围绕它构建回归测试的冲突;而一个你只模糊感知的冲突,每次你升级模型时它都会重新浮现。
