☰
AI应用上下文管理实战:分层压缩方案详解
2026/10/13 7:13:46 网站建设 项目流程

我做过一个内部的智能客服项目,上线三个月后收到最多的用户反馈不是"回答不准",而是"它怎么又把我说过的话忘了"。用户在第十分钟重新提起自己五分钟前强调过的产品型号时,模型一本正经地给出了另一款产品的介绍。查日志才发现,根本不是模型变笨了,而是它的上下文窗口里,早期对话已经被后续内容挤出了视野——它压根没"看到"那条消息。

从那天起我开始认真研究 AI 应用里的上下文管理,也从"上下文窗口"这个词的关注者,变成了"分层压缩"这条工程路线的实践者。这篇想聊的就是这一路踩过来的真实经验:上下文管理到底在管什么、长窗口为什么不能一劳永逸、分层压缩的落地方案长什么样,以及那些坑在哪里。

1. 被"上下文窗口"卡住的第一课:先搞清楚它在系统里的位置

1.1 上下文窗口不是"聊天记录",是整个对话系统的核心状态

很多人对上下文窗口的理解停留在"模型能记住多少字的对话"这个层面,实际操作中最容易踩的坑也是从这里开始的。

先说定义。上下文窗口是大语言模型在生成下一个 token 时能够参考的输入区间,它包含系统指令、历史对话、工具返回结果、检索到的资料片段,以及当前用户的问题。在 API 层面,这些内容统称为 messages 数组;在应用层面,它就是模型每轮决策时能看到的"全部世界"。

一个常见的错误是把上下文管理和数据库存储混为一谈。数据库保存的是完整的原始消息,而上下文管理关心的是"每一轮请求里到底放什么进模型、放多少、按什么顺序放"。你可以在数据库里存一百万字的历史记录,但每次请求真正送入模型的,只有那有限的一段窗口。所以上下文管理的本质是筛选与压缩——在有限预算内,让模型每轮都能拿到当前最需要的背景信息。

我有个做开发的朋友,总喜欢把所有历史记录一股脑塞给模型。他一开始觉得窗口大就稳了,结果 token 消耗暴涨,单轮对话的接口延迟从 0.8 秒飙到 3 秒多,用户明显感觉到"卡"。这不是个例,几乎每个做长对话场景的人都会经历"从无脑全量到被迫做管理"的过程。

1.2 上下文管理要解决的三类典型问题

结合我自己做的项目,上下文管理要面对的核心问题大致有三类:

第一类是"溢出"。用户一轮轮聊下去,消息总量超过模型窗口上限,请求直接报错。这个最容易理解,但实际中很多人处理得很粗暴——直接从中间截断,结果模型失去前半段的关键信息。

第二类是"稀释"。窗口没爆,但内容太多太杂,模型把注意力平均分配给了无关信息。比如用户聊了半小时天气和闲聊后突然问"那刚才说的那个方案第 3 步是什么",模型需要从一堆无关消息里自己找重点,效果可想而知。

第三类是"失真"。早期信息虽然还在窗口里,但因为位置太靠前,模型对它的注意力权重下降,相当于"看见了但没注意"。这是 transformer 架构本身的特性,也是光靠扩窗口解决不了的根本问题。

理解了这三类问题,你就明白为什么"窗口越大越好"是个危险直觉。窗口只是容量,容量大不等于体验好,真正的难题是内容的质量、密度和结构。这也是后面分层压缩方案出现的直接动机。

2. 为什么窗口越来越长,"弱智"感却依然存在:两个被忽视的代价

2.1 长上下文的成本不是线性增长,而是"处处叠加"

长窗口背后的工程代价,很多人只在账单上感受过,但没有细算过账。

首先是 token 成本。假设每轮对话大约 500 token,20 轮之后的历史就是 1 万 token。如果每一轮都把全部历史带上,那么第 21 轮的请求就是 1 万 token 的历史加 500 token 的新输入;第 22 轮就是 1.05 万加 500;到第 50 轮,单次请求已经接近 2.5 万 token。这个量在按 token 计费的场景里,不是线性增长,而是"每轮都在替前面的所有轮次付费"。

