1. 上下文管理到底在管什么第一次听到“上下文管理”这个词很多人会下意识觉得它是个很虚的概念像是某种架构师画图时才用的黑话。但如果你真正写过稍微复杂一点的对话系统、Agent 应用或者哪怕只是一个带多轮记忆的客服机器人你就会发现上下文管理做得好不好直接决定了这个系统是“能用”还是“没法用”。我先把话说直白一点。所谓上下文管理本质上就是解决一个非常朴素的问题在有限的窗口里塞进最该塞的信息并且让模型能顺着这些信息做出正确的判断。它管的不是“记忆”本身而是“记忆的取舍、压缩、检索和注入”。你可以把它理解成一个非常挑剔的编辑手头有一堆素材但版面只有那么大必须决定哪些放头版、哪些做摘要、哪些直接扔掉。这个领域之所以最近被反复提起是因为几个现实压力同时压过来了。第一模型窗口虽然越做越大但成本和延迟并没有等比下降塞满窗口的代价依然很高。第二多轮对话一旦超过十几轮早期信息就开始被稀释模型会“忘事”。第三Agent 类应用需要跨会话、跨工具调用保持状态单纯靠拼接历史消息已经撑不住了。第四检索增强生成RAG把外部知识拉进来之后上下文里同时存在系统指令、历史对话、检索片段、工具返回结果冲突和噪声问题变得非常突出。所以上下文管理适合谁来学我的判断是三类人最该重视一是做对话产品和 Agent 的开发者二是负责 RAG 系统调优的工程师三是任何需要让模型在长流程里保持稳定输出的从业者。哪怕你只是做一个个人知识库问答上下文管理的好坏也会直接体现在回答质量上。接下来我不打算泛泛而谈而是按照我实际做项目时的思路把设计取舍、核心细节、实操流程和踩坑经验一层层拆开讲。你看完应该能直接照着搭一套属于自己的上下文管理方案。2. 整体设计思路与方案选型2.1 为什么不能只靠“把历史全塞进去”最原始的做法是把所有历史消息原封不动拼进 prompt。这个方案在对话轮次少的时候没问题但一旦轮次上去问题就集中爆发。我实测过一个客服场景当历史超过 20 轮之后模型开始把用户早期提到的订单号和新问题混在一起回答里出现了张冠李戴的情况。原因很简单注意力被大量无关历史稀释了关键信息没有获得足够的权重。另一个隐性成本是 token 费用。假设每轮对话平均 200 token20 轮就是 4000 token再加上系统提示和检索内容单次请求轻松破 6000。如果每天有上万次调用这个成本是实打实的。所以“全量拼接”只能作为兜底不能作为主策略。2.2 分层管理是更合理的骨架我比较推荐的思路是分层。把上下文拆成几个逻辑层每层有不同的生命周期和注入策略系统层角色设定、行为约束、输出格式要求。这层基本不变永远放在最前面。长期记忆层用户偏好、历史关键事实、跨会话结论。这层需要持久化存储按需检索。短期对话层最近几轮的原始消息。这层保留原文保证连贯性。检索层从知识库或文档中召回的片段。这层按当前问题动态生成。工具层工具调用的返回结果。这层通常只在当前轮有效用完即弃。分层的价值在于你可以针对每一层单独做预算控制和压缩策略而不是把所有东西混在一起做一刀切。比如系统层可以完整保留短期对话层保留最近 5 轮原文更早的对话压缩成摘要放进长期记忆层检索层限制条数和长度。这样整体 token 可控关键信息也不会丢。2.3 压缩与检索的取舍逻辑压缩和检索是上下文管理里最核心的两个动作但它们解决的问题不一样。压缩解决的是“历史太长”检索解决的是“信息太多”。很多人会把两者混为一谈结果做出来的方案既不省 token 也不准。压缩的常见做法是摘要。把早期对话交给模型生成一段简短总结替换掉原始消息。这里有个关键点摘要不是越短越好而是要保留“可追问的锚点”。比如用户说过“我上周买的那台设备”摘要里最好保留“上周”“设备”这些关键词而不是笼统写成“用户提到过购买行为”。因为后续用户很可能追问“那台设备保修多久”锚点丢了就接不上。检索的核心是召回质量。我的经验是不要只依赖向量相似度。向量召回擅长语义匹配但对精确的编号、日期、专有名词往往不够敏感。比较稳的做法是混合检索向量召回加关键词召回再做一个重排序。重排序这一步很关键它能把真正相关的片段顶到前面避免噪声片段占据宝贵的上下文位置。2.4 预算分配的实际考量上下文窗口是有限资源必须做预算。我通常按比例分配而不是拍脑袋定绝对值。一个比较通用的起点是系统层占 10%短期对话层占 30%长期记忆层占 15%检索层占 35%留 10% 作为缓冲给工具返回和模型输出。这个比例不是固定的要根据场景调。比如客服场景检索层可以再高一点因为答案主要来自知识库而陪伴类对话短期对话层权重应该更高因为连贯性比知识准确性更重要。我建议你把这个比例做成配置项方便后续按数据调优而不是写死在代码里。提示预算分配一定要留缓冲。我见过太多因为没留缓冲导致工具返回被截断、模型输出被硬切的情况体验非常差。3. 核心细节解析与实操要点3.1 系统提示的稳定性设计系统层看起来最简单其实最容易出问题。很多人把系统提示写得很长很细结果每次请求都带着一大坨固定文本token 浪费严重。我的做法是把系统提示拆成“不变部分”和“可变部分”。不变部分是角色和硬约束可变部分是根据当前场景动态拼接的少量指令。举个例子一个通用助手的不变部分可能是“你是一个严谨的技术助手回答要基于事实不确定时要说明”。可变部分则根据当前是问答、总结还是代码生成追加一两句针对性指令。这样既保证了行为一致又不会让固定开销过大。还有一个细节是系统提示的位置。绝大多数模型对开头和结尾的内容注意力更强所以硬约束尽量放在系统提示的开头格式要求可以放在结尾附近。这个不是玄学是注意力分布的常见规律实测下来对遵守率有可见提升。3.2 短期对话的窗口滑动策略短期对话层我一般用滑动窗口但不是简单地保留最近 N 轮。更细的做法是给每轮消息打一个“重要性分数”分数低的优先被移出窗口。重要性怎么算可以综合几个信号是否包含用户明确的问题、是否包含实体编号、日期、名称、是否是工具调用结果、是否被后续消息引用过。被后续消息引用过这个信号特别有用。如果用户在第 3 轮说了某个信息第 8 轮又提到了它那第 3 轮的重要性就应该被抬高。实现上可以做一个简单的引用检测把被引用的消息标记为“锚点消息”滑动时优先保留。窗口大小也不是越大越好。我实测过一个场景窗口从 5 轮加到 10 轮回答质量并没有明显提升但延迟增加了近 40%。所以窗口大小要结合延迟要求来定不能盲目求大。3.3 长期记忆的写入与召回长期记忆的关键在于“写什么”和“怎么取”。写入时机我一般选在两个点一是对话结束时做一次总结写入二是检测到用户表达了稳定偏好或重要事实时即时写入。即时写入要谨慎因为用户可能只是随口一说写进去反而造成干扰。我的做法是加一个置信度判断只有明确、重复出现或用户强调过的信息才即时写入。召回时不要一次性把所有相关记忆都拉进来。我通常限制条数比如最多 3 条并且按相关度和新鲜度加权排序。新鲜度很重要用户上个月说的偏好可能已经变了所以时间衰减因子要加上。存储格式上我建议用结构化字段加自然语言描述的组合。结构化字段方便过滤和排序自然语言描述方便直接注入上下文。比如一条记忆可以存成{type: preference, key: language, value: 中文, text: 用户偏好用中文交流, timestamp: ...}。这样检索时可以按 type 过滤注入时直接用 text。3.4 检索片段的去重与重排检索层最容易犯的错是“召回一堆相似片段”。向量检索天然会返回语义相近的内容如果不做去重上下文里会出现大量重复信息既浪费 token 又干扰模型。我的做法是在重排之后加一步去重用文本相似度做阈值过滤相似度超过阈值的只保留分数最高的那条。重排模型的选择也有讲究。如果预算有限可以用轻量的交叉编码器如果追求效果可以用更强的重排模型。但要注意重排本身也有延迟条数太多会拖慢整体响应。我一般先召回 20 条重排后取前 5 条再经过去重和长度过滤最终注入 3 到 4 条。注意检索片段的长度要控制。过长的片段会挤占其他层级的预算而且模型对超长片段的利用效率并不高。我通常把单条片段限制在 300 到 500 字超长的先做切分。3.5 工具返回结果的精简工具层是最容易被忽视的一层。很多工具返回的是结构化 JSON字段一大堆直接塞进上下文非常浪费。我的做法是在工具返回之后加一个“精简器”只提取后续推理真正需要的字段其余丢弃。比如一个查询订单的工具返回了 20 个字段但后续只需要订单号、状态和金额那就只保留这三个。如果工具返回的是长文本比如网页内容或文档那就先做摘要再注入。摘要的粒度要匹配当前任务不要做过度压缩否则模型拿到的信息不足以支撑推理。4. 实操过程与核心环节实现4.1 整体流程的搭建顺序我建议按这个顺序搭先做系统层和短期对话层保证基础对话能跑通再加检索层让回答有知识支撑然后加长期记忆层解决跨会话问题最后加工具层和精简逻辑。这个顺序的好处是每一步都能独立验证出问题容易定位。如果一上来就把所有层都堆上去一旦效果不好你根本不知道是哪一层拖了后腿。我踩过这个坑后来改成增量搭建调试效率高了很多。4.2 上下文组装的具体实现组装逻辑我一般写成一个独立的函数输入是各层的候选内容输出是最终拼好的消息列表。核心步骤包括按预算裁剪每层内容、按优先级排序、拼接成最终 prompt、做一次总长度校验。裁剪时要注意顺序。我的做法是先满足系统层再满足短期对话层然后按剩余预算分配检索层和长期记忆层最后留缓冲。如果总长度还是超了就从检索层开始砍因为检索层的可替代性相对较高。下面是一个简化的组装逻辑示意用 Python 写def assemble_context(system_prompt, short_term, long_term, retrieved, tools, budget): # 系统层完整保留 ctx [{role: system, content: system_prompt}] used count_tokens(system_prompt) # 短期对话层按预算裁剪 for msg in reversed(short_term): if used count_tokens(msg[content]) budget * 0.4: break ctx.insert(1, msg) used count_tokens(msg[content]) # 检索层和长期记忆层按剩余预算分配 remaining budget - used - reserve retrieved trim_by_budget(retrieved, remaining * 0.7) long_term trim_by_budget(long_term, remaining * 0.3) # 注入检索和记忆 if retrieved or long_term: ctx.insert(1, {role: system, content: build_memory_block(long_term, retrieved)}) return ctx这段代码的关键点在于短期对话用倒序插入保证最近的在前检索和记忆合并成一个块注入避免消息列表过于碎片化。实际项目中你还需要加上 token 计数、异常处理和日志方便排查。4.3 摘要生成的参数选择摘要生成我一般用较小的模型因为摘要任务对模型能力要求不高用大模型是浪费。温度设低一点0.2 到 0.3 之间保证摘要稳定。提示词里要明确要求保留实体和关键动作并且限制长度。一个我常用的摘要提示词结构是先说明任务再给出必须保留的信息类型最后给出长度上限。比如“请总结以下对话保留用户提到的所有编号、日期、名称和明确诉求控制在 150 字以内”。实测下来明确列出保留类型比笼统说“保留关键信息”效果好很多。4.4 检索召回的参数调优检索这块我通常调三个参数召回条数、相似度阈值、重排后保留条数。召回条数我一般设 15 到 25太少容易漏太多增加重排负担。相似度阈值不要设太高否则容易召回不足我一般设 0.6 左右作为初筛真正的筛选交给重排。重排后保留条数根据预算定通常 3 到 5 条。如果发现回答经常缺信息可以适当增加如果发现回答被噪声带偏就减少。这个需要结合具体数据反复调没有万能值。4.5 效果验证的方法验证上下文管理效果我建议做两件事。一是构造多轮测试集覆盖“早期信息追问”“跨会话偏好”“检索冲突”这几类场景看模型是否能正确接上。二是做消融实验分别去掉长期记忆层、检索层、摘要逻辑看效果变化从而判断哪一层贡献最大。我做过一次消融发现去掉摘要逻辑后长对话的准确率掉了将近 20 个百分点而去掉长期记忆层只掉了 8 个点。这说明在这个场景里短期对话的压缩比跨会话记忆更关键。不同场景结论可能不同所以一定要自己测。5. 常见问题与排查技巧实录5.1 模型“忘记”早期信息怎么办这是最高频的问题。排查顺序我一般是先看早期信息是否还在上下文里如果被裁掉了那就是预算或滑动策略的问题如果还在但模型没用上那可能是位置太靠前被稀释了可以尝试把关键信息在检索层或记忆层重新注入一次相当于“提醒”模型。还有一个技巧是把关键信息放在系统提示的结尾附近利用注意力对结尾的偏好。我实测过同样的信息放在开头和放在结尾模型的利用率能差 10 个百分点以上。5.2 检索片段互相矛盾怎么处理多来源检索很容易出现矛盾。我的处理原则是优先信任时效性更新的、来源更权威的片段并且在注入时明确标注来源和时间让模型自己判断。如果矛盾无法调和我会在系统提示里加一句“如信息存在冲突请说明冲突并给出你的判断依据”把矛盾显式化而不是让模型硬选一个。5.3 上下文超长被截断的应急处理即使做了预算偶尔还是会超。应急处理我一般分两步先砍检索层再砍长期记忆层最后才动短期对话层。短期对话层是连贯性的基础尽量别动。如果实在要动从最旧的消息开始砍并且把砍掉的内容做一次快速摘要补进去。提示一定要在组装完成后做一次总长度校验不要等到请求报错才发现超了。校验失败时要有降级逻辑保证请求能发出去。5.4 常见问题速查表问题现象可能原因排查方向处理建议模型忘记早期信息被裁剪或位置太靠前检查上下文内容与位置重新注入或调整位置回答被噪声带偏检索片段质量差检查召回与重排提高重排权重加去重响应变慢上下文过长统计各层 token 占比压缩检索层减小窗口跨会话偏好丢失长期记忆未写入检查写入触发条件增加即时写入与置信度判断工具结果被截断预算不足检查缓冲预留增加缓冲精简工具返回摘要丢失关键锚点摘要提示词不明确检查摘要输出明确要求保留实体与编号5.5 几个我踩过的坑第一个坑是过度压缩。早期我为了省 token把摘要压得很短结果模型经常接不上用户的追问。后来我把摘要长度放宽并且强制保留实体问题才解决。省 token 不能以牺牲可用性为代价。第二个坑是检索条数贪多。我一度把召回条数设到 50想着多召回总没错结果重排延迟飙升而且噪声片段反而更多。后来降到 20 左右效果和速度都更平衡。第三个坑是忽略工具返回的精简。有个工具返回了很长的日志文本直接注入后把检索层挤没了回答质量骤降。加了精简器之后才恢复正常。工具层一定要做字段级提取不能偷懒。第四个坑是长期记忆写入太随意。用户随口说的一句话被当成偏好存了下来后续对话一直被这个错误偏好影响。后来加了置信度和重复确认机制才避免了这类污染。6. 上下文管理的扩展方向这套框架搭好之后其实还有很多可以延伸的地方。比如可以把长期记忆做成可编辑的让用户自己查看和修正这样能进一步提升准确性。也可以引入反馈信号把用户对回答的满意度作为记忆权重的调整依据让系统逐渐学会哪些信息更重要。另一个方向是做上下文的可观测性。把每次请求的上下文组成、各层 token 占比、检索命中情况都记录下来做成面板。这样调优时就有数据支撑而不是凭感觉。我在项目里加了这个之后调优效率提升非常明显很多之前靠猜的问题都变得可量化了。如果你现在正准备做上下文管理我的建议是先从最小可用版本开始把系统层和短期对话层跑通然后逐步加层。每加一层都做一次效果对比确认有正向收益再保留。不要一上来就追求大而全那样很容易陷入调不动的困境。
阅读完成 · 觉得有帮助?