跳到主要内容

对话历史是你的提示词从未承认的负担

· 阅读需 12 分钟
Tian Pan
Software Engineer

下次当用户抱怨“AI 今天变笨了”时,去看看你产品的分析数据。筛选出对话轮次超过 20 轮的会话。你会发现每次都是同样的 U 型曲线:前几轮表现良好,中间几轮表现也不错,而到了后期轮次,质量却直线下跌。提示词没变,模型也没变。变化的是,后期每一轮对话都背负着沉重的载荷:用户的拼写错误、话说到一半的废话、模型的模棱两可、后来被撤销的更正、没人重读的工具输出,以及用户在第四轮就放弃了的目标残余。你的提示词模板把这些“沉积物”当成了信号,模型也是如此。但这不应该。

对话历史并非免费的上下文。它是一种你每一轮都在付费重新发送的负债;它变得越混乱,就越会损害你向用户收费提供的答案。“聊天”这个隐喻是混乱的根源。聊天界面让用户和工程师习惯于将记录视为神圣不可侵犯的 —— 可滚动的、仅限追加的、从不重置。这种习惯被原封不动地引入了 LLM 应用中,尽管在模型处理上下文的物理机制上并没有这种依据。模型是无状态的。对话记录只是你选择不断拉长的一段字符串。你可以缩减它,而且通常你应该这样做。

没人绘制的逐轮质量损耗曲线

“上下文腐烂”(Context rot)是 2025 年描述这种现象的专业术语,而现在的证据已不容辩驳。Chroma 对 18 个前沿模型(GPT-4.1、Claude、Gemini 等全系列)进行的系统性研究发现,每一个模型都会随着输入长度的增加而性能退化;标称具有 20 万(200K)上下文窗口的模型,在输入达到 5 万(50K)之前就开始出现明显的质量下降。微软的多轮对话研究模拟了 15 个生产级模型中的超过 20 万次对话,结果显示,当同一任务被拆分为多轮交流而非单次提示词时,平均准确度下降了 39%。这并不是在某些小众压力测试中的退化,而是通用任务表现上的全面滑坡。

原因层层叠加。注意力在长输入中的分布并不均匀:开头和结尾的 token 占据主导地位,中间部分则陷入低注意力的“谷底” —— 这就是记录详实的“迷失中间”(lost in the middle)效应。旋转位置嵌入(RoPE)在设计上强化了这一点。每一轮新的对话都会把之前的正确信息推向那个谷底,并增加新的 token 来争夺有限的注意力预算。更糟的是,模型自身的历史输出对未来的轮次有着不成比例的影响:一旦它在第三轮确定了某种回答模式,即使在第十二轮用户已经悄然转换了话题,它仍会倾向于遵循那个模式。微软的研究人员精准地将其标记为:过早尝试回答、冗余度升级,以及过度调整以迎合第一轮和最后一轮,却牺牲了中间的所有内容。

去绘制一个真实 Agent 会话(而非基准测试)的逐轮准确率。那条线不是平的,它是弯曲的。

每一轮都会增加模型无法与信号区分的沉积物

一个持续的对话会累积四种垃圾,而对于读取它们的 Transformer 来说,没有一种看起来像垃圾。

第一种是用户端的噪音:下一轮纠正的拼写错误、未写完的半截话、因为第一次表达不清楚而重新叙述的请求。模型对第一次尝试和第二次尝试给予同样的关注。在后面的轮次中,它可能会再次翻出语法不通的第一个版本,因为那是注意力模式捕捉到的内容。

第二种是模型端的模棱两可和撤回。“我认为是 X,但我不确定”,三轮之后紧跟着“实际上是 Y”,这两个断言都留在了上下文中。在第 20 轮问模型任何稍微相关的点,它会欣然引用它已经撤回的含糊词。Google 的 Gemini 团队在构建一个玩宝可梦的 Agent 时记录了一个典型案例:Agent 幻觉自己拥有某个道具,这个幻觉被写进了上下文的“目标”部分,从此之后每一轮都在强化这个错误信念。上下文污染(Context poisoning)不是理论风险,它是任何将自身过去输出视为事实依据的系统的默认行为。

第三种是过时的修正。用户在第二轮说“叫我的名字”,然后在第八轮改变了主意。第一个指令仍在记录中,第二个指令是后来的,哪一个占据主导地位取决于你无法控制的位置效应。

第四种是没人重读的工具输出。三轮前查询产生的 4KB JSON 对象在后续的每一轮中仍会消耗 1200 个 token 的注意力预算,而其中包含的答案早已被提取并使用了。你一直在付费重新发送这些无用的垃圾。

无状态模型没有办法将这些与用户当前的实际问题区分开。你必须替它完成这项工作。

裁剪既廉价又具有破坏性,总结既昂贵又容易产生幻觉。请有意识地选择你的“毒药”。

行业默认的两种压缩策略都有大多数团队在生产环境中才会发现的失效模式。

裁剪(Trimming) —— 即最后 N 条消息的滑动窗口 —— 是 O(1) 复杂度的、确定性的,并且零概率引入新的幻觉,因为它不生成新文本。代价是用户在窗口截止前提供的任何指令、偏好、约束或事实都消失了,模型无法知道它们曾经存在。在第二轮说“始终用公制单位回复”的用户,在第四十轮会收到英制单位的回复,并理所当然地认为这个产品坏了。裁剪虽然实现简单、运行成本低,但它会悄无声息地丢掉那些区分“长会话好坏”的关键细节。

