对话压缩
对话装不下时总得扔点什么,而最先被扔掉的往往是最早确立的约束。所以必须活下来的东西,不该只活在对话里。
对话会一直变长,窗口却是有限的。所以任何长对话产品迟早都要回答同一个问题:装不下了要扔掉哪些内容。默认答案是「扔掉最早的那些」,而最早的那些通常正是你定下的规矩。context-window-wiki
读完你能回答:
- 压缩最先丢掉的通常是哪三类东西?
- 什么时候该主动压,而不是等它自动发生?
- 为什么必须活下来的信息不该只活在对话里?
- 有什么办法比压缩更便宜也更可靠?
两个触发点,账单那个更常被算漏
一是装不下。窗口有硬上限,超了就必须处理,这件事谁都躲不开。
二是贵。对多数产品来说,这才是真正的推手。上下文整段都要在每一轮重新发送、重新计费——第十轮里那句话本身可能只有十个字,可这一次调用的输入量是它前面所有内容之和。对话越往后走,累计开销涨得越接近平方级,长度翻一倍,账单不止翻一倍。
Agent 场景里这件事最刺眼。每调用一次工具,结果就多堆一块,几十轮之后,光历史记录就足以把窗口占满——两个触发点在这里同时出现。
三条做法的区别,在丢什么
一、直接截断。只留最近的若干轮,把最早的扔掉。实现上最省事,不少框架默认就这么干。它丢掉的是开头那几轮装的要紧内容——任务原本怎么描述的、用户交代过什么背景、双方敲定过哪些约束。这些东西一旦没了,模型就开始漂移。
二、摘要替换。前面若干轮不再保留原文,改由模型总结成一小段来替代。比截断好,主线能保住。但它必然有损:具体的数字、逐字的措辞、以及当初被排除的选项,都容易在归纳过程中消失。况且摘要也是模型自己写的,它可能总结错——这一步一旦出错,后面所有内容都建立在错误前提上。
三、分层保留。把内容按重要性分类处理:硬约束原文照留(用户立下的规矩、关键参数、已经定下不能翻案的决定,一个字都不改);中间过程做摘要(讨论、试错、被否掉的方案,压成一段);最近几轮保留原文(最新的上下文最需要精确)。要多花些功夫,但稳定性提升明显。
三条路的分界其实不在做法本身,而在你愿意丢掉哪一类东西。所以第三条才是最稳的:它把「绝对不能丢」和「丢了也能接受」分开处理,而不是整段一起压。
主动压还是等它自己压
压缩不一定要等窗口满了才发生。两种时机要分清:
被动压缩是框架发现装不下时自己动手。这时候你没得选,丢什么是别人定的默认规则。
主动压缩是你在一个明确的节点上做:一个阶段结束、一次任务交付完、用户明确切换话题时。好处是你能决定丢什么,而且能在丢之前把该留的挪走。
判断标准很简单:如果这次对话已经不需要早期那些试错过程了,就现在压;如果需要,压了就是给自己制造问题。压缩本身要花一次模型调用,所以压得太频繁也是浪费。
消失得最有规律的三类
一、早期定下的约束。丢得最多的就是这一类。约束一般都在对话一开始讲定——不要出现内部系统的代号、每段控制在两行以内、只能引用用户上传的那份质检报告。讲过这一次之后,后面就没有人再提起它们。到了摘要那一步,这些句子读起来不像「正文内容」,于是被顺手归并掉了。表现出来就是那个很典型的状况:对话进行到一半,它开始不守一开始讲好的规矩。用户以为「它变笨了」,真实原因是那条规矩早就从上下文里消失了。
二、精确的数字和标识。工单编号、赔付金额上限、返工批次号、质检员姓名。摘要习惯于往笼统里写——「讨论了几笔赔付的处理」,至于究竟是哪几笔,找不回来了。
三、否定信息。「讨论后决定不再申请延长」很容易被写成「讨论了延长申请」,接下来模型有可能当成要做的事去办。在所有语义里,否定最容易被概括掉,因为在原文里它只占一个词。
这三类有一条共同点:它们都属于「说一次就够、后面不必再提」的信息,而在压缩看来,不再重复就等于不重要。
流程上的三条纪律
一、承重的约束不进压缩流程。用户立下的硬约束单独抽出来,安放在一个固定的位置(通常紧接系统提示),永不参与压缩。大部分漂移问题到这一步就没了,而且它比任何摘要策略都简单。
二、压缩要打点。每一次压缩发生在什么时间、去掉了多少轮、摘要写了什么,都要留档。缺了这份日志,线上问题基本没法复现——连当时上下文里有什么都说不清。
三、考虑让用户可见。如果压缩会明显影响体验,主动提示一句(「较早的对话已归纳」),比让用户毫无征兆地撞上失忆要好。有些产品让用户把要紧的信息置顶,本质上就是手动划进不压缩的那一块。
比压缩更值得先问的两个问题
第一个:这段对话真的需要这么长吗?这一步被跳过的次数最多,效果却最好。很多场景并不需要一条无限延续的会话——提供一个明确的新任务入口,比任何压缩策略都管用,花的钱更少,行为也更好预期。压缩是不得已的补救手段,不该当成默认设计。
第二个:这条信息为什么非要靠对话记住?只要一个信息必须活下来,它就不该只存在于对话历史里。对话是易失的存储——它会被压缩、被截断、被摘要弄丢。而凡是能写进结构的地方,都比对话可靠:用户偏好做成设置项,任务状态写进业务数据库,跨会话的事实放进长期记忆,阶段成果落成一份交接物。
这两个问题把「怎么压得更好」换成了「什么本来就不该靠对话记住」。前者是在有损的过程里做取舍,后者是让信息根本不进入这个有损过程。所以顺序是:先把该外置的挪出去,再决定剩下的怎么压。需要跨会话记住的东西,长期记忆那一篇讲的就是怎么存和怎么取回;而多轮任务之间怎么把状态交出去,分层加载与交接包给的是系统侧的做法。
