大模型上下文管理实战:context-mode 设计与 token 优化方案

发布时间:2026/10/8 23:26:27
大模型上下文管理实战:context-mode 设计与 token 优化方案 1. 项目概述context-mode 到底是什么1.1 从一次翻车现场说起先讲一个我亲身经历的场景。半年前我在做一个智能客服机器人用户和机器人聊了大概二十轮之后机器人开始失忆——一会儿说刚才您说要退款请问订单号是多少一会儿又重复推荐已经推荐过的产品。排查到最后罪魁祸首就是上下文管理这块我傻乎乎地把所有对话历史全部塞进模型token 爆了不说关键信息根本没被模型记住等于钱花了、效果也没有。后来我把这套上下文管理逻辑单独抽出来起名 context-mode专门解决该记住什么、该丢掉什么、该压缩什么的问题。这里的 context-mode 可以理解为一套通用的上下文管理模式它不绑定某个具体模型而是一种设计思路加实现方案用来在有限的上下文窗口里尽可能高效地保留对回答最有用的信息。它解决的痛点非常明确对话越来越长之后模型要么超出窗口限制直接报错要么把早期重要信息冲掉要么被无关闲聊占据大量 token 导致回答质量下降。无论是做客服机器人、AI 写作助手、代码生成工具还是 Agent 系统只要你跟大模型打交道超过三轮对话就一定会遇到这个问题。这篇文章适合谁看如果你正在用大模型 API 做产品已经跑通了基本功能但发现多轮对话效果不稳、token 费用失控或者想给团队沉淀一套可复用的上下文处理方案那这篇内容就是给你准备的。我会把 context-mode 的设计思路、核心实现、参数配置和踩坑记录都摊开来讲照着做就能落地。1.2 context-mode 的核心职责一句话概括context-mode 是对话系统里负责上下文调度的中间层。它夹在用户请求和大模型 API 之间干三件事。第一件事是筛选。对话历史里不是每条消息都值得保留比如用户连续说了三遍你好、中间夹了一条在吗这些信息对当前回答几乎没有贡献留着只会浪费 token。context-mode 需要判断哪些消息有保留价值。第二件事是压缩。对于早期的重要信息比如用户的订单号、偏好、已经确认过的事实原文保留太费 token可以用摘要代替。比如把二十条关于退换货政策的讨论压缩成一句用户已知晓退换货规则七天内可无理由退换。第三件事是组织。筛选和压缩之后的内容要按照什么样的顺序、什么样的格式拼装起来喂给模型系统提示词放哪里、历史对话放哪里、当前问题放哪里、检索到的背景资料放哪里这些排列方式直接影响模型的注意力分布。这三件事说起来简单做起来全是细节。很多人以为上下文管理就是把历史消息都传过去实际上那是最低级的做法也是效果最差的做法。context-mode 的价值就在于把这三件事做成一套可配置、可观测、可替换的工程化模块。2. 设计思路拆解为什么上下文管理这么难2.1 上下文窗口的资源约束要理解 context-mode 的设计必须先搞清楚模型的上下文窗口是个什么脾气。你可以把上下文窗口想象成一张有限的草稿纸模型的所有推理都在这张纸上进行。草稿纸越大能写的内容越多但有两个代价费用和注意力。费用很好理解按 token 计费传进去多少就收多少钱。上下文从 2000 token 涨到 8000 token单次请求成本直接翻好几倍如果每天有十万次请求这笔账非常可观。注意力的问题更隐蔽但更要命。Transformer 模型对长文本的注意力是稀释的——信息越长模型对每条信息的关注度就越低特别容易忽略夹在中间的内容。有研究表明模型对长上下文中间部分的记忆是最差的开头和结尾记得牢中间全忘了。如果你把二十轮对话一股脑传进去模型真正盯着看的可能只有最后两轮。所以 context-mode 的本质是在有限的草稿纸上只写模型最需要看到的内容把字数控制在合理范围同时保证关键信息一个不少。这就像面试官面前只有一张纸你要把候选人最重要的经历写在上面而不是把所有简历页都堆过去。2.2 三种主流压缩策略我在设计 context-mode 的时候筛选对比了市面上几种常见的上下文处理策略各有各的适用场景。第一种是滑动窗口。只保留最近 N 轮对话更早的一律丢弃。这个方案最粗暴也最省事实现起来就一个数组加一个截断条件。问题是它不分轻重早期提到的关键信息比如用户的地址、手机号如果是三十轮之前说的到第三十一轮就丢了模型会问麻烦提供一下你的收货地址用户体验非常糟糕。第二种是先入先出淘汰FIFO。也保留最近 N 轮但把更早的内容压缩成摘要塞在系统提示词里。这样早期信息不会完全丢失但摘要怎么生成、多久更新一次、摘要和原始对话之间如何取舍都需要仔细设计。摘要本身也是要花钱调模型的频繁生成摘要在 token 成本上也不低。第三种是语义筛选。对每轮对话做重要度评分保留高价值信息丢弃低价值信息。这是最聪明但也是实现成本最高的方案需要引入额外的分类模型或规则引擎。比如检测到用户消息里包含订单号、金额、日期等实体就把这条消息标记为高价值强制保留。我在 context-mode 里用的是第二种为主、第三种为辅的混合策略正常的短对话走滑动窗口加摘要检测到关键实体命中时走语义保留规则。这个组合在效果和成本之间找到了一个不错的平衡点。2.3 我的策略选型为什么不做纯语义筛选两个原因。第一是延迟每次对话都要跑一遍实体识别模型增加了几百毫秒的响应时间放到实时聊天场景里挺明显。第二是不确定性分类模型有出错的可能万一误判把关键信息丢掉了故障排查时很难定位。所以我的原则是用确定性规则处理 80% 的常规场景用模型能力处理 20% 的复杂场景。具体来说常规对话轮次用固定规则判断比如超过 10 轮就把第 1-5 轮摘要化。实体命中规则用正则或者字典匹配检测到手机号、订单号、日期、金额等关键词立刻标记为高优先级保留。摘要生成只在两种情况下触发一是轮次超限二是用户主动切换了话题通过意图分类判断。这套策略的另一个好处是可预测。我随时能算出来当前上下文的 token 占用不会出现这次突然爆了的情况。之前那个纯滑动窗口方案就吃过亏——窗口设置了 20 轮但用户每轮都发很长很长的消息结果 5 轮就撑爆了 token 上限模型直接报错。注意无论采用哪种策略都必须在代码里做 token 数量的实时估算不能只按轮数截断。用户消息的长度分布差异极大按轮数控制根本不可靠。3. 核心实现context-mode 的关键环节3.1 上下文分层结构设计context-mode 的上下文在内存里是一个分层结构一共四层每一层都有自己的职责和更新策略。第一层是系统提示词层。存放角色设定、行为规范、输出格式要求。这一层基本是静态的只有在产品配置变更时才更新。需要注意的是系统提示词不应该被对话内容挤占位置它是所有回答的地基优先级最高永远完整保留。第二层是用户画像层。包括用户已经确认过的身份信息、偏好、关键事实。比如用户说过我需要发票这就是一条高价值信息要放进这一层。这一层在每次对话结束时更新把新发现的关键信息追加进去同时做去重。第三层是对话历史层。分两个子区摘要区和明细区。摘要区存放早期对话的压缩摘要明细区存放最近几轮的原始对话。明细区采用滑动窗口默认保留最近 6 轮摘要区通过定期合并生成。第四层是即时输入层。就是用户当前这一条消息以及上下文检索到的参考材料如果有 RAG 的话。这一层永远放最前面因为模型对最近内容的注意力最强。这个四层结构映射到 API 调用时的消息数组顺序是系统提示词 → 用户画像 → 摘要区 → 明细区 → 即时输入。你可以想象成写论文摘要放前面让读者先了解背景细节放中间提供证据最后是当前要解决的问题。模型读消息的顺序跟人一样先读的先建立基调最后读的最容易被注意。3.2 Token 预算分配分层结构定下来之后下一件事就是给每层分配 token 预算。我把模型窗口比作一个总盘子比如选用 8K 窗口的模型实际可用量按 7K 算留 1K 给系统预留和 API 返回余量。我的初始分配方案是这样的层级Token 预算策略系统提示词1,000超过则精简提示词文案用户画像500超限时按最近更新时间淘汰对话摘要区2,000超限时对旧摘要再次合并对话明细区1,500滑动窗口丢弃最旧原始消息即时输入1,500超长则截断或提示用户精简检索参考材料500按相关性分数截断这个分配不是拍脑袋定的。最早我做过一版把 70% 的预算全给了对话历史结果发现回答质量反而下降了——因为模型读了太多历史对当前问题的注意力被稀释了。后来我刻意压缩历史占比把更多预算留给即时输入和检索参考材料效果明显改善。这个经验放在大模型领域也同样适用该省的省该花的地方要舍得花。预算分配还有一个技巧就是动态调整。用户画像如果长时间没有新增信息说明这段对话没有出现新的关键事实可以把这部分预算临时让给检索材料。我实现了一个简单的预算借贷机制每层有一个基础预算和一个弹性上限用完基础预算但弹性上限没到的时候可以从当前不太活跃的层级借。3.3 上下文持久化与恢复context-mode 不能只活在内存里用户聊到一半关掉页面过两天再回来上下文需要能从存储中恢复。我用的方案是 Redis 加 JSON 序列化以会话 ID 为 key把四层结构完整存进去。Redis 的 TTL 设置为 72 小时。短于 24 小时用户隔一天回来就失忆了长于 7 天存储成本虽然低但上下文内容大多已经过时恢复出来反而干扰判断。72 小时是个平衡值。恢复的时候有一个细节值得注意摘要区要原样恢复明细区可以部分丢弃。用户隔了三天回来最近 6 轮的原始对话里可能有大量过期的临时信息比如闲聊的哈哈好的留着反而稀释注意力。我恢复时会把明细区只保留最近 2 轮其余合并进摘要区这样既省 token又不丢关键事实。持久化还有一个不可忽视的问题并发写。用户可能在多个设备上同时打开会话两个请求同时更新上下文后写的会覆盖先写的导致信息丢失。我的处理是给每个会话加了一个版本号更新时检查版本号冲突则做合并保留两边各自的新增信息删除两边都认为是旧信息的内容。这个方案不完美但足够解决绝大多数场景。4. 实操过程与核心环节实现4.1 关键代码结构拆解context-mode 我建议用独立模块实现不要跟业务逻辑混在一起。我习惯把它写成一个类对外暴露两个方法add_message(sender, content)和build_messages(current_query)。add_message负责接收新消息、更新各层内容、处理摘要合并、更新持久化存储。build_messages负责把四层结构拼装成 API 需要的消息数组同时做 token 估算和超限裁切。伪代码大概长这样class ContextMode: def __init__(self, session_id): self.session_id session_id self.system_layer load_system_prompt() self.profile_layer load_profile(session_id) self.summary_layer load_summary(session_id) self.detail_layer load_detail(session_id) self.budget TokenBudget(7000) def add_message(self, sender, content): # 1. 检测关键实体命中则写入用户画像层 entities extract_entities(content) if entities: self.profile_layer.update(entities) # 2. 追加到明细层 self.detail_layer.append(sender, content) # 3. 明细层超轮次上限时触发摘要合并 if self.detail_layer.count() MAX_DETAIL_TURNS: old_turns self.detail_layer.pop_oldest(SUMMARY_TRIGGER_COUNT) new_summary self.summarize(old_turns) self.summary_layer.merge(new_summary) def build_messages(self, current_query): messages [] messages.append({role: system, content: self.system_layer.text()}) if self.profile_layer.text(): messages.append({role: system, content: 用户已知信息 self.profile_layer.text()}) if self.summary_layer.text(): messages.append({role: system, content: 历史摘要 self.summary_layer.text()}) for turn in self.detail_layer.get_turns(): messages.append({role: user, content: turn.user_text}) messages.append({role: assistant, content: turn.assistant_text}) messages.append({role: user, content: current_query}) return self.budget.trim(messages)这个结构里最需要注意的是摘要合并的时机。我用了两个参数来控制MAX_DETAIL_TURNS设为 12SUMMARY_TRIGGER_COUNT设为 5。意思是明细区最多保留 6 轮每条消息算半轮不对一轮是 user assistant 两条所以 12 条消息等于 6 轮超过后每多 5 条就触发一次摘要合并。4.2 摘要生成的踩坑摘要这块是我踩坑最多的地方。第一次实现的时候我用了一个很蠢的方案每次触发摘要都对整个明细区做一次完整摘要结果发现摘要有时候比原文还长而且摘要里出现了原文没有的信息幻觉问题很严重。后来我改了两点。第一点是摘要只对即将被移出明细区的那部分内容做已经摘要过的历史不参与避免重复摘要导致的累计噪声。第二点是摘要生成时拆成两个步骤做重点区分先让模型提取硬性事实实体、数字、日期、确认过的决定再让模型概括争议和偏好。硬性事实用 JSON 格式输出方便后续放入用户画像层偏好和结论才放入摘要区。这里有一个非常关键的提示词技巧。摘要生成时我会在提示词里强调千万不要出现原文中没有的信息不确定的信息直接丢弃。哪怕摘要短一点也不能编造。原因很简单摘要一旦出现幻觉后续所有回答都会在这个错误基础上继续推理错误会被放大。4.3 突发超限的兜底策略就算做了预算分配和裁切还是会遇到极端情况。比如用户一次粘贴了 5000 字的文章进来直接把即时输入的预算打爆了。这种时候不能傻傻地截断到 1500 token用户体验会很差。我的兜底策略分三步走。第一步检测到即时输入超长先做一次快速过滤——把明显的无意义字符、重复内容、超大段空白过滤掉。第二步对内容按段落做重要性排序保留最重要的段落。这个排序我用了关键词匹配的近似方案不跑模型速度控制在 50ms 以内。第三步如果仍然超限把处理后的内容拆成两段第一段跟当前问题拼装请求模型第二段作为补充材料附带在下一轮再传。这套策略实测下来用户的极端输入从直接 500 报错变成了能正常得到回答虽然部分细节需要追问才能获取这在产品体验上完全是可以接受的结果。还有一个差点被忽略的细节API 返回的内容也要算进 token。很多人在估算上下文时只算请求里的输入忘了模型的输出也会占用窗口。如果模型返回一个 2000 token 的长篇回答而你的窗口已经被输入占了 6500 token这次请求大概率会失败。所以TokenBudget.trim()里我会预留至少 1500 token 给模型输出宁可让输入少一点也要保证请求能成功发出。5. 常见问题与排查技巧实录5.1 高频问题速查表把这段时间别人问得最多的问题和我自己踩过的坑整理成一张表有类似症状的直接查。现象根本原因解决方案模型重复提问已提供过的信息早期关键信息被滑动窗口丢弃引入实体识别命中实体写入用户画像层上下文 token 超限报错只按轮数截断未按 token 估算使用 tokenizer 做实时计数预留输出空间回答质量随对话轮次下降历史占比过高稀释当前问题注意力压缩历史预算提高即时输入比例摘要内容与原文矛盾摘要模型产生幻觉或在摘要中臆测摘要提示词强调不提原文未出现内容恢复会话后模型失忆持久化只存明细区摘要区未保存四层结构全部序列化存入 Redis多端并发导致上下文错乱更新冲突后写覆盖先写增加版本号冲突时做合并策略摘要越合越长费用飙升每次全量重摘要只摘要即将移出明细区的部分5.2 如何观测上下文状态context-mode 如果只是一套代码出问题时很难排查。我在设计时就加了一个调试模式关键是两条快照输出和成本统计。快照输出很简单在build_messages返回前把最终的 messages 数组打到一个日志文件里每条消息打上所属层级的标记比如 [SYSTEM]、[PROFILE]、[SUMMARY]、[DETAIL]、[CURRENT]。出问题时我可以直接看到模型到底收到了什么一眼就能定位是没传进去还是传了不该传的。成本统计是我后来才加的。每次请求记录三个数输入 token 数、输出 token 数、费用估算。看板展示后我发现一个有意思的现象摘要生成的 token 消耗占了总消耗的 20%这比我预期的高不少。于是我调整了摘要触发频率从每 5 条合并一次改成了每 8 条合并一次成本立刻降下来回答质量也没有明显变化。这个经验说明上下文管理模块本身的成本也要被监控不然很容易在优化主流程时忽略了管理模块自己烧掉的钱。提示调试模式下日志量大生产环境建议只保留异常请求的快照正常请求只记录 token 统计。我吃过一次亏全量快照一天写了几十个 GB 日志把磁盘打满了。5.3 排查超限问题的一个硬核技巧我分享一个排查 token 超限问题的做法。不要只看最终报错信息报错只告诉你超了不告诉你超在哪层。正确做法是让 context-mode 在构建消息数组时输出每层的 token 占比。我在TokenBudget.trim()里加了这么一段for layer, text in layers.items(): layer_tokens estimate_tokens(text) percentage layer_tokens / max_budget * 100 logger.info(flayer{layer}, tokens{layer_tokens}, percentage{percentage:.1f}%)然后按百分比排序哪层超了、哪层吃掉了大头一目了然。有一次我排查某个会话说 token 异常飙升一看日志发现用户画像层居然积累了 3000 个 token——原来是用户在对话里无意中提到了很多个编号号码全部被实体识别规则当成高价值信息塞进了画像层。我赶紧补了一条规则实体数量超过 20 条时只保留最近 10 条其余移到摘要区。问题立刻解决。这个技巧的核心思想是先量化再优化。不要靠猜去排查上下文问题先让系统把数据摊开给你看。6. 我最后想说的几件事context-mode 这套设计我前前后后改了四个版本从最初的纯滑动窗口到现在这个四层结构加预算控制的方案每一步都是被真实业务问题逼出来的。如果让我重新做一遍我会在一开始就想清楚两件事。第一件事是上下文管理的目标不是记住所有内容而是让模型在当前时刻做出最好的回答。想明白这一点很多取舍就简单了——该丢的丢该省的省一切以当前回答质量为最高优先级。第二件事是可观测性要和功能同步建不要等功能写完再补日志否则你根本不知道问题出在哪一层。最后分享一个我一直在用的技巧每次调完上下文策略不要只看一两轮对话的效果要跑一个 50 轮以上的长会话测试脚本专门检查早期信息能否在后期被正确引用。这个测试脚本我强烈建议你写一个因为它能把大多数上下文管理的问题提前暴露出来——等真实用户帮你测出问题的时候成本就高多了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询