总结(Summarization) —— 通过 LLM 处理旧轮次并将其替换为压缩后的摘要 —— 原则上保留了长程信息。但在实践中,它是一个带有语义漂移的有损编码器。关于迭代总结的研究显示了一种特定且可预测的病态现象:“我喜欢微辣”在经过一次处理后变成了“喜欢辣”,在下一次处理后变成了“非常爱吃辣”。低频但高价值的细节 —— “切勿直接调用生产数据库”、“客户 ID 是 47 而非 4772” —— 往往在第三次压缩时消失。而且每次总结都是一个新的幻觉产生面:总结器会虚构出原本不存在的联系,将不同的请求混淆在一起,并把自己对用户“可能说了什么”的先验偏见注入其中。你用裁剪的“干净遗忘”换取了总结的“创造性重写”。

混合方法 —— 固定系统提示词和最后 K 轮的原文,仅对中间部分进行总结,并在总结中携带一个提取的小型事实结构 —— 在实践中优于这两个极端。但它们也不是免费的。Factory.ai 的生产实践报告描述了维护“真正重要的信息的滚动摘要”,并结合锚定总结,仅在丢弃新片段时才重新总结。其代价是真实的工程投入:你需要提取事实的模式(schema)、决定哪些事实能留存的策略,以及一套能在压缩删掉错误内容时捕捉回归的评估体系。对于真正这样做的团队来说,回报是 10 倍的有效会话长度。而那些没这么做的团队(大多数团队)则在“裁剪或漂移”的自动驾驶模式下运行,并纳闷为什么第 30 轮的表现会比第 3 轮差那么多。

你的系统应该关注的压缩信号

压缩不应该基于固定的对话轮数触发。它应该基于可观察到的、能够预测即将来临的性能退化的对话健康信号。以下是一些衡量成本低且效果显著的信号:

  • 相对于模型有效窗口(Effective Window)而非标称窗口(Nominal Window)的 Token 利用率。 如果 Chroma 的数据准确,那么“有效”窗口通常只有“标称”窗口的 25%–40%。请据此制定预算。
  • 模型最近 K 次输出中的重复率。 跨轮次的二元语法重叠(Bigram-overlap)分数上升是一个早期指标,表明模型正陷入重复的模板中,并忽略了新的用户输入。
  • 用户纠错频率。 统计最近三次用户对话中包含“不”、“实际上”、“我的意思是”或任何表达“你弄错了”的措辞的频率。频率激增意味着模型误读了状态;进行压缩 + 重新表述有助于重置状态。
  • 工具调用冗余。 如果智能体正在调用十轮前已经调用过的相同工具和参数,说明早期的工具输出已经从有效注意力中消失了。显式地压缩并重新呈现相关事实。
  • 主题偏移距离。 对最近一次用户对话和第一次用户对话进行向量化(Embed),并测量距离。超过某个阈值后,早期的对话可能不再是正确的上下文——它们成了累赘。
  • 自我矛盾检测。 低成本方案:从助手最近一次回复中提取主张,并根据正在运行的“已提取事实”状态进行检查,标记出不匹配项,以便在压缩时解决。

我看到的大多数团队在“接近上下文限制”时才触发压缩,这是最糟糕的触发方式,因为它等到损害已经造成后才开始行动。请基于健康信号触发。尽早触发。积极触发。

“不重置”禁忌是 UX 选择,而非工程约束

这个问题最难的部分不是技术。而是对用户来说,“AI 重置了我们的对话”读起来显得无礼、系统损坏或健忘,因为自短信息(SMS)以来,每一款聊天产品都让他们习惯了对话记录是永久的。因此,团队会避免可见的压缩,任由对话记录增长,转而承受质量下降。然后他们收到关于 AI 变差的错误报告,并将其视为模型质量问题,而不是上下文管理问题。

正确的思路是,你的工作是保留用户的意图和状态,而不是字面上的对话记录。在 UI 中展示对话记录——用户想要滚动查看。但不要把完整的记录发给模型。发送一个精心策划的上下文,它由固定的系统提示词(System Prompt)、持久的已提取事实结构、最近几轮的原始对话以及中间部分的摘要组成。用户的滚动视图和模型的输入可以——也应该是——不同的产物。将它们视为同一种东西是对聊天比喻的妥协,而这正以牺牲准确性为代价。

还有两个 UX 方案很有帮助。让压缩事件变得清晰易读——例如一个简单友好的“我整理了我们目前的对话”标记,可能还可以展开查看保留了什么——这样压缩会被理解为细心而非遗忘。此外,给用户一个明确的一键式“重新开始但保留 [偏好 / 项目 / 事实]”的操作。对于长会话用户来说,最实用的做法往往是开启一个仅继承提炼状态的新会话。大多数产品让这一步变得异常痛苦,这就是为什么会话会一直进行下去,直到在自身的重量下崩塌。

周一该做的事

如果你正在运营一款聊天产品,且过去六个月没碰过上下文处理,本周有三件事值得做。首先,在你运行的任何评估中加入逐轮准确度检测,并根据轮数绘图。如果曲线是平的,要么你的评估中对话轮数不够,要么你的评估不够敏感。其次,测量每个提示词中有多少是三轮或更早之前的工具输出;这是成本最低的优化点。第三,从上面的列表中选择一个压缩信号——用户纠错频率是最简单的——并将其接入压缩触发器。在尝试任何花哨的方案之前,先上线滚动摘要 + 固定事实的模式。

对话历史不是记忆。它是非结构化的、只能追加的日志数据,而你每一轮都在选择将其作为模型的输入,但模型无法区分其中的核心部分和噪声。处理好长会话的团队将对话记录视为需要精心策划的负担,将模型的各种工作上下文视为需要管理的契约。而那些做不好的团队,他们的用户会在反馈表里写下“它以前更聪明”。

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