其次是生成延迟。模型生成阶段的速度相对稳定,但输入阶段的 prefill(预填充)耗时和输入 token 量直接相关。在我的实际测试中,1 万 token 和 10 万 token 的输入,首 token 延迟可以差出好几倍。用户等一个回答也就算了,如果系统内部还要做检测、记忆、工具调用等多个环节,每一环都在大输入上反复处理,累积延迟更可观。

还有注意力稀释问题。近期的研究表明,当输入过长时,模型在长上下文任务上的表现并不会线性提升,反而在某些任务上出现退化。一个比较形象的比喻是:人读一份 50 页的报告,翻到第 40 页时还能一字不差记住第 3 页数字的概率很低,模型也一样——它只是在"物理可见"的层面都看到了,但注意力的分配并不均匀。

2.2 长上下文窗口带来的"虚假安全感"

更大的坑是心理层面的。自从主流模型陆续把窗口推到几十万甚至百万 token,很多人默认再也不用做上下文管理了。但窗口大不等于记忆好,这个错觉会让项目在早期埋下大量隐患。

举个实际案例。我曾在一个技术问答机器人项目里测试某长窗口模型,在连续 30 轮对话后输入一个非常早期的问题细节,模型给出了模棱两可的回答。换上同样的窗口、但把早期关键信息提前做高亮和结构化摘要后,同一个模型立刻就答对了。这至少说明:窗口只是舞台,你怎么布置舞台上的道具,才是决定表演质量的关键。

所以我的结论很直接:长上下文窗口是"最后一道保险丝",不是"第一选择"。真正可靠的做法是把上下文管理当成一种刻意的工程系统来设计——不是所有内容都有资格进窗口,也不是所有进窗口的内容都应该保持原始形态。这个思路,就是分层压缩的起点。

3. 从滑动窗口到分层压缩:我的方案演进路线

3.1 早期方案:滑动窗口与全局摘要各自的局限

我最开始用的是最朴素的滑动窗口:只保留最近 N 条消息。实现简单,效果也来得直接——上下文开销稳定了,用户最新的问题被完整覆盖了。但问题很快暴露:用户在第 5 轮说过自己"住在南山区,通勤希望控制在半小时内",到了第 40 轮聊通勤方案时,模型完全忘掉了这个约束。

后来我升级到"滑动窗口 + 全局摘要":所有超过窗口的消息异步生成一份摘要,作为系统提示词的一部分放在最前面。效果改善了不少,模型能"大概记得"早期内容了,但新的问题来了——摘要粒度太粗,细节大量丢失。用户问"你说那个方案里提到过 3 个候选地点,我记得第二个是在海岸城附近?",全局摘要只写了"讨论了通勤方案",抓瞎。

这两次尝试让我意识到:单一的上下文策略在真实场景里不够用。对话里有大量不同性质的上下文——有些需要保持原始精确,有些需要被压缩提炼,有些则是高频更新的动态信息。应该做的是把它们分在多个层次上,分别维护,再按需组装。

3.2 分层压缩的核心思想:原始、摘要、事实三级分层

我最终落地的方案,是围绕"原始层-摘要层-事实层"三个维度来组织的:

  • 原始层:保存最近几轮的原始对话。理由是最近的对话往往包含用户刚给出的明确指令、最新决策和尚未完成的任务依赖,这些内容一旦压缩就可能丢失关键细节。原始层的预算通常控制在总窗口的 40% 左右。
  • 摘要层:较早但仍有参考价值的对话,按"回合"为单位压缩成密度较高的摘要,比如"用户确认 A 方案,放弃 B 方案,原因是预算超限",而不是"讨论了方案选择"这种空话。摘要层可以进一步分级:短期摘要(数轮合并)和长期全局摘要(整会话的骨架)。
  • 事实层:从对话中抽取的结构化信息,比如用户偏好、业务规则、确认过的结论、待办事项。用键值对或卡片形式存储,例如"通勤约束:30分钟以内"、"方案倾向:A方案"。这一层的特点是精确、可检索。

这三层不互斥,它们在每次请求中按优先级拼接:事实层优先进入上下文,摘要层次之,原始层进一步补充。核心原则是:能用事实表达的内容不要用摘要,能用摘要表达的内容不要用原始对话。

3.3 为什么是"分层"而不是"更长的窗口"

有人会问:都分层了,窗口难道还会爆吗?会,但分层的意义不在于"无限延长记忆",而在于控制质量。

