1. 上下文管理不是记性好而是知道什么时候该忘很多人第一次听到上下文管理这个词脑子里浮现的是让模型记住更多东西。这个理解不能说错但只对了一半。真正做过落地项目的人会告诉你上下文管理的核心矛盾从来不是记不住而是记太多——记太多会导致成本飙升、响应变慢、关键信息被噪声淹没最后模型反而答非所问。我接触这个概念是从一个多轮对话项目开始的。当时的需求很朴素做一个能陪用户聊十几轮、还能记住用户前面说过的偏好的助手。最初的做法简单粗暴把每一轮对话原封不动地塞进请求里。前五轮效果很好到第十轮开始变味到第二十轮直接崩——模型开始把早期无关的寒暄当成重要指令回答越来越跑偏而且每次请求的token量线性增长账单肉眼可见地往上走。这就是上下文管理要解决的真实问题在有限的上下文窗口里动态决定哪些信息该留、哪些该压缩、哪些该丢、哪些该外置存储。它更像是一个信息调度的工程问题而不是单纯的记忆容量问题。适合阅读这篇内容的人包括正在做对话系统、Agent、RAG应用的开发者被长上下文成本和延迟困扰的工程同学以及想理解为什么我的助手聊着聊着就傻了的产品和运营。下面我会把上下文管理拆成几个真正影响落地效果的层面来讲窗口预算怎么算、信息怎么分层、压缩和摘要怎么做、外部记忆怎么接、以及那些只有踩过坑才知道的细节。全程按我实际项目里的做法来说不堆概念。2. 先算清楚你的上下文预算再谈任何策略2.1 上下文窗口不是免费空间它是有价格的稀缺资源很多人把上下文窗口当成一个能塞多少塞多少的容器这是最危险的认知。窗口里的每一个token都在参与注意力计算都在影响延迟和费用。你得先建立一个预算意识这次请求我总共能用多少token其中系统提示占多少、历史对话占多少、检索到的资料占多少、留给模型输出的空间又是多少。我习惯用一个简单的分配模型来规划。假设模型窗口是128K token我不会天真地以为能用满128K。实际项目里我会这样切用途建议占比说明系统提示与工具定义10%~15%固定开销越精简越好检索/外置资料30%~40%按相关性动态裁剪历史对话20%~30%需要压缩和摘要当前用户输入5%~10%通常不大但必须完整保留输出预留15%~20%千万别忘了给回答留地方这个表不是标准答案但它逼你去思考一件事窗口是零和的。你多塞一段历史就少放一段资料你多留一点输出空间就得多砍一点上下文。想清楚这个取舍后面所有策略才有意义。2.2 为什么输出预留最容易被忽略我见过太多项目栽在输出预留上。表现是平时回答正常一旦检索到的资料稍微多一点模型就开始输出被截断或者干脆返回一个空响应。排查半天才发现输入把窗口占满了模型没有空间生成完整回答。提示无论你用什么框架都要在组装请求前显式检查输入token 预期输出token ≤ 窗口上限。这个检查要写成代码里的硬约束而不是靠人肉估算。具体做法是先估算本次期望的输出长度比如回答类任务预留800~1500 token摘要类预留300~500然后反推输入能用的上限。如果输入超了就触发裁剪逻辑。这个顺序不能反——先定输出再定输入而不是先塞满输入再看剩多少。2.3 token估算的坑别用字符数除以固定值新手常犯的错是用字符数 ÷ 4来估算token。英文勉强能用中文完全不准。中文一个汉字经常对应1~2个token标点、数字、代码符号的切分规则又各不相同。我的经验是估算阶段可以用粗略系数但关键路径上一定要用真实的分词器算一次。在工程上我会做两层第一层是快速估算用于日常裁剪决策误差可以接受第二层是在真正发请求前用对应模型的分词工具精确计算一次超了就再裁。这样既保证了性能又避免了因为估算误差导致的截断事故。这个双层的思路是我踩过几次估算说没超、实际超了的坑之后才定下来的。3. 把上下文分层不是所有信息都配得上永久保留3.1 四层结构系统层、摘要层、近期层、检索层上下文管理做得好不好很大程度上取决于你有没有给信息分层。我现在的项目基本都用这套四层结构效果稳定系统层角色设定、能力边界、输出格式要求、工具定义。这一层几乎不变属于宪法级别永远放在最前面。摘要层把较早的对话压缩成一段结构化摘要保留关键事实、用户偏好、已达成的结论。它替代了原始的多轮历史。近期层最近几轮的原始对话一字不改地保留。因为最近的交互往往和当前问题最相关压缩反而会丢细节。检索层根据当前问题动态召回的外部资料、知识库片段、历史记录。这四层的顺序也有讲究。我一般把系统层放最前摘要层次之然后是检索层最后是近期层和当前输入。原因是模型对开头和结尾的信息注意力更强这就是常说的中间遗忘现象把最关键的近期交互放在结尾附近能显著提升回答质量。3.2 为什么摘要层要用结构化而不是一段话早期我用一段自然语言摘要来压缩历史结果发现模型经常抓不住重点。后来改成结构化摘要效果立刻不一样。所谓结构化就是固定几个字段【用户目标】用户想完成什么 【关键事实】用户提到的具体信息姓名、数字、偏好 【已确认结论】双方已经达成一致的点 【待解决问题】还没搞定的部分 【情绪与语气】用户的沟通风格偏好这样做的理由是自然语言摘要会随着压缩次数增加而漂移越压越模糊而结构化字段每次更新时只改动对应部分信息保真度高得多。而且模型读取结构化摘要时能更快定位到它需要的那一类信息。3.3 近期层保留几轮最合适这个问题没有统一答案但有个判断方法看你的任务需要多长的指代链。如果用户经常说就按刚才那个来改成上面说的第二种那近期层至少要保留能覆盖这些指代的范围通常3~6轮。如果任务比较独立每轮都是新问题那保留1~2轮就够。我实测下来多数对话场景保留最近4轮原始对话是个不错的平衡点。再往前的内容压缩进摘要层。这个数字不是拍脑袋而是我对比过保留2轮、4轮、8轮的效果2轮经常丢指代8轮成本明显上升但质量提升有限4轮是性价比拐点。4. 压缩与摘要怎么忘得聪明4.1 触发时机别等窗口满了才动手最糟糕的做法是窗口快满了才紧急压缩。这时候你往往被迫做激进裁剪容易丢掉关键信息。我的做法是设置一个水位线当历史对话占到预算的60%时就开始把最老的一批对话滚动压缩进摘要层。这个滚动压缩是渐进式的每次只处理最老的1~2轮把它们合并进现有摘要。这样每次压缩的负担都很小摘要质量也稳定。等到真正接近窗口上限时摘要层已经消化了大部分历史你只需要处理少量近期内容从容得多。4.2 摘要不是复述而是提炼决策相关信息很多人做摘要就是让模型把这段对话总结一下结果得到一堆无关痛痒的复述。正确的摘要指令应该明确告诉模型只保留会影响后续回答的信息。比如用户随口说的天气、寒暄、重复确认这些都不该进摘要。我常用的摘要指令大意是从以下对话中提取用户的目标、约束条件、已确认的事实和未解决的问题忽略寒暄和重复内容用结构化字段输出。这个指令的关键词是会影响后续回答它像一个过滤器把噪声挡在摘要之外。4.3 压缩的三种粒度与适用场景压缩不是只有摘要一种手段我实际会按粒度分三档用粒度做法适用场景轻度删除寒暄、重复、确认类语句历史不长只想省点空间中度结构化摘要替换原始对话历史较长需要长期保留要点重度只保留结论和关键实体丢弃过程超长会话过程已不重要选择哪一档取决于过程信息对后续还有没有价值。比如一个调试类对话中间试错的细节可能对后续排查有用那就用中度而一个已经敲定方案的需求讨论过程就不重要了重度压缩即可。注意压缩是有损的且不可逆。所以我在压缩前会把原始对话落盘存档万一摘要丢了关键信息还能回溯。这个存档习惯救过我好几次。5. 外部记忆把记不住的部分放到窗口外面5.1 什么时候该用外部存储而不是硬塞进上下文一个判断标准如果某条信息不是每次都需要就不该常驻上下文。用户的长期偏好、历史订单、知识库文档这些都属于按需召回的信息应该放在外部存储里用检索的方式在需要时拉进来。硬塞进上下文的代价是双重的一是占用宝贵的窗口预算二是引入噪声干扰模型判断。我见过一个项目把用户全部历史订单都塞进上下文结果模型经常把三个月前的一笔订单和当前问题混在一起回答驴唇不对马嘴。改成检索召回后问题立刻消失。5.2 检索召回的关键查询要改写不能直接用原话直接用用户当前这句话去检索效果往往很差因为用户的话里可能缺少关键实体。比如用户问那个还能退吗直接检索那个还能退吗基本召回不到有用信息。这时候需要先做查询改写结合摘要层里的上下文把那个还原成具体对象再拿去检索。查询改写这一步我一般让模型基于当前问题 摘要层关键事实生成一个检索友好的查询。实测下来加了改写之后召回准确率提升非常明显尤其是多轮对话里指代频繁的场景。5.3 召回结果也要再排序和再裁剪召回回来一堆片段不能全塞进去。我会做两步先按相关性排序取Top-K再对每个片段做长度裁剪只保留和问题最相关的部分。因为检索片段里经常有大段无关内容整段塞进去纯属浪费预算。这里有个经验召回数量不是越多越好。我试过召回10条和召回3条对比3条经过精排的结果回答质量反而更高。原因是过多的召回片段会互相干扰模型难以判断哪条才是真正相关的。所以宁可少召回、精排序也不要多召回、全塞入。6. 那些只有踩过坑才知道的细节6.1 摘要层会累积误差需要定期重建滚动压缩有个隐患每次压缩都基于上一次的摘要误差会累积。压了十几次之后摘要可能已经偏离原始对话很远了。我的应对方法是定期重建每隔若干轮不用旧摘要而是从存档的原始对话重新生成一次摘要。这样能把累积的漂移清零。重建的频率看会话长度我一般每20~30轮重建一次。重建时虽然多花一点计算但换来的是摘要的准确性很值。6.2 系统提示要抗压缩别把关键约束埋在中间系统提示里的硬约束比如输出格式、禁止行为如果被放在很长系统提示的中间模型容易忽略。我的做法是把最关键的约束放在系统提示的开头和结尾各强调一次中间放次要说明。这个首尾强化的技巧在长系统提示场景下效果很明显。6.3 多轮对话里的话题切换要主动清理用户突然换话题时旧话题的上下文如果还留在近期层会干扰新话题的回答。我一般会做一个轻量的话题检测如果当前输入和近期对话的语义相似度骤降就触发一次话题切换处理——把旧话题的内容压缩进摘要层清空近期层让模型轻装上阵。这个检测不需要很复杂用简单的向量相似度就能做。加上之后多话题混合的对话质量提升很明显。6.4 别忘了给当前输入最高优先级所有压缩和裁剪逻辑里有一条铁律当前用户输入永远不能被裁剪。我见过有项目为了省空间把超长的用户输入截断了结果用户问的问题本身就不完整模型自然答不对。如果用户输入真的超长正确做法是提示用户分段或者对输入做摘要后和用户确认而不是偷偷截断。6.5 监控上下文构成别让它变成黑盒上线之后我会记录每次请求的上下文构成系统层多少token、摘要层多少、检索层多少、近期层多少。这些数据能帮你发现很多问题。比如某天发现检索层占比突然飙升一查是检索逻辑出了bug召回了大量无关内容。没有监控这种问题很难被发现。我一般会把这些指标做成简单的看板观察趋势。上下文管理是个动态过程不是配置一次就一劳永逸的。7. 一套可复用的上下文组装流程把上面的东西串起来我现在的项目基本都按这个流程组装上下文接收当前输入先做话题检测判断是否需要清理近期层。检查水位线如果历史占比超过阈值触发滚动压缩把最老的对话并入摘要层。查询改写结合摘要层事实生成检索查询。检索召回取Top-K并精排、裁剪。组装上下文系统层 → 摘要层 → 检索层 → 近期层 → 当前输入。精确计算token如果超预算按检索层 → 近期层 → 摘要层的顺序逐层裁剪系统层和当前输入不动。预留输出空间确认输入加输出不超窗口。发送请求并记录本次上下文构成。这个流程看起来步骤多但每一步都很轻量实际开销可控。关键是它把什么时候该忘、忘多少、怎么忘变成了明确的规则而不是靠感觉。8. 关于成本与延迟的几句实在话上下文管理做得好最直接的收益是省钱和提速。我对比过优化前后的数据同样的对话场景优化后平均每次请求的输入token降了大约一半响应延迟也跟着下降。这不是因为模型变快了而是因为要处理的信息变少了。但我要提醒一句不要为了省token而过度压缩。压缩是有损的压得太狠会丢关键信息导致回答质量下降用户反复追问反而更费token。我见过有人把摘要压到只剩一句话结果模型每轮都要重新问用户已经说过的信息体验极差。压缩的目标是去掉不影响回答的信息而不是压到最小。找到那个平衡点靠的是实测。我的建议是先按保守策略少压一点上线观察质量和成本再逐步调整压缩力度直到质量开始下降为止然后回退一档。这个试探边界的方法比拍脑袋定参数靠谱得多。上下文管理说到底是一门取舍的手艺。窗口是有限的信息是无限的你要做的是在有限里放最有价值的那部分。想清楚每一层为什么存在、每一条信息为什么留下比套用任何现成框架都重要。我在实际项目里最大的体会是先把预算算清楚再谈策略先把分层做对再谈压缩。顺序反了后面全是补丁。