跳到主要内容

与先验对抗:当模型掌握了错误版本的技术栈时

· 阅读需 12 分钟
Tian Pan
Software Engineer

有一种特定的争论,你只会和语言模型发生。你粘贴进代码。它把一个能运行的调用改写成了早在几个大版本前就不存在的形式。你纠正它。它道歉、表示同意,然后在下一轮对话中故技重施。你不是在对抗无知,而是在对抗一段对 不同 版本技术栈的、自信且经过充分排练的记忆——这段记忆被远超你纠正信息的训练样本所强化。

这就是我所认为的“与先验知识对抗”(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。这是先验知识在行使风格权威。

记录这些。一个被命名的冲突是一个可以围绕它构建回归测试的冲突;而一个你只模糊感知的冲突,每次你升级模型时它都会重新浮现。

真正持久的对策

现在进入实用的部分:哪些方法有效。按持久性排序,因为几种流行的修复方法其实比看起来要脆弱。

否定指令会衰减。直觉上会写“不要使用已废弃的 foo() 方法”。这种做法帮助最小,失效最快。禁令是脆弱的:它们消耗注意力,没有告诉模型该做什么,且随着对话增长和指令滑向上下文后方,它们会失去效力。告诉模型 不要 去想流行的 API,就像告诉一个人不要去想大象一样无效。每一个“不要使用 X”同时也是对 X 存在的一个提醒。

上下文中的权威文档胜出——前提是它们是结构化的。研究中最大的杠杆是将当前的 API 规范直接放入上下文。这样做将正确采用率从大约 75% 提升到了 93%,并使生成代码实际运行的成功率翻了一倍多。这就是为什么 llms.txt 约定和按依赖注入文档的方法流行起来的原因:修复错误先验的方法是一个更强、更近、更权威的真相来源。但请注意限定词——结构化。埋在文字墙中的冗长变更日志(changelog)表现不如放置在调用点附近的简练、规范的代码片段。接近性和清晰度与存在感同样重要。

将生成转变为校验。最有效的单一技术之一是自我反思:在模型草拟代码后,提示它在最终确定前根据提供的文档检查自己的输出。这把一个生成任务(先验知识占主导)转变为了一个校验任务(眼前的证据占主导)。测得的收益非常显著,特别是对于修改型(而非仅仅是新增型)API 的最难案例。生成依赖记忆;检查依赖页面上真实存在的内容。

如有疑问,通过重命名消除冲突。最被低估的修复方法是最不“聪明”的那一个。如果你的内部 API 因为名称与模型记忆中的流行库冲突而不断被“纠正”为他人的习惯用法,有时最廉价且持久的修复方法就是 停止冲突。重命名你的内部辅助函数,使其不再与模型对另一个 Clientparse()connect() 的强先验重叠。你无法在一场概率之战中赢过模型见过一万次的名称。将你的名称移出爆炸半径,冲突就会自然消失——无需提示词工程(prompt engineering)。

翻新邻里环境。因为模型通过阅读上下文来决定它处于哪个时代,所以要保持周围代码的现代性。现代的 import、最新的调用点以及整洁的文件头部,都会免费地将生成偏好引导向新的习惯用法。如果你在一个充满遗留调用的文件中工作,就要预料到模型会匹配它们,并拉取一个权威示例到视野中以重置参考框架。

你无法预见的升级风险

令人不安的隐含意义是,这个问题不会随着下一个模型的发布而消失——它会 转移。每个新模型都有新的截止日期(cutoff),这意味着它有一组新的自信掌握的知识,以及一组新的自信记错的内容。你正在使用的库可能在下一个模型的训练数据中表现良好,但在再下一个模型中却表现糟糕。你精心调优的“使用新 API”支架可能变得不再必要,然后又变得必要,接着又以一种新的方式出错,而你这边却没有任何变化。

这就是为什么持久的投资不是任何单一的提示词。而是 脚手架(harness):映射你的技术栈与中位数偏离程度的小型对抗性评估(adversarial eval),探测模型落在冲突哪一边的探针,以及让权威来源比先验知识更近的文档注入机制。将模型的记忆视为一个你永远在与之竞争的现任者——有时是盟友,有时是对手,但绝非中立。在这些工具的辅助下能够可靠交付的团队,并不是那些拥有最好提示词的团队,而是那些不再假设模型是一张白纸,并开始针对“它带着成见而来”这一事实进行工程设计的团队。

References:Let's stay in touch and Follow me for more thoughts and updates