分层压缩相当于给模型做了一场"记忆整理":事实层是笔记本上的摘抄重点,摘要层是章节小结,原始层是最近几页的完整原文。人复习备考时不会在考场上带一百本教材,而会带提纲和错题本——模型也一样。窗口预算就是书包容量,分层压缩就是在容量有限的前提下,把最可能考到的内容放在最顺手的位置。

从工程实现上看,分层的好处也很明显:每一层的容量、触发条件和刷新频率都可以独立调优。比如事实层可以做到精确覆盖,摘要层有损但不失真关键信息,原始层保持最新语义完整。这种结构让系统的行为变得可预测,也方便出问题时定位到底是哪一层丢了信息。

4. 分层压缩的工程落地:触发时机、存储设计和组装策略

4.1 上下文组装时的优先级规则

在实现时,我把上下文的组装顺序固定为"事实层 → 摘要层 → 原始层 → 当前输入",每一层再按预算比例切分。

以我们团队的标准配置为例:一个 token 预算为 8000 窗口的对话应用,我的分配大致是:

层预算占比预算 token内容
系统指令8%约 640角色设定、输出规则、工具说明
事实层22%约 1760用户偏好、业务规则、已确认结论
摘要层30%约 2400早期对话的回合摘要与全局摘要
原始层30%约 2400最近 N 轮原始消息
当前输入10%约 800用户本轮问题和临时检索结果

这个比例不是拍脑袋定的,是根据多轮测试得出的平衡点:原始层太少会丢失近期的关键细节,事实层太少会让模型在长对话中"变笨"。你可以根据自己的场景调,但记住一个原则——当前输入和最近的原始对话永远是最重要的,它们的预算不应该被压缩挤占。

组装时还有两个细节:一是系统指令和事实层要放在最前面,这是模型注意力最强的地方;二是各层之间用清晰的标签分隔,比如"## 用户事实 ##"、"## 历史摘要 ##",能明显减少模型的混淆。

4.2 压缩触发条件:什么时候启动分层

分层压缩不能每次对话都跑,也不能等窗口快爆了才跑,否则要么浪费算力,要么来不及。我采用了一套基于 token 计数的触发机制。

每轮对话结束后,系统统计当前会话的总 token 数。当总量超过窗口预算的 60% 时,触发"轻度压缩":把最早的原始对话合并成摘要,释放原始层的空间。当总量超过预算的 85% 时,触发"深度压缩":除了合并原始对话,还会重新生成全局摘要,并抽取更新的事实卡片。

这里有个关键细节:压缩应该是异步的。你总不能每次对话都让用户等 2 秒钟去做压缩,再接着回复吧。正常流程是:

  1. 模型生成回复后,立刻返回给用户,同时后台异步启动压缩任务。
  2. 压缩任务完成,更新存储中的摘要层和事实层。
  3. 下一轮对话时,系统读取的是已经压缩完毕的最新状态。

异步压缩意味着要处理数据一致性——比如压缩过程中又有新消息进来怎么办。我采取的简单办法是加一个版本号,压缩前记录当前会话版本,压缩完成后如果版本没变化则更新,否则丢弃这次压缩结果重新排一次。

4.3 存储选型:消息、摘要和事实各放哪里

存储层我用过内存方案,也用过专门的数据库方案,实践下来分开存储比单一存储更清晰。

原始消息层适合追加写入和批量查询,一般用 Redis 的 List 结构或者 PostgreSQL 的消息表都行。摘要层是"一个会话一条全局摘要 + 若干回合摘要"的结构,用键值存储比较方便,key 是"会话ID:摘要类型:摘要序号"。事实层因为需要精确更新和查询,我建议用结构化存储,每条事实至少包含"属性名 + 属性值 + 更新时间 + 来源 round ID"。

一个容易被忽略的点是:token 计数不要以消息条数为单位,要用 token 数为单位。不同模型的 tokenizer 不同,同一个汉字在不同模型里可能占 1 个或多达 4 个 token,所以压缩触发逻辑必须和具体模型绑定,模型换掉了,token 计算逻辑也得换。

我们在生产里用了一个轻量的计数函数,每轮消息都记录其 token 数并在入队时累加,这样每次判断压缩条件时只需要读一个累计字段,不用重算整段历史,性能开销几乎为零。

