聊了50轮Token爆了记忆该压缩还是该丢
开篇引言
上一篇聊了记忆系统的存储与加载,核心机制是一个滑动窗口——historyKeepTurns 默认保留最近 8 轮对话原文,通过 CompletableFuture 并行拉取摘要和历史,最后按固定顺序注入 Prompt。看起来挺稳的,8 轮原文够用吗?
来看一个场景。假设你在一家企业做 IT 采购知识库助手,有个员工连续聊了十几轮:
第 1 轮:我们部门想采购一批笔记本电脑,预算大概 5000 元一台。 第 2 轮:总共要 50 台,需要支持 Windows 11。 第 3 轮:有没有推荐的品牌? 第 4
8 轮:围绕联想、华为、戴尔几个型号 展开对比讨论。 第 911 轮:讨论了几款型号的售后保修政策。 第 12 轮:那还有没有其他符合预算的型号推荐一下?
此时 historyKeepTurns=8,窗口只保留第 512 轮的对话,第 14 轮已经被淘汰了。问题来了——预算 5000 元/台、数量 50 台、Windows 11 这三个关键约束全在第 1~2 轮,现在全没了。模型看到第 12 轮的问题,不知道用户的预算是多少,也不知道要采购几台,推荐出来的型号可能不靠谱。
直接丢弃早期对话的代价很直观:用户需要把说过的约束重复一遍,对话体验割裂。但反过来,如果把所有历史原文都保留呢?50 轮对话下来,对话历史的 Token 轻松突破一两万,上下文窗口是有限的,历史占多了,检索结果的空间就被挤压,模型生成答案的空间也不够。
能不能把早期对话浓缩成一小段摘要?不丢关键信息,又不占太多 Token。这就是本篇要讲的摘要压缩策略。
滑动窗口为什么不够用
1. 一个让系统失忆的场景
把刚才的场景画得更具体一点。用对话轮次来标注,看看滑动窗口在第 12 轮时的覆盖情况:
轮次: 1 2 3 4 5 6 7 8 9 10 11 12
│ │ │ │ │ │ │ │ │ │ │ │
预算 数量 品牌 型号A 型号B 配置 续航 外观 保修A 保修B 保修C 追问
5000 50台 推荐 对比 对比 讨论 讨论 讨论 政策 政策 政策 预算
←────── historyKeepTurns=8 ──────→
窗口保留:第 5~12 轮
第 1~4 轮已被淘汰 ↑
第 12 轮用户问“还有没有符合预算的型号”,窗口里没有预算信息。模型只能看到型号对比和保修讨论的上下文,预算 5000 元/台这个核心约束消失了。
这不是极端情况。在实际的企业客服场景中,用户经常在对话开头提出核心约束(预算、时间、数量、身份),然后花好几轮围绕细节展开讨论。等讨论深入了,窗口恰好把那些约束滑出去了。