1. context-mode是什么先搞懂问题再谈方案1.1 我一开始理解的“上下文”其实全是错的我最早做对话类应用时“上下文”在我脑子里就是“把聊天记录拼起来一起发给模型”。于是代码里写了一个简单的列表每轮聊完就往里append一条消息等列表足够长了就截断最前面的。跑起来确实能聊但聊到十几轮就开始不对劲——模型开始忘记用户最初交代的需求开始重复之前说过的内容甚至把早先的临时信息当成最新指令来执行。后来我才意识到问题从来不在于“有没有上下文”而在于“以什么模式管理上下文”。同样的聊天记录全量塞进去、截断式塞进去、压缩后塞进去、检索后塞进去最终生成的回复质量天差地别。这个“以什么模式组织和使用上下文”的问题就是我一直想聊的 context-mode。所谓 context-mode核心就是一套上下文的管理策略哪些信息要放进模型视野、哪些要压缩、哪些要丢弃、哪些要按需检索回来以及每一步如何分配 token 预算。它不只是一个开关而是从数据组织、优先级排序、token 控制到一致性维护的整体方案。1.2 context-mode要解决的三个核心矛盾做了一段时间之后我把问题归纳成三对矛盾搞明白这三点后面选型就不会抓瞎。第一对矛盾是容量与完整。模型的上下文窗口再大也是有限的而真实对话产生的信息量是无上限的。用户聊一小时你不可能把所有内容都原样喂给模型。容量是硬约束完整只能是相对的。没有上下文管理时我们往往走到了两个极端要么粗暴截断丢失关键信息要么什么都不管导致 token 超限报错。第二对矛盾是成本与效果。很多上下文方案只考虑“让模型记性好”但每一步请求的 token 都是真金白银。全量上下文虽然效果稳定但长对话场景下成本可能翻好几倍。我在一个模拟项目X里做过统计不做任何裁剪时单轮对话的 token 消耗随轮数以二次曲线增长而做了上下文压缩之后总消耗能压到原来的三成以下回答质量却几乎持平。第三对矛盾是即时与稳定。系统既要能快速反映最新一轮对话中的新信息又要保证核心任务目标不被带偏。典型翻车现场是用户问了一句“你现在用简单的话总结一下”模型就开始全程用简单语气甚至连客服任务的身份都丢了。这就是典型的上下文被短期信息污染缺少任务级稳定上下文和局部动态上下文的隔离。context-mode 的本质上就是给这三个矛盾划出清晰的边界谁必须保留、谁可以压缩、谁可以丢弃、谁需要临时注入。接下来我从最常见的几种模式入手逐个拆解它们的适用场景和坑。2. 五种常见context-mode及选型逻辑2.1 truncation mode最简单的裁切这是最省脑子的模式维护一个固定大小的 FIFO 队列新消息进来旧消息出去。很多人的第一版系统都是这么做的我也一样。truncation mode 的适用场景非常窄——对话轮次少、单轮信息独立、历史对当前回答没有强依赖时它可以工作。比如一次性的问答工具、简单的表单填写流程用这个模式完全没问题。但它的缺陷也很直观。历史一旦被切掉模型对用户先前给出的约束条件就彻底失忆。我踩过的典型例子是用户的诉求中途发生过修改比如“不要用表格”改成了“用表格”但截断窗口只保留了后面的说法前面修改的意图被理解成矛盾信息模型直接懵了。所以truncation mode 我现在的定位很明确只作为兜底方案或者临时调试用。它保证系统不报错但保证不了质量。2.2 summarization mode压缩而非丢弃既然直接丢历史不行那就把旧内容“压缩”成摘要再保留。这就是 summarization mode。核心思路是被移出窗口的消息不丢弃而是通过一轮额外的模型调用把它压缩成精简的文字摘要。摘要占的 token 可能只有原文的十分之一但关键的信息点都留住了。举个例子原文可能是几百字的一段产品需求讨论压缩后变成一行“用户确认优先做扫码登录暂不做验证码登录界面配色偏好深色系。”模型拿到这行摘要一样能维持后续回答的一致性。实现上有两个细节要特别注意摘要的生成时机。我见过有人每轮都重新摘要全部历史成本高到离谱。更合理的做法是分段摘要超过阈值就触发一次压缩把最旧的一段历史变成摘要保留最近几轮原文。这就像写笔记一样旧内容提炼成条目新内容保留完整细节。摘要的分层维护。不要把所有摘要混在一个数组里。我习惯按时间或主题分成多个摘要块比如“目标摘要”“偏好摘要”“过程摘要”需要哪块就注入哪块而不是一股脑全塞。summarization mode 适合中长对话场景。它比 truncation 聪明的地方在于保留了核心信息代价是多了一次模型调用和额外延迟不过这个成本通常可控。2.3 retrieval mode按需注入如果说 summarization 是“主动翻旧账”那 retrieval mode 就是“要用时才查”。这个模式应用到 LLM 应用上就是我常说的 RAG 思路的变体。系统维护一个外部存储向量库或者普通数据库把历史消息按语义切块、向量化后存起来。每次新请求进来先做召回选出最相关的几个片段再和当前问题拼接送给模型。retrieval mode 最大的优势是让上下文窗口“跳出容量限制”。无论系统积累了多少历史每次实际进入模型的只有相关片段。这样既不会丢失太多信息也能精准控制 token 用量。但要注意检索不是万能的它强依赖两个前提切分策略要合理。太粗的 chunk 会携带噪声太细的 chunk 会切断语义。我后来实践下来按“独立事件”或“单轮问答对”切分比固定按字数切分效果好得多。比如一段用户描述需求的长文本可以按段落切再补上段落标题或时间戳作为辅助锚点。嵌入模型的质量不能拖后腿。如果你的向量化模型本身就不擅长区分对话意图召回结果大概率会跑偏。召回之后最好再加一个简单的相关性阈值不相关的片段不要硬塞。retrieval mode 适合知识库问答、长期记忆回访、多角色对话这类场景。它让“大上下文”成为可能但你不能指望它解决所有一致性问题因为召回天然是“近似”的关键任务型信息一旦没被召回模型照样会犯傻。2.4 memory mode短期与长期记忆分层聊到多轮对话时我发现很多系统把“上下文”和“记忆”混为一谈。其实这是两个不同的层级。短期上下文当前会话窗口内的信息直接存在于 prompt 里每轮结束后就消失。长期记忆跨会话保留的、需要长期遵循的用户偏好或任务状态存放在外部存储中需要时主动读回。memory mode 的核心是“分层”而不是“全量”。我做过的方案是这样组织的系统内置三层存储短期缓冲层保存最近几轮原文和 model 的对话窗口直接对接摘要层保存历史对话的压缩结果用于维护中长程的一致性长期记忆层保存更稳定的用户画像、任务目标和偏好信息不随会话轮次频繁变动。这三层各司其职。每轮请求时系统把三层内容按优先级组装成一个分段式 prompt长期记忆在最前面作为“背景设定”短期缓冲紧随其后作为“最新动态”摘要层居中作为“历史脉络”。模型回答时自然会更稳定地记住用户的核心诉求。memory mode 的代价是实现复杂度高因为每一层都需要各自的写入、更新、淘汰策略。但一旦跑起来对话体验会有质的提升。个人体会是长任务的客服机器人、复杂学习陪伴类产品特别适合这个模式。2.5 routing mode上下文感知的任务分发最后说一个较少被单独提起、但实战中很关键的模式routing mode。它的思路是不止是一份上下文作用于一个模型而是根据上下文内容把不同的任务路由到不同的处理模块或不同的 prompt 模板中。每一步都会重新审视当前上下文属于什么类型的请求——是闲聊、是查询、是行动指令还是系统维护类请求。我见过的一个典型实现是在对话入口加了一个分类层把用户最新一层的意图和整体上下文组合在一起做一个轻量分类然后决定走哪条处理链路。比如闲聊请求走简洁回复链路复杂分析请求走深度推理链路状态查询请求走工具调用链路。每条链路对上下文的组织方式、token 预算、信息注入粒度都不一样。这种模式的好处是让上下文管理不再“一刀切”能针对不同类型的请求做精细化处理。代价则是系统复杂度上升多了一个分类环节也就多了一个故障点。所以我建议 routing mode 等系统成熟之后再引入前期先把单链路的 context-mode 跑顺。以上五种模式不是互斥的我在实际项目中很少只用一种。通常的做法是长期记忆用 memory mode短时对话用 summarization mode知识性内容用 retrieval mode入口再加一层 routing 做分流。组合之后效果比单一模式好得多。3. 一个可直接复制的context-mode实现方案3.1 模块划分与数据结构很多人实现上下文管理是从 prompt 拼接处下手的但我建议先想清楚数据结构。上下文不是一串消息而是一个有结构的对象。我常用的核心结构是这样SessionContext会话级上下文持有当前会话的全部状态包括记忆层引用、token 总预算、上下文模式配置MessageRecord单条消息记录角色、内容、时间戳、token 数、优先级标签MemoryBlock记忆块可以是原文片段、摘要文本、或是检索索引项统一挂在 context 的 memory 层。整个模块可以拆成四个组件budget_manager负责 token 预算分配和超限检测compressor负责把旧文本转成摘要retriever负责从外部存储中召回相关记忆块assembler负责最终 prompt 的组装将各来源的信息拼接成模型输入。这四个组件各自独立方便替换和测试。我最初把逻辑全塞在 assembler 里后来重构才发现那个写法调试一次要半天。3.2 token预算分配算法token 预算是整个 context-mode 的骨架。没有预算控制再好的内容组织方式都是空中楼阁。我通常给每次请求设置一个总预算比如 8000 token具体值取决于模型的窗口和能力。然后把预算拆成几个固定的“槽位”系统指令固定占 1000 token当前用户问题及相关命令占 1000 token短期上下文最近 510 轮原文占 2500 token长期记忆与摘要占 1500 token检索片段和工具结果占 1500 token剩余的部分约 500 token作为生成回复的预留空间。注意这只是给 prompt 的输入预算回复生成的 token 是另一部分。如果模型窗口是 8000输入预算必须明显小于窗口值否则会出现“输入占满、输出没地方”的错误。预算分配不是写死的。当某一轮用户问题特别长时我会让 budget_manager 先从“非关键槽位”里借额度比如压缩检索片段的数量而不是动短期上下文的额度。这个优先级的判断逻辑我写成了一个简单的规则函数def allocate_budget(long_term_blocks, recent_messages, query_tokens): budget { system: 1000, query: min(1000, query_tokens 200), recent: 2500, memory: 1500, retrieval: 1500, reserve: 500, } over query_tokens - budget[query] if over 0: # 从检索槽位里扣优先保短期上下文 budget[retrieval] - over budget[query] over return budget这个函数不复杂但它明确了一件事出现超额时谁先让路谁必须保留。把这个取舍逻辑显式写出来能省去很多排查“怎么突然超限了”的时间。3.3 上下文压缩与优先级保留拿到了预算下一步是决定哪些内容以原文进入哪些以压缩形式进入。我的做法是给每条消息打一个优先级标签分三档P0任务目标、关键约束、用户明确指定的要求必须保留原文P1有价值的对话过程、重要中间结论可压缩但不可丢P2寒暄、重复表达、可推导信息可删除或极致压缩。每当上下文超过预算时先从 P2 开始清理再不够就把最旧的 P1 内容做摘要压缩P0 原则上不裁。这样做的好处是无论窗口怎么滚动任务锚点能一直留在模型视野里。具体到压缩触发时机我是按 token 总量判断的recent 槽位满了就取最旧的一段 P1 内容交给 compressor生成摘要后放到 memory 层原文从短期缓冲区删除。这里有一个值得注意的性能细节不要每轮都执行压缩。我设定为只有当 recent 槽位占用超过其额度的 80% 时才触发。否则高频的摘要调用会成倍增加延迟用户体验一下子就垮了。4. 实操过程与踩坑记录4.1 从单轮问答到多轮对话的改造我之前接了一个内部问答工具最开始是纯单轮的用户问一句、模型答一句没有历史记录。后来需求加了“连续追问”功能我才真正开始做 context-mode 的落地。第一步我把聊天记录按轮次存进一个本地列表并在每轮请求时带上最近 10 轮原文。刚跑起来就发现两个问题一是连续追问时模型分不清用户说的是新要求还是补充说明二是超过 10 轮后模型开始“失忆”。于是第二步我引入了摘要层。每当第 11 轮进来触发一次压缩把前 10 轮压成一段“会话纪要”。这样模型群体看到了“纪要 最近 10 轮”上下文容量和记忆质量都有了明显提升。第三步是给检索留出口。当纪要本身也很长时我把纪要切块放进向量库每次从里面召回和当前问题相关的一两块。至此这个问答工具的 context-mode 才算真正跑通。整个过程遇到的坑不比写的代码少。最大的一个坑是摘要层会自动产生“幻觉式结论”。有一次摘要模型把“用户尚未确定方案A”概括成“用户确定方案A”导致后续回答方向全错。从那以后我要求摘要生成时必须带上“未确认/待定”等状态标记并且在 prompt 里明确提示“不要臆测用户尚未明确表达的意图”。4.2 上下文污染比截断更隐蔽的坑context-mode 里最容易翻车的不是信息丢失而是信息污染。什么意思就是上下文里混入了不该影响后续判断的信息。最常见的是“指令级别的污染”。用户随口说了一句“你讲得不对”如果这句话直接进入上下文模型可能会改变后续的语气甚至立场。另一个版本是模型自己生成内容中的错误表述被当作真实状态存进了记忆于是错误开始自我强化。针对这个问题我在设计 context-mode 时加了三条约束上下文内容的写入必须区分“事实状态”和“情绪反馈”。情绪类信息不进入长期记忆只留在当前会话内最短的窗口里模型生成的内容不直接写回记忆只有经过一轮用户确认或者一致性校验之后才允许沉淀系统指令放在 prompt 最前端且固定不变。这样可以防止当轮用户的临时话语覆盖掉系统设定的任务角色。这三条听起来简单但做到之后明显感觉到系统稳定性提升了一大截。之前很多莫名其妙的跑偏本质上都是上下文污染在作怪。4.3 数据格式与模型输入的组装顺序组装顺序这件事看起来不起眼但影响很大。同样一批信息顺序不同模型的注意力和理解就有差异。我最终沉淀出来的组装顺序是系统指令固定长期记忆/用户画像摘要检索召回的知识片段如果确认相关历史对话摘要最近几轮原文当前用户问题工具调用结果如果是工具场景这个顺序的逻辑是把全局信息放在前面给模型先建立“背景”再给局部信息。最后放当前问题让模型把注意力集中到最后一项上。实测下来这种顺序比“当前问题放最前面”的版本理解准确率高不少。有个细节值得提醒检索片段不要混在对话流中间。我试过把召回片段穿插在历史对话里结果模型常常把知识片段误认为用户说的话导致回答风格突变。后来一律把检索片段单独成一个区块并在前面加一个标识“以下为参考资料不是对话记录”。这个问题就消失了。5. 常见问题与排查技巧5.1 速查表从现象到根因我在实战中整理了一个排查速查表遇到问题先对号入座现象可能根因排查方向模型忘掉用户早期的需求早期信息被截断且未摘要检查 truncation 逻辑确认摘要层是否生效token 频繁超限预算分配没有考虑回复预留空间检查 budget_manager 的槽位之和是否接近窗口上限回答风格不稳定系统指令被当轮对话污染检查 system 区块是否固定且隔离检索到的内容与问题无关切块太粗或嵌入模型不够强检查 chunk 切割策略设置相关性阈值摘要内容出现幻觉事实摘要模型没有约束其“确定性”表达修改摘要提示词要求区分事实与推测多轮后成本暴涨全量历史每次都原样注入检查是否启用了压缩/检索模式连续追问理解偏了当前问题和历史没有分开处理检查 prompt 组装顺序历史摘要是否过长这张表不能替代排查但它能帮你快速定位问题出在哪个组件上省去漫无目的地翻代码的时间。5.2 现场实录一个真实问题的排查过程有一次系统在用户聊到第 20 轮左右时开始把用户早期提到的一个临时例子当成固定要求反复在回答里强化。排查时我先看了字节数没超限又打印了 prompt发现系统指令还在短期上下文里有最近 10 轮摘要层有一段更早期的内容。再一看摘要内容问题找到了早期的那个例子在摘要时被写成了“用户要求采用该案例的风格”但实际上用户只是口头举例。也就是说摘要模型在做压缩时自动提升了一条“临时信息”的优先级。这个问题的根子不在检索而在压缩层的提示词缺少对优先级保留的控制。修复方法是在摘要提示词里显式加入规则“如果原文中的信息看起来是临时举例、假设或用户的主观表达不要将其总结为固定要求只保留描述事实的部分。”修完之后这个现象基本消失。这种“提示词级别的规则注入”是 context-mode 优化中最便宜也最有效的一招。5.3 成本控制与性能调优做 context-mode 的一个隐性收益是顺带把成本调优了。我实测的结果是同样的业务使用摘要 检索组合模式后单请求 token 消耗比全量模式平均下降了 55%70%而回答质量的用户评分不降反升。性能方面retrieval mode 的额外延迟主要来自检索和重排序。如果检索结果在 5 个以内通常只增加 100200ms但如果开了复杂的重排序模型那延迟就可能翻倍。我的建议是中小规模场景先用余弦相似度 简单阈值不要迷信重排序。等检索召回的瓶颈真正成为主要矛盾时再考虑升级。压缩调用的延迟可以通过“异步预压缩”来规避。也就是说在当前回复尚未结束时后台就把下一轮可能要压缩的内容先做好。这个技巧需要改造成预测性的写入策略但效果非常显著用户体验上几乎感觉不到压缩的存在。6. 写在最后的几点实战建议6.1 不要一开始就上全套方案context-mode 的完整形态确实诱人但我不建议从第一天就同时启用五种模式。每加一种模式排查问题的难度就上一层。我更习惯的节奏是先跑通 truncation再升级 summaries再引入 retrieval最后才考虑 memory 分层和 routing 分流。每层都验证有效之后再加下一层。6.2 上下文管理可观测性很重要给上下文系统加上日志甚至简单的可视化面板能省下大量调试时间。我每次调上下文都会打印一份“上下文组装快照”包含每一层的 token 数、内容来源、是否触发压缩。看得见才改得准。很多问题单靠脑补很难定位一旦能看到各层占比答案就在眼前。6.3 提示词是 context-mode 的一部分很多人只关注代码逻辑却忽略了写进模型视野的提示词本身也是一种上下文管理。我在压缩、检索、组装几个环节都通过提示词做了精细约束。这些提示词的调整经常比复杂的数据结构改动更能解决问题。做这个方向不要把自己完全当纯后端开发还得有“提示词工程师”的视角。6.4 模式是工具不是银弹最后说一句我反复提醒自己的话context-mode 是工具不是银弹。再精巧的上下文策略也补不上数据质量、模型能力或产品逻辑的短板。真正稳的系统是把这个模式的规则讲清楚、把取舍逻辑写明白、把异常路径测完整一遍一遍在真实对话里打磨出来的。每次打磨就是一次经验沉淀。等你把坑都踩过一遍你自然就明白为什么这个模式值得花时间去设计而不是随便写个 FIFO 列表了。