首页 / 资讯中心 / 文章详情

大模型上下文管理模式(context-mode)实战:从原理到代码实现

大模型上下文管理模式(context-mode)实战:从原理到代码实现 ★ FEATURED ARTICLE
context-mode 这个词乍一看像是某个编辑器插件里的开关选项。我第一次见到它是在一个对话式 AI 项目的技术方案评审里当时没太当回事后来被上下文溢出、角色混乱、答非所问这些问题反复摩擦才真正理解这个词背后的分量。说白了context-mode 解决的是一个特别朴素但又特别要命的问题系统在每一次做判断的时候到底该看到哪些信息屏蔽哪些信息这些信息用什么样的结构喂进去。这篇文章我会从大模型应用开发这个最常见的切入角度把这个词涉及的原理、关键技术、可落地的代码实现和踩过的坑一起讲透适合正在做智能问答、Agent、Copilot 类产品的开发者也适合想搞清楚上下文管理机制的技术人。1. 先搞明白context-mode 到底在解决什么问题1.1 三个层面里的上下文模式如果你在搜索引擎里搜 context-mode会发现这个词在不同领域有完全不同的解释。在编程语言层面它可能指一个贯穿方法调用的 Context 对象用来传递用户身份、事务信息或请求参数在交互设计层面它可能指应用根据用户所处页面、权限、时间段动态切换操作入口的能力而在大模型应用层面它指的是发送给模型的 token 组合——对话历史、检索到的知识片段、工具返回的结果全部塞进同一个窗口模型基于这堆信息生成回答。我最初以为这几个概念只是同名巧合后来发现它们背后有同一个核心逻辑让系统知道我是谁、现在在哪、下一步该怎么办。Context 对象是程序员手动维护这个答案交互设计是程序自动推导这个答案而大模型应用是把这个问题丢给模型自己去理解。当你的业务足够复杂这三个层面会同时出现并且互相影响。我在一个项目中就遇到过这种组合场景前端页面根据用户角色切换操作模式后端接口用 Context 传递调用链信息而最上层的 AI 助手又要根据用户当前的意图选择不同的 Prompt 策略。任何一个环节的上下文给错了整个链条都会出问题。1.2 为什么不能简单粗暴地全塞进去很多团队一开始的做法是把所有内容一股脑拼进 Prompt反正模型上下文窗口够大。这种思路在 demo 阶段确实能跑通但一旦真实用户连续聊了五十轮问题就全冒出来了。模型开始重复早期说过的话把用户第一轮提到的错误信息当成事实引用甚至忘记系统给它设定的身份。你以为是模型变笨了其实是你把太多无关信息堆到了它面前导致它对关键指令的感知被稀释。上下文的价值不是越多越好而是刚刚好。这就像给一个分析师开会前递材料你如果把他过去五年看过的所有报告全放桌上他大概率抓不住今天要决策的重点你如果只给一份摘要加上相关的三份附件他反而能快速给出高质量判断。context-mode 要解决的就是如何动态准备这份会议材料。一个设计得当的上下文模式管理器需要回答三个问题这段历史值不值得保留、用什么形态保留、在什么时机注入当前 Prompt。1.3 信息边界才是核心设计 context-mode本质上是在给模型的判断划定一个信息边界。边界太宽噪音淹没信号边界太窄关键信息丢失。我见过一个客服机器人的案例用户先问退货流程是什么接着又追问运费谁出。如果上下文模式是全量拼接式模型很可能把退货政策里的运费规则和当前会话混在一起回答得模棱两可。但如果模式设计成按意图分类存储系统会先识别出当前意图是退款运费咨询再只把相关规则碎片和订单信息注入上下文回答不仅准确还能自然带出用户订单里的真实商品信息。这就是信息边界的作用它不是简单地对上下文做裁剪而是对上下文做语义层面的重组。后面我会具体讲怎么实现这种重组。2. 上下文管理里绕不开的关键技术点2.1 窗口上限与注意力稀释先聊一个很多人都会误解的点模型上下文窗口从几千 token 涨到百万 token 级别是不是意味着以后不用做上下文裁剪了我的答案是别高兴太早。窗口变大解决的是能不能塞下的问题没有解决塞下去之后模型能不能用好的问题。当序列变得很长时注意力机制会出现一种被称作注意力稀释的现象——模型会把有限的注意力权重分散到非常多的 token 上那些真正关键、却藏在长文本中段的指令反而拿不到足够的权重。我在实测一个长文档问答项目时发现把相关片段从 8k 上下文挪到 30k 上下文里回答的准确率不升反降。原因很简单额外的 20k token 都是用户为了测试效果粘贴的无关资料它们把关键信息的信噪比拉低了。所以不管模型窗口有多大上下文都需要做筛选和聚合。这个原则在 context-mode 的设计中始终有效。2.2 三种经典的上下文压缩策略窗口再大也有用完的时候更别提实际部署时推理成本跟 token 数直接挂钩。上下文压缩大致有三条路线截断式Drop只保留最近 N 轮对话。实现最简单但会丢失早期信息用户如果中途改口模型可能察觉不到。摘要式Summarize把早期对话压缩成一段摘要。比如用户已经确认了收货地址是杭州西湖区正在询问发票抬头。这种方案能保留长期语义但代价是摘要的过程本身要消耗 token 和时间。结构化存储Structured Memory把对话中的关键信息抽成字段比如用户名、订单号、商品规格、当前意向。这种方式信息密度最高但需要定义清晰的信息抽取规则通用性稍差。真实项目里很少只用一种策略。我在设计一个购物助手的上下文模式时采用的是结构化为骨架、摘要为中间层、近期对话为细节的三层结构第一层存用户画像和订单状态字段第二层存每一轮会话的主题摘要第三层才放最近五轮的完整对话。这样的好处是模型在任何时候都能从骨架层拿到稳定信息从中间层拿到历史脉络从细节层拿到最新的用户表达。2.3 检索增强让外部知识动态进入上下文上下文不只来源于对话历史很大一部分来自外部知识库。这就是 RAG检索增强生成的用武之地。context-mode 和 RAG 的关系经常被搞混RAG 解决的是哪些外部文档该进上下文context-mode 解决的是这些文档以什么形态和顺序排列以及和对话历史如何融合。举个实际的例子。同一个法律咨询产品里用户问辞职需要提前几天通知单位。如果检索系统只做关键词匹配很可能把违法解除劳动合同的条款也一并检索出来。一个好的上下文模式设计会结合用户的意图分类结果只选取和员工主动辞职相关的条款然后把这些条款放在一个独立的提示区块里和对话历史分开前面再加上一句以下为相关法律规定请基于这些条款结合用户情况回答。这个区块划分和引用说明就是 context-mode 在 RAG 之上的额外价值。3. 实操从零实现一个简单的上下文模式管理器3.1 需求与模式选型与其空谈理论不如直接上一个可以抄作业的实现。我下面要写的这个上下文模式管理器是我在几个项目里沉淀出来的简化版核心目标是在一个有限的 token 预算内尽可能保留最有价值的上下文信息。先定义需求支持多轮对话历史的维护token 预算可配置。支持三种模式chat 模式最近对话全量保留、summary 模式早期对话压缩成摘要、field 模式关键信息抽成结构化字段。模式之间可以自动切换切换时上下文状态不丢失。我用 Python 写模型接口用 OpenAI 风格的 Chat API 做示例但整体思路不绑定任何特定厂商。3.2 核心类设计与关键参数先定义上下文模式的基础数据结构from dataclasses import dataclass, field from typing import Dict, List, Optional dataclass class Message: role: str # user 或 assistant content: str # 消息正文 timestamp: float field(default_factory__import__(time).time) dataclass class ContextState: user_profile: Dict[str, str] field(default_factorydict) # 结构化字段层 summary: str # 摘要层 recent_messages: List[Message] field(default_factorylist) # 近期对话层 class ContextModeManager: def __init__(self, max_tokens: int 8000, recent_rounds: int 5): self.max_tokens max_tokens self.recent_rounds recent_rounds self.state ContextState() self.mode chat def add_message(self, role: str, content: str) - None: self.state.recent_messages.append(Message(rolerole, contentcontent)) self._maybe_compact() def _estimate_tokens(self, text: str) - int: # 粗略估算中文约 1.5 字符/token英文约 3.5 字符/token # 更精确的做法是使用模型的 tokenizer这里为示例做简化 return int(len(text) / 1.5 1) def build_context(self) - List[Dict[str, str]]: context_parts [] if self.state.user_profile: profile_text 用户信息 .join( f{k}{v} for k, v in self.state.user_profile.items() ) context_parts.append({role: system, content: profile_text}) if self.state.summary: context_parts.append({role: system, content: f历史摘要{self.state.summary}}) # 只取近 recent_rounds 轮的对话 recent self.state.recent_messages[-self.recent_rounds * 2:] context_parts.extend( {role: m.role, content: m.content} for m in recent ) return context_parts代码的核心思路是分层system 层放稳定信息对话层放动态信息。build_context()输出的结构直接传给大模型顺序上有讲究——稳定的信息放前面动态的放后面模型会更容易把 system 层的指令当作长期约束。3.3 token 预算自动压缩_maybe_compact()是整个管理器的关键它负责在上下文超出预算时自动降级模式。我的实现逻辑是def _maybe_compact(self) - None: current_usage self._estimate_tokens( json.dumps(m.content for m in self.state.recent_messages, ensure_asciiFalse) ) if current_usage self.max_tokens * 0.7: return if self.mode chat: self._summary_recent_history() self.mode summary elif self.mode summary: self._update_user_profile() self.mode field # 如果压缩到最后仍然超限就只保留最近一轮 if self._estimate_tokens( json.dumps(m.content for m in self.state.recent_messages, ensure_asciiFalse) ) self.max_tokens: self.state.recent_messages self.state.recent_messages[-2:]这段逻辑看起来简单但有几个点我在实际项目中反复调整过压缩阈值不能定得太高。如果等 token 快用满了才压缩一次大段的摘要生成会短暂占用双倍上下文很容易打爆预算。我习惯在 70% 的占用率时就触发下一级压缩。压缩是有损的所以要控制压缩频率。频繁触发 chat→summary→field 的切换会让上下文状态一直不稳定。解决方式是在两次压缩之间加一个最小间隔比如至少相隔三轮对话。3.4 摘要与字段提取的实现摘要和字段提取需要调用模型。为了让这段代码不依赖具体的模型厂商我封装了一个_call_llm()方法实际使用的时候可以替换成任何 SDKdef _summary_recent_history(self) - None: content 请把以下对话压缩成不超过100字的中文摘要保留关键事实和用户意图\n \n.join(f{m.role}: {m.content} for m in self.state.recent_messages[:-2]) response self._call_llm(content) self.state.summary response.strip() def _update_user_profile(self) - None: content 从以下对话中抽取用户画像信息例如姓名、城市、偏好、当前目标。只输出JSON格式不要无关内容\n \n.join(f{m.role}: {m.content} for m in self.state.recent_messages) response self._call_llm(content) # 这里局简解析 JSON实际要用 json.loads 异常处理 import json data json.loads(response.strip().strip()) self.state.user_profile.update(data)这里有一个非常容易踩的坑摘要提示词里如果直接说请总结对话模型很容易输出用户询问了退货流程并提到了运费问题这种高度概括、却丢失了用户订单号、地区信息这类关键字段的内容。我的改进是强制摘要包含关键事实和用户意图并且把输入限制为早期对话给最近几轮对话留出空间。抽取字段时也要明确指名要哪些字段而不是让模型自由发挥否则模型会给你洒一堆锦上添花的修饰词。3.5 实测效果一个购物助手的上下文模式切换我把这套管理器接进一个演示用购物助手模拟了十五轮真实对话。用户的问询轨迹大概是咨询商品参数、确认收货地址、询问发票抬头、然后又绕回商品颜色选型。在默认 chat 模式下十五轮对话的体量大约 3500 token。当对话达到第十二轮时_maybe_compact()触发第一次压缩早期十轮对话被压成一段 90 字摘要里面保留了用户在杭州西湖区办公采购希望开增值税发票三个关键信息。第二十轮时再次触发压缩此时摘要层已经足够稳定系统把字段抽取后放进了 user_profile近期对话只保留最近四轮。整体上下文从 3500 token 降到了 1900 token而回答质量我在人工评估里反而感觉更稳了因为模型不再被早期重复的讨价还价过程干扰。这个结果符合我对 context-mode 的预期它做的不是简单的信息减负而是把信息结构化让模型把注意力集中在真正重要的变量上。4. 常见问题与排查技巧实录4.1 上下文溢出的处理现象调用模型接口时报错提示超过最大上下文长度。排查思路先确认是谁在涨。在build_context()里加一行日志输出用户画像、摘要、近期对话三部分的 token 占比基本一眼就能看出是摘要膨胀还是近期对话失控。我遇到过最典型的情况是模型生成的回答本身越来越长长期累积后触发了上限——这时候单纯限制用户消息是没用的还得给 assistant 的历史回复做截断。处理方案优先把recent_rounds从 5 调到 3降级速度最快如果还不够就把摘要的压缩比例调高。另一个实用技巧是给 assistant 历史回复单独设一个 token 上限比如单条不超过 400 token超了就压成系统已自动截断过长回复。4.2 上下文污染的识别与规避现象模型回答出现幻觉把不同会话的规则混在一起或者引用了当前会话里从未出现过的人名。这种问题通常不是模型本身能力不行而是上下文模式设计时把不该放的信息放进去了。我犯过一个典型的错误在实现字段抽取时把用户画像的更新逻辑写成了全量覆盖导致上一个用户测试时留下的偏好低价优先被带到了下一个会话。后来加了会话 ID 隔离字段抽取只在当前会话范围内生效。排查技巧检查注入上下文的 system 消息里有没有包含时间敏感或会话敏感的信息。如果有就在构建上下文时加一道过滤。4.3 模式切换时的状态同步现象从 summary 模式切回 chat 模式后模型突然失忆忘了用户之前确认过的信息。原因模式切换时summary和user_profile没有合并进新的上下文或者摘要生成的时候丢弃了太多关键细节。处理方案我在_summary_recent_history()里强制摘要输出一个固定结构【用户已确认信息】【待解决问题】【用户情绪/语气】。这样即使模式切换模型也能快速从摘要中恢复关键状态。还有一个很实用的小技巧每次压缩前的原始对话不要直接删除暂存到磁盘或 Redis保留 24 小时。万一后续用户追问一个只有原始对话里才有细节的问题可以临时检索回补。4.4 不同上下文模式的取舍速查模式优点缺点适用场景全量拼接实现简单细节不丢易超预算信噪比低短会话、内部测试最近N轮截断可控性强成本低丢失早期关键信息闲聊型助手摘要压缩保留长期语义摘要过程耗 token可能丢细节长会话、记忆型 Agent结构化字段信息密度高稳定需定义抽取规则灵活性差客服、购物、CRM 场景这个速查表是我给自己团队整理时写的实际选型时可以把它当成讨论的起点但最终的搭配一定来自你对自己业务数据的观察。建议上线前统计一下真实对话的平均轮数和平均单轮 token再倒推预算分配。5. 几个我反复用到的设计原则5.1 稳定信息优先放 system 层有些信息是不随对话变化的比如用户身份、业务规则、助手能力边界。这些信息应该放进 system 层并且放在 Prompt 的最前面。很多模型对 Prompt 头部内容的注意力权重会更高把你是 XX 助手你只处理 XX 业务放在最前面比放在对话中间效果好得多。如果有多块相对独立的稳定信息比如用户画像和业务规则我建议拆成两条 system 消息而不是塞进同一段话。实测下来分块后模型对不同信息块的切换更干净不太会出现规则条和用户画像串味的情况。5.2 动态信息要标明来源对于检索来的文档、工具返回的结果我的做法是在每条内容前加一行来源标注比如【检索来源退货政策_3.1节】。这个小小的改动能让模型在引用时更严谨减少信口开河。用户看到回答带出处可信度也会提升。5.3 把不知道也写进上下文很多上下文模式只管该放什么没有管不该有什么。但对我来说Model 的表现很大程度上取决于它是否知道自己不知道。所以在上下文中我会明确加一条当上述信息不足以回答用户问题时请直接说明需要补充的信息不要猜测。这一条虽然简单却能明显降低幻觉出现率。5.4 定期回看压缩产物上下文压缩是一个有损过程但很多人从不回头检查摘要和字段提取的质量。我建议每隔一段时间抽看几条被压缩后的摘要对照原始对话确认有没有丢掉关键信息。这个动作看起来笨但非常有效。我就在一次抽检中发现摘要逻辑会把用户多次提到的周六上门安装压缩成用户安排了安装结果模型在后续回答里丢失了对时间敏感度的理解。后来我在摘要提示词里强制加入保留所有时间、地点、数字、姓名的约束问题立刻解决了。做 context-mode 的时间越长我越觉得它不是一个技术问题而是一个信息治理问题。你不需要把每个 token 都用到极致但你需要清楚地知道哪些信息是必须保留的哪些是可以舍弃的哪些信息值得模型花注意力哪些信息只会干扰判断。一个好的上下文模式设计就像给模型配备了一个懂业务的分诊台护士帮它快速聚焦到真正要紧的问题上。如果你正在被长对话、上下文超限、模型忘记设定这类问题困扰我建议你先别急着换更大的窗口试着把上下文分好层、立好边界你可能会发现很多问题在源头就解决了。
阅读完成 · 觉得有帮助?
咨询建站