上下文压缩这事,四种做法里我最不信任的就是摘要替换。它看起来最省 token、最优雅,实际最常出事——用户三小时前说过的那句「这个别用 Redis」,进了摘要就蒸发。我的做法土得多:滑窗 + 显式 pin 约束,被用户否决过的方案一律原文留着,不交给模型去概括。
四种做法,成本不在你以为的地方
滑窗截断最简单,代价是遗忘变成硬性的:第 21 轮突然要用第 1 轮定下的口径,模型是真的看不见,不是"想不起来"。
向量召回补的是"相关内容",但它按语义相似度排。约束往往跟当前话题在语义上不像——"不要用 X"和正在讨论的 X 实现细节,embedding 出来离得近还是远,取决于你 embed 的是原句还是整段。这个不稳定因素很难调。
分层缓存(system / 长期记忆 / 近期对话 / 当前轮)是最好落地的,因为它把"什么进 prompt"变成显式配置,而不是让模型自己决定。我倾向把预算花在这一层上。
摘要替换的问题最隐蔽:它丢的不是信息,是否定信息。
摘要天然偏向"发生了什么",不偏向"什么被排除了"
模型做摘要时的默认目标是压缩叙述:谁提了什么、决定了什么、下一步干什么。约束是反叙述的——它记录的是没发生的事。"用户否掉了方案 B"经常被压成"讨论了方案 B",再压一轮就没了。多压几轮,剩下的全是正向结论,所有被否决的路径被洗得干干净净。
然后 agent 特别自信地把你上周毙掉的方案重新推一遍,还带着"我们之前聊过"的口气。这不是模型变笨,是它的输入里真的没有那条约束了。
我的做法:滑窗保近期,约束单独 pin
三层:近期若干轮原文,不做任何加工;一个 pinned 区,只放约束条目,每条是用户原话或工具报错原文,永不进摘要器;更早的历史才走摘要,而且摘要只当检索入口,不直接当上下文塞回去。
关键在 pin 的写入规则要机械,不靠模型判断。我现在的规则是:出现"不要 / 别 / 不用 / 以后别"这类否定,或者一次工具调用失败后换了方案,就把那一句原话追加进 pinned。append-only,不做去重合并——合并就是摘要,就是丢东西的开始。
代价是 pinned 会越来越长。我的处理是给它设预算,超了按"最近被引用"淘汰,而不是按语义合并。淘汰错了顶多丢一条旧约束;合并错了会悄悄改写一条还在生效的约束。这两种错的性质不一样,第二种你根本发现不了。
怎么验证,别靠感觉
自己造个小回归集:从真实会话里把每一个"用户否决"的节点抠出来,做成"给定这段历史 + 一个容易踩坑的新问题",看模型会不会再提那个被否的方案。跑一轮就知道你的压缩策略有没有把约束压没。这比任何公开 benchmark 都直接,因为它测的是你系统里真实存在的约束,而不是别人假设的约束。
这周外部没什么直接相关的料
HN 上基本是 agent 编排和安全话题,跟压缩策略正面相关的没有。有一条我觉得同源:i-have-adhd,一个让 coding agent 别把答案埋在长篇输出里的 skill。它治的是输出侧的显著性,压缩治的是输入侧的显著性,都是"关键信息有没有浮到最上面"。
OpenAI 那篇存储扩容提到 10 亿用户、每秒 2200 万请求。这说明存原始日志这一侧的成本还在往下走。既然留原文越来越便宜,"先摘要再说"就更没有理由了。
托管方案这边,OpenAI 的 Agents API 和 Meta 的 Muse 都在把会话状态往平台侧收。好处是不用自己维护,但你自己 pin 的那些条目能不能原样透传进去,得先试一次再决定要不要把关键约束托管出去。我不太信"平台会帮你记住"这句话。
也许半年后回头看,这些手写的 pin 规则会被更好的机制替掉。但按我现在看到的,摘要器还分不清"这件事没做"和"这件事没发生过"。