5. 摘要质量与事实抽取:压缩不是简单丢字,是信息重组

5.1 回合摘要怎么写,模型才不会"丢失重点"

分层压缩里最难的不是架构,是摘要本身的质量。我一开始让模型做摘要时给了一句很笼统的指令:"总结这段对话",结果返回来的是"用户询问了产品价格,助手进行了回答"这种毫无信息量的废话。

后来我把摘要的写作要求改成了一套结构化模板,核心是要求摘要回答几个固定问题:

这段对话中用户表达了什么目标? 有哪些明确的信息(数字、时间、地点、偏好)? 哪些选项被提出、被接受或被拒绝? 有没有未完成的待办事项或需要下一步跟进的内容?

用模板约束后,摘要质量提升非常明显。比如原文 3000 字的一段对话,过去摘要可能是"讨论了项目排期",现在会变成"用户希望 6 月底上线,P0 范围固定为登录模块与支付模块,P1 的推荐模块暂缓;用户明确拒绝了每周同步会议的方案,倾向于每两周异步同步一次;待办:下周三前提供登录模块的接口文档"。

模板的另一个好处是可校验性。你可以在摘要生成后用规则检查是否覆盖了预设的关键字段(比如"待办事项"是否为 null),漏了就触发一次补充生成,避免模型自由发挥。

5.2 事实抽取:把"隐性信息"变成"显式状态"

事实层是分层压缩里最有价值也最容易被忽视的部分。它的目标是让模型不必从一段冗长对话里自己找线索,而是直接在一张"状态卡"上读取。

事实抽取的关键是要区分"客观事实"和"对话情绪"。在我的方案里只有两类事实值得抽:

一是稳定的约束条件,比如偏好、边界、禁止项,这类信息一旦确认就可以长期生效,直到用户主动推翻。 二是明确的决策结论,比如选定了某个方案、确认了某个需求。

抽取时机最好选在模型生成完摘要后,让模型同时输出一份 JSON 结构的事实卡片,格式类似:

{ "facts": [ { "attr": "delivery_deadline", "value": "2027-06-30", "confidence": 0.9, "source_round": 32 }, { "attr": "preferred_sync_frequency", "value": "biweekly_async", "confidence": 0.8, "source_round": 32 } ] }

每次新的事实卡片生成后和旧卡片做一次合并:相同属性名且新值更晚出现的覆盖旧值,不同属性名的保留。这样事实层始终是最新的决策状态,摘要层和原始层则保留了决策的过程细节,各司其职。

5.3 全局摘要的重写:不要持续追加,要周期性"重写"

另一个容易踩的坑是全局摘要的更新方式。我见过不少实现直接用一句话"把新摘要追加到老摘要后面",结果全局摘要越来越长,最后变成一份垃圾堆。

我的方案是做"重写式更新":每隔固定的压缩周期,把旧的全局摘要、本周期新增的回合摘要、以及最新的事实卡片一起丢给模型,让它重新生成一份更精简的全局摘要。这样做的理由是——旧摘要本身包含了冗余,只有重写才能去掉冗余,而持续追加只会在上面不断堆肥。

代价是每次全局重写都会消耗一定的 token 和延迟,所以我给重写设置了阈值:会话总 token 超过预算的 120% 时强制重写,平时只做回合级的小压缩。这个"120%"是一个安全冗余,因为压缩过程中可能产生临时的双份内容,留出余量防止溢出。

6. 效果验证与踩坑实录:哪些指标真的有用,哪些是自我安慰

6.1 用"重启测试"来验证上下文管理是否有效

搭建完整的分层系统后,最大的问题是:怎么证明它比简单方案好?只看用户反馈太慢,我采用了一个比较硬核的验证方法,我管它叫"重启测试":

  1. 找一段长对话,提前定义 5 个关键问题,问题的答案都藏在不同时期的对话里。
  2. 分别用"全量上下文"、"滑动窗口"、"滑动窗口+全局摘要"、"分层压缩"四种配置跑同一段对话。
  3. 让模型回答这 5 个问题,统计正确率。

这个方法能直观反应不同策略的差异。在我的测试集里(模拟了 60 轮客户服务对话),结果大概是这样的:

