news 2026/10/24 4:44:58

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型上下文管理实战: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设为 12,SUMMARY_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(f"layer={layer}, tokens={layer_tokens}, percentage={percentage:.1f}%")

然后按百分比排序,哪层超了、哪层吃掉了大头,一目了然。有一次我排查某个会话说 token 异常飙升,一看日志,发现用户画像层居然积累了 3000 个 token——原来是用户在对话里无意中提到了很多个编号,号码全部被实体识别规则当成高价值信息塞进了画像层。我赶紧补了一条规则:实体数量超过 20 条时,只保留最近 10 条,其余移到摘要区。问题立刻解决。

这个技巧的核心思想是:先量化,再优化。不要靠猜去排查上下文问题,先让系统把数据摊开给你看。

6. 我最后想说的几件事

context-mode 这套设计我前前后后改了四个版本,从最初的纯滑动窗口到现在这个四层结构加预算控制的方案,每一步都是被真实业务问题逼出来的。

如果让我重新做一遍,我会在一开始就想清楚两件事。第一件事是上下文管理的目标不是"记住所有内容",而是"让模型在当前时刻做出最好的回答"。想明白这一点,很多取舍就简单了——该丢的丢,该省的省,一切以当前回答质量为最高优先级。第二件事是可观测性要和功能同步建,不要等功能写完再补日志,否则你根本不知道问题出在哪一层。

最后分享一个我一直在用的技巧:每次调完上下文策略,不要只看一两轮对话的效果,要跑一个 50 轮以上的长会话测试脚本,专门检查早期信息能否在后期被正确引用。这个测试脚本我强烈建议你写一个,因为它能把大多数上下文管理的问题提前暴露出来——等真实用户帮你测出问题的时候,成本就高多了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 16:52:14

工业级电源路径保护:TPS259483+STM32G4构建嵌入式韧性供电系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 1:51:14

TPS259483AYWPR+STM32F303RE工业电源路径保护方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 13:37:34

零配置评论组件:为静态手账博客接入轻量无侵入的 Utterances 评论区

十月七日的午后,阳光透过阳台的白纱窗,在木地板上洒下一片片斑驳的光影。 我正在浏览自己刚搭好的静态手账博客,读到一篇前几天写的关于“水彩界面微卷阴影”的技术随笔。文章末尾空空荡荡,只有一行简单的版权声明。有几位常常一起…

作者头像 李华
网站建设 2026/10/23 4:07:03

PHP strlen()函数用法详解

前言 strlen() 返回字符串的字节数。注意,手册里写的是字节数,不是字符数——这是关于它最需要记住、也最容易出错的一点。 最常见的症状是:一段中文昵称,肉眼数着是 4 个字,strlen() 却报 12。「长度校验怎么总是提前…

作者头像 李华
网站建设 2026/10/23 22:14:07

上了 AGV 人效却没动:一次企业 AI 落地复盘,以及 FDE 的真实工作结构

摘要:一场从业者群聊里,有人用自家工厂的真实改造给出了完整答案——问题从来不在模型,而在计价规则、利益重构和预期管理。本文整理了这场讨论中最有价值的五个判断,包含四方激励逆冲模型、三个零硬件改造动作,以及一…

作者头像 李华