资讯动态

大模型上下文管理实战:五种模式与压缩策略全解析

发布时间:2026/10/15 22:56:54 来源:尧图企业网站定制
做AI应用开发最头疼的问题之一就是大模型的临时记忆——上下文。你刚交代过的任务要求多聊几轮之后模型就开始按自己的理解发挥换个用户进来上一轮对话的痕迹还残留在提示里导致答非所问同一个模型接口在不同场景下表现忽好忽坏其实也不是模型抽风大部分时候是上下文没有梳理好。context-mode这个项目核心就是把上下文从不可控的隐变量变成一个可以显式定义、灵活切换、精确控制的对象。它不是一个具体的开源框架而是一套我在多个项目里沉淀下来的组织方式和设计模式把上下文按照会话、任务、检索、继承、隔离这五种模式进行管理每种模式有自己的数据来源、组装方式和生命周期。做对话应用、AI客服、知识库问答、Agent类工具的同学都能从这套思路里找到对应的落地方案。以下是我在实际工程里反复调整后的完整拆解以及可以直接照抄的实现结构。1. 项目解读context-mode到底在管什么1.1 上下文失控的三个典型场景我先说结论大部分AI应用出问题尤其是那种功能看起来都对但体验总差一口气的问题十个里有八个出在上下文管理上。这里我举三个我真实踩过的场景。第一个是任务约束丢失。最早我做模拟项目X一个智能客服助手的时候给系统提示里写了回答必须控制在200字以内不能编造数据。前几轮对话效果很好但用户追问两三次之后模型就开始长篇大论甚至把没有的数据也说得有鼻子有眼。排查到最后发现根本不是提示词写得不好而是聊天历史的长度增长之后最初的任务约束被稀释了模型在后续生成时根本看不到那条指令。第二个是场景串场。还是同一个系统我后来加了一个文档解读功能用户可以在对话框里粘贴合同片段让模型分析。问题来了有的人在上一个文档还没结束的时候又开始聊别的话题结果模型一会儿分析合同、一会儿聊闲天角色来回横跳。原因也很简单不同场景需要的上下文是不一样的但当时我没有对不同场景做隔离所有内容全塞在一个上下文里。第三个是信息超载。一个知识库问答系统几千字的检索结果全量塞进提示词模型可能确实能记住但生成的回答变得非常啰嗦因为你塞进去了一大堆其实跟当前问题无关的段落模型分不清主次。更麻烦的是token开销直线上升钱花得快速度还慢。这三个问题都属于典型的上下文失控。临时记忆该记什么、不该记什么、按什么规则组织这是context-mode要回答的核心问题。解决方案也不是单独调整提示词而是把上下文管理作为一个独立中间层显式地设计它。1.2 context-mode的设计目标与适用边界这套模式的定位很明确它是应用代码和模型接口之间的一个上下文管理层。应用层只负责把业务动作发过来管理器负责从各种数据源收集信息、按模式组装上下文、裁剪压缩、再交给模型。业务侧不用关心最终提示词长什么样只需要声明当前处在什么模式。它解决的核心问题有三个约束不被冲淡、场景不互相干扰、上下文长度可控。实现方式概括成一句话就是按模式定义上下文按优先级组装按规则切换。适用边界我需要强调一下它不是提示词工程的替代品。提示词工程解决的是同一份上下文里怎么把指令写得让模型更听话的问题context-mode解决的是哪些信息该进入上下文、哪些不该进、怎么进去的问题。两者是上下游关系。如果你的场景只是简单的一次性问答用一个system prompt就能搞定那没必要上这套东西只有对话轮次多、知识来源杂、场景切换频繁的应用这套模式才能真正体现价值。2. 五种核心上下文模式怎么选型这一章直接给结论在多数业务场景里把上下文拆成五种模式基本就够用了——会话模式、任务模式、检索模式、继承模式、隔离模式。下面逐个说明。2.1 会话模式对话内的短期记忆会话模式对应的是多轮对话本身。它记录的是刚才说了什么包括用户消息、助手回复、中间产生的临时状态。这是最基础的模式没有它模型就是个每次回复都失忆的接口。设计会话模式时最容易犯的错是只存文本不存结构化信息。我一开始也是把所有历史消息拼成一个长字符串后来发现真正需要的时候根本没法精细化处理——比如想单独取出用户上一条消息做意图判断就得从一大段文本里找。后来我改成用列表存结构每次交互存一条记录每条记录带上role、content、timestamp以及自己加的round_id。这样无论是做裁剪、做摘要、还是定位某一条关键信息都很方便。另一个关键参数是会话长度的阈值。模型接口的上下文窗口是有限的会话模式不可能无限制增长。我一般按token数设定阈值超过阈值就触发压缩策略这个在第四章展开。阈值怎么定可以粗略按最大上下文窗口的70%来计算留出30%给任务模式、检索模式等其他模块。举个例子如果窗口是8k那会话模式最多占5.6k左右剩下2.4k给任务指令和检索结果。2.2 任务模式压住模型的行为基调任务模式对应的是模型在本次对话里应该怎么表现的指令层。它固定了角色、约束、回答风格、输出格式这些不随对话内容变化的东西。这一层就是把原来散落在system prompt里的公共指令抽出来独立成一个稳定的上下文块。之所以要单独抽出来是因为它会和会话模式产生冲突如果任务指令混在很长的对话历史里前面说过会被稀释。我见过一个极端案例某开发者的机器人助手任务要求是无论用户说什么都要保持冷静和礼貌但因为任务文本排在几千字的历史之后模型在某次激烈对话中直接模仿用户的语气开始回怼。单独抽出来以后我把任务模式块的优先级设为最高每次组装上下文时固定放在最前面这个问题就再没出现过。任务模式还有一个容易被忽略的作用它适合放动态参数。比如当前时间、用户姓名、所在页面、是否需要联网等。这些信息每次请求都会变化但又不算对话历史放在任务模式里作为系统级动态上下文非常合适。2.3 检索模式外部知识补全检索模式是知识库问答、文档助手类应用的核心。它的作用是把外部检索回来的相关信息作为上下文片段注入给模型。注意我这里说的是检索到的结果不是整个知识库——这是设计上很重要的一个点知识库是数据层检索模式是上下文层两个不能混。检索模式的难点在于注入的粒度。全量塞进去信息交叉污染严重只给Top1有时候答案又不够。我实操下来的经验是按段落粒度注入单段控制在300到500字以内一次最多注入3到5段并且每段要带上来源标识比如来源文档A第3节。一个隐蔽的好处是带上来源标识之后模型在回答中会更倾向于引用对应来源编造内容的概率明显下降。另外检索模式需要设置检索结果与用户当前问题的相关性阈值。常见的做法是设置相似度下限低于阈值的段落干脆不注入。很多问答系统体验差不是检索不到而是把一堆相关性都在0.6左右的模糊内容塞进去反而干扰了模型对正确答案的判断。2.4 继承模式跨会话的经验复用继承模式解决的问题是下次对话还想用上次的结论。用户和AI聊了一轮产出了一个重要结论比如这个项目采用方案B下次新会话里如果没有继承模式模型完全不记得。继承模式就是把那些关键结论从上一个会话中提取出来打包下沉在新会话启动时自动加载。实现上我通常给关键信息做一个沉淀标记。在会话过程中用规则或小模型识别哪些内容是需要长期记住的结论一旦命中就写入一个持久化的记忆存储。下次新会话启动时管理器从记忆存储里捞取与当前任务相关的结论作为继承模式块注入。这里有一个实用的坑继承信息不要全量注入。很多人把记忆理解成把所有历史结论全塞进去结果几十个结论堆在一起模型反而不知道当前该重点参考哪个。我的做法是限制继承模式最多注入3条、每条一两句话且必须与当前会话主题强相关。2.5 隔离模式多场景不串味隔离模式解决的是开头提到的场景串场问题。它的设计思路一句话就能说清不同场景domain使用独立的上下文空间切换场景时旧场景的上下文可以部分保留或归档但不参与新场景的组装。典型的落地场景是文档分析自由聊天混合的对话框。用户在分析模式下讨论一份合同切到自由聊天模式后系统应该把合同相关上下文归档而不是继续让模型带着合同的上下文聊其他话题。实现上就是在上下文中加一个scene_id字段组装上下文时只取当前scene_id下的块。隔离模式和继承模式经常配合使用隔离保证当前场景不被其他场景污染但如果你希望某个结论跨场景传递就显式地把信息从隔离空间中提升到继承模式。这个显式提升的操作很重要——它要求你在设计业务逻辑的时候就明确哪些信息可以跨场景而不是让系统自动乱猜。3. 核心实现一个可运行的上下文管理器光讲概念没用这一章给出一个可以直接落地的最小实现框架。我以Python为例但思路本身和语言无关。3.1 整体架构与调用流程管理器放在应用代码和模型接口调用之间。假设原来你的代码是这样调用模型的resp model_client.chat(messages[{role: system, content: system_text}, ...历史消息...])改造之后变成两步。第一步应用侧声明会话和模式manager.start_session(session_ids_001, scenecustomer_service) manager.switch_mode(task) manager.set_task_params(agent_name小助理, toneprofessional)第二步应用侧发业务动作管理器自动组装完整上下文然后调用模型manager.add_user_message(你们产品的退款周期是多久) reply manager.get_reply()这样做的好处是业务代码里再也不用拼装字符串提示词了所有上下文组装逻辑收拢到管理器内部。出了问题只需要调试管理器不用满项目去找散落的prompt拼接代码。调用流程拆解下来是这样一个环动作进入管理器 - 按当前模式收集上下文块 - 按优先级排序 - 执行压缩若超限 - 拼成最终消息 - 调模型 - 收到回复 - 归档回会话记录。3.2 上下文对象的结构设计上下文块是最小单元我的经验是至少包含下面几个字段字段类型说明modestr属于哪种模式session/task/retrieval/inherit/isolatecontentstr实际文本内容priorityint组装时的优先级值越大越靠前scene_idstr所属场景隔离模式靠它区分timestampfloat写入时间用于排序和过期淘汰token_estimateint预估token数用于压缩判断sourcestr来源标识如user_msg/doc_retrieval/rule这里特别提醒token_estimate不要用精确的tokenizer实时计算性能扛不住。用字符数除以3或者0.75词数之类的粗估就行。我们只要知道大约多少token用于判断是否超限没必要精确到个位数。块的组装逻辑是这样管理器维护一个blocks列表新信息到达时append进去。准备调用模型时执行_compile_contextdef _compile_context(self): # 按场景过滤 scene_blocks [b for b in self.blocks if b.scene_id self.current_scene] # 按优先级排序 scene_blocks.sort(keylambda b: b.priority, reverseTrue) # 逐块累加控制总量 result [] total 0 for block in scene_blocks: if total block.token_estimate self.max_tokens: break result.append(block.content) total block.token_estimate return \n\n.join(result)这段代码很简单但它体现出两个关键设计按场景过滤保证了隔离按优先级排序保证了任务指令这类高优先级内容不被会话历史挤掉。3.3 模式切换与调度逻辑模式切换的调度是整个管理器最容易出bug的地方。我建议的规则是切换模式时不删除已有块只改变当前活动模式的作用范围。也就是说切换到任务模式后会话模式的历史块仍然存在只是组装时是否包含这些块由模式决定而不是把历史清空。具体到每种模式的路由规则我设置了一个注册表mode_registry { task: {build: build_task_block, scope: global}, session: {build: build_session_block, scope: scene}, retrieval: {build: build_retrieval_block, scope: scene}, inherit: {build: build_inherit_block, scope: global}, isolate: {build: build_isolate_block, scope: scene}, }scope字段区分全局块和场景块。全局块task、inherit在所有场景下都参与组装场景块session、retrieval、isolate只在本场景下参与。切换场景时场景块不再参与组装切换模式时真正变的是路由逻辑会去构建哪些新块以及哪些块被激活参与组装。一个很容易踩的坑模式切换时丢失上下文。比如用户从文档分析切回自由聊天你把分析模式的块全部删了然后用户说刚才那份合同的有效期是多久模型就答不上来。正确做法是归档而不是删除——分析模式的块仍然保存在存储里只是不在当前上下文中参与组装如果业务需要可以随时通过检索或者显式回退再唤回来。4. 上下文压缩让长对话不失忆的几种手段上下文压缩是context-mode里最后一道关键工序。无论模式划分得多好长期对话累计的信息总会涨到超限。这里给出三种我在实战中验证过的手段以及它们的取舍。4.1 滑动窗口简单但粗暴滑动窗口的思路是只保留最近N轮对话更早的直接丢弃。实现最简单开销也最低但缺点明显较早的、可能仍是重要信息的内容会被无差别丢弃。比如用户在第3轮说过我的预算上限是5万聊了30轮之后这个约束就被窗口挤掉了模型后面会按无此约束的方式回答。滑动窗口适合那些对话主题变化快、历史依赖很弱的场景比如闲聊、轻量咨询。窗口大小一般按轮数或token数设定轮数通常7到15轮比较合适token数则要和max_tokens联动通常设在max_tokens的40%到50%。4.2 摘要压缩保留骨架摘要压缩是把历史消息用模型或算法压缩成一段概述。比如用户咨询了退款流程要求提供书面说明客服已承诺48小时内回复。它解决了滑动窗口丢信息的问题但也引入新问题摘要会损失细节。实操上我推荐一个折中方案会话最近3轮保留完整消息更早的历史做成摘要每5轮再对摘要做一次二次压缩。这样既保住了最近对话的即时性又让早期内容不会无限膨胀。摘要本身也是上下文块按优先级放在会话块之后。4.3 关键信息提取结构化记忆关键信息提取比摘要更进一步不是保留大概讲了什么而是提炼出必须记住的事实。它可以是一组键值对预算5万、退款周期48小时、用户身份企业客户。结构化之后这些信息就不再是一段需要重新理解的文本而是可以直接作为条件参与后续决策的硬约束。用大模型做信息提取会多一次模型调用成本和时间都要算进预算。我的实测建议是不用每次都提取而是设一个触发条件。比如每满3轮或检测到用户表达了某种结论性语句时才执行一次提取。这样既控制了成本又能及时把重要结论固化下来。4.4 三种手段的实测取舍我自己在项目里实测过一组对比数据能直观看出三者的区别手段上下文保持能力成本与耗时适用场景滑动窗口低极低闲聊、轮次少摘要压缩中中客服对话、长篇交互关键信息提取高高合同分析、决策支持类我现在的默认策略是组合拳最近3轮原文保留 早前历史摘要 关键结论结构化三层叠加。实测下来在max_tokens为8k的场景里能把有效对话延续到30轮以上而且早期关键信息如预算、时间约束的保真度从不到40%提升到了接近90%。5. 典型问题排查与避坑经验最后一部分把我在实际调试过程中踩过的坑整理成一份速查表。有几个问题特别隐蔽如果你正在调试类似系统直接对照排查。5.1 模式定义太多反而互相干扰我见过有人把模式拆到十多个结果组装上下文时不仅没好处反而因为各模式块之间内容重复模型被互相矛盾的信息弄糊涂。经验是模式数量控制在5个左右且每个模式要有明确的功能边界切忌为了架构好看而拆。排查方法在配置里打开调试开关打印每次组装后的完整上下文人工扫一眼哪些块是冗余的。冗余的块删掉比调整提示词有效得多。5.2 压缩后模型前后矛盾压缩本身不会让模型变傻但压缩策略如果丢了关键约束模型就会在后续回复里和之前打架。比如前面承诺了周一给结果压缩后这条信息没了模型在回答时可能会说可以尽快具体时间看情况。我的解法很朴素压缩前把硬约束类信息时间、金额、承诺、身份单独抽出来不管怎么压缩这些键值对始终保留在任务模式或继承模式里不参与摘要压缩的丢弃范围。相当于给这些信息开了免死金牌。5.3 上下文构建耗时严重上下文管理器如果每个请求都做检索、做提取、做压缩延迟会非常难看。我实测发现最耗时的是关键信息提取和外部检索。优化手段有三个缓存、异步、预构建。缓存同一场景、同一会话的检索结果可以按问题哈希缓存相同问题直接命中。异步关键信息提取不阻塞用户回复可以在生成回复的同时后台异步做下一轮再使用。预构建对任务模式和继承模式这种高稳定性的块提前拼好存起来每次请求直接复用只有会话模式和检索模式需要动态构建。5.4 敏感信息泄漏风险隔离模式做得不好最严重的后果是信息泄漏。我在项目早期犯过一个错两个用户共用一个场景ID导致用户A的对话内容被用户B的请求检索到了。虽然只是测试环境但当场吓出一身冷汗。排查建议场景ID一定要绑定到会话源头不能全局复用每次组装上下文前检查所有块的scene_id是否都等于当前场景ID涉及用户隐私的数据在进入上下文之前做脱敏处理。这些不是性能问题是底线问题优先级最高。再提醒一个细节模型输出也可能泄漏上下文。如果系统允许用户分享对话记录分享出去的记录可能会带上其他场景的检索内容。做分享功能时要把隔离模式的范围一起带过去或者干脆只导出当前场景的可分享内容。写到最后我个人的体会是context-mode这套方法不是一次设计出来的而是被真实业务里一个个问题推着迭代出来的。刚开始我也觉得不就拼个上下文吗直到对话一长、场景一多、约束一复杂各种奇怪的表现都冒出来才意识到上下文管理本身值得用一个中间层来专门治理。如果你正在被模型答非所问上下文越聊越乱之类的问题折磨不妨先别急着改提示词试试把这五类模式梳理清楚。模式理清了很多问题会自己消失。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