配置早期信息召回准确率平均输入 token平均响应延迟
全量上下文80%约 250002.8s
滑动窗口(最近 15 轮)40%约 75001.1s
滑动窗口+全局摘要65%约 90001.2s
分层压缩85%约 75001.1s

最有意思的是"全量上下文"和"分层压缩"的对比:一个是把全部都塞进去,一个是经过分层压缩后塞进去一半都不到的内容,结果准确率反而更高。这正好印证了我前面说的——窗口大不等于记忆好,内容的结构化程度比信息量更重要。

6.2 我在生产环境里踩过的五个坑

讲几个实际运营中遇到的教训,这些细节如果你没踩过,大概率会在某个深夜上线时遇到。

第一个坑是压缩任务和用户新消息同时发生时的"覆盖冲突"。我最初没有版本号控制,结果用户刚发完新消息,后台压缩任务就把旧状态写回去了,下一轮模型对新消息毫无反应。后来加了版本号控制,压缩前检查当前会话版本,如果变了就丢弃过期压缩结果,问题才解决。

第二个坑是 token 计算和模型不匹配。我之前用 A 模型的 tokenizer 计算,后来切换到 B 模型后没有同步更换计数逻辑,结果压缩时机判断严重偏离。换模型时一定要重新跑一轮测试来校准 token 预算,不能拿旧模型的经验值硬套。

第三个坑是"异步压缩导致的多实例重复压缩"。当应用部署了多个实例时,同一条消息可能被两个实例同时压缩,产生重复摘要。我的解决方案是把压缩任务放进一个统一的队列,每个会话只有唯一的消费者,避免并发冲突。

第四个坑是"敏感信息在摘要中的持久化风险"。摘要和事实层会长期留在存储中,如果在对话中用户说过一些不适合长期保存的内容(比如临时给出的内部账号),摘要生成时模型可能把它当成"关键事实"抽取出来。所以我在生成摘要的指令里明确加了过滤规则,凡是符合敏感模式(身份证号、内部凭证号等)的内容一律改写为"用户提供了相关凭证"。

第五个坑是"摘要的重写导致早期事实被新决策掩盖"。比如用户一开始说要红色款,后来改成蓝色款,旧事实被覆盖是对的,但如果整个会话的全局摘要也被重写掉早期决策的"原因",后续可能就说不清楚为什么改成了蓝色。我的做法是事实层里保留价值和更新时间两个字段,同时全局摘要里额外保留一段"决策变更记录",专门记录关键决策的前后变化。

6.3 分层压缩适合什么场景,不适合什么场景

最后说说适用边界。分层压缩不是万能药,它最适合的是"长对话 + 需要精确记忆 + 对成本和延迟敏感"的场景,比如客服助手、企业知识库问答、陪伴型聊天、多轮任务型 agent。

但也有一些场景不适合。比如单轮短问答场景,比如搜索式问答,上下文本身就是一次性的,分层压缩纯属浪费算力。再比如强依赖"原文逐字引用"的合规场景,比如医疗问诊或法务咨询,分层压缩的有损性可能导致关键措辞变化,这种场景应该优先选择全量记录或精确检索。

如果对话非常长、超过百万级 token,我会建议把长窗口模型作为"兜底",而把分层压缩作为默认路径,两层配合使用。长窗口保底保证极端情况下不溢出,分层压缩保证日常场景下的质量、速度和成本。

7. 最后再分享一点个人经验

做了几个月的上下文管理优化,我的整体体会是:这是一项"投入回报比"极高的工程。你不需要更换模型,不需要重新训练,只靠把上下文的组织方式从"无脑堆叠"改成"分层压缩",就能同时获得效果提升、成本下降和延迟降低三个收益。

如果让我给刚开始做这件事的人一个最小起步建议,我会说:不要一上来就做很重的分层系统,先实现"滑动窗口 + 回合摘要 + 关键事实卡片"这三个就够了。把这套跑通、验证有效后再往全局摘要和层级细化上扩展。我最初一上来就设计了五层体系,结果维护成本很高,后来反而砍掉了两层。

上下文管理本质上是一个"取舍的艺术"——在有限的信息展示空间里,决定给模型看什么、不给他看什么,比给模型一个无限大的窗口更考验设计功力。希望这篇能帮你少走一点弯路,文中的思路都可以按你的业务形态改成自己的版本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询