开头聊一个最近折腾得最多的东西context-mode也就是 LLM 应用里的「上下文管理模式」。做 AI 应用快两年了我发现 80% 的功能翻车最后都翻在上下文处理上。模型本身没毛病是喂进去的上下文不对——要么太长爆了 token 上限要么太短丢了关键信息要么顺序乱了导致模型前言不搭后语。context-mode就是为解决这个问题而设计的一套上下文管理策略它不是一个开箱即用的工具而是一套在什么场景下、用什么方式组织历史对话信息的决策机制。这篇文章会把我在实际项目中落地的 context-mode 完整拆开核心设计思路、几种模式的适用场景、关键参数怎么定、常见坑有哪些。适合正在做 AI 对话产品、有 agent 开发经验或刚接触 LLM 应用开发的工程师参考。我能保证的是这些内容不是文档里抄来的概念而是踩过坑之后沉淀下来的实操经验。1. 内容整体设计与思路拆解1.1 context-mode 到底解决什么问题先举个贴近生活的例子。你跟一个人连续聊了三个小时对方每隔几分钟问一句刚刚那个事情你怎么看。如果你已经完全忘了前两个小时聊了什么你给出的回答大概率会前后矛盾。人类是这样LLM 更是这样——它的记忆完全依赖于每次请求时喂进去的上下文。我做的一个 AI 客服机器人项目早期版本特别天真把所有历史消息一股脑塞进 prompt。头一两天测试没问题一旦对话超过 20 轮prompt 直接超过上下文窗口上限程序报错。就算有些模型支持 128K 上下文塞满之后响应延迟从 1 秒飙到 8 秒而且模型对中间信息的注意力明显下降——后来我才明白这叫做lost in the middle现象模型对长上下文中间的细节记忆很差。context-mode 的设计目标就是解决两件事控制 token 消耗。对话无限增长但上下文窗口是有限的必须有一套裁剪策略。保留关键信息。不是简单粗暴地截断而是理解哪些信息对当前问题最重要把重要内容留下来。1.2 几种核心模式的设计选型我在项目里最终沉淀出四种 context-mode对应不同的对话场景。这不是什么标准协议是我自己在实践中验证过的组合。全量模式Full Mode。适合短对话或需要高精度回忆的场景。逻辑最简单把全部历史消息按顺序拼进系统提示词。优点是不会漏信息缺点是 token 消耗大。我只在对话轮次较少低于 10 轮或用户明确要求回顾全部历史时才用。窗口模式Window Mode。只取最近 N 轮对话作为上下文。这是成本最低的方案也是大多数简单 Chatbot 的默认选择。N 的选择很讲究太小模型失忆太大成本高还容易注意力分散。我在实际项目中一般用 6~12 轮作为默认值。摘要模式Summary Mode。把早期对话用 LLM 本身提炼成摘要只保留用户关心什么、结论是什么、承诺过什么这类长期信息然后与最近的原始对话组合。这个模式解决的是对话很长但关键结论分散在不同时间段的问题。检索模式RAG Mode。适用于知识密集型场景。每轮对话进来先从历史消息和知识库中向量检索最相关的片段只把这些片段拼入上下文。这是效果上限最高的方案但工程复杂度也最高需要管理向量库和 embedding 流程。实际项目中这四种模式不是互斥的而是组合使用。我的生产架构是最近 8 轮走窗口模式更早的对话走摘要模式同时用检索模式从知识库补充事实性信息。整个 context-mode 就是一个调度器根据对话轮次、用户意图和 token 预算来决定当前请求使用哪种模式或模式的组合。1.3 为什么不能只用单一策略做个简单推演你就明白了。假设每轮对话平均消耗 400 token10 轮之后就是 4000 token。如果对话持续到 50 轮全量模式需要 20000 token按月调用量 10 万次计算仅 prompt 费用就可能破万。更糟的是即使你愿意付这个钱模型在超长上下文上的表现也会退化。但纯窗口模式同样有问题。用户在第 40 轮突然问我刚开始说的那个需求你记得吗而那个需求在第 3 轮出现过窗口模式已经完全把它丢掉了。模型只能瞎猜或者承认不记得用户体验非常差。这说明上下文管理本质上是一个遗忘与记住的平衡问题。好的 context-mode 必须在成本和效果之间做动态取舍。我的经验是要给每种模式设定明确的使用条件和切换阈值而不是拍脑袋随机切换。比如对话轮次小于 10 用全量10 到 30 轮用摘要最近窗口超过 30 轮则启用检索模式进一步压缩。2. 核心细节解析与实操要点2.1 窗口模式的轮次选择逻辑窗口模式最核心的参数就是 N保留的对话轮数。我一开始拍脑袋设了 20 轮结果效果很差原因是很多历史轮次对于当前问题已经完全没有信息量。后来我通过实验对比发现了一个规律对于大多数任务型对话相关性集中在最近 4~8 轮对于闲聊型对话相关性分布更均匀但总量也不超过 15 轮。具体如何确定 N我没有用复杂的数学公式而是做了个简单的梯度测试从 N4 开始逐步增加到 N16每档跑 50 条历史对话样本人工打分评估回答的完整性和准确性。记录不同 N 值下的 token 消耗与回答质量分数。结果很有意思从 N4 增加到 N8质量分数上升明显从 N8 到 N12分数微涨但 token 增加 50%超过 12 之后分数基本不再增长偶尔还下降因为引入了噪声信息。所以我把默认 N 定在了 8并根据业务复杂度设置 6 和 12 两个档位。还遇到过一个特殊情况——用户会在对话中粘贴大段日志或文档这种消息一轮就可能占掉 3000 token。如果按轮数来切很快就把上下文窗口塞满了。所以我改成了按 token 数切窗口优先保留最近 N 轮但同时累计计算 token 总数超过阈值比如 6000 token时强制截断早期消息。用轮数的保底用 token 总量兜底两者取交集。2.2 摘要模式的关键实现细节摘要模式的核心是什么时候触发摘和摘要里写什么。触发时机我建议用两个条件同时判断对话轮次超过阈值比如 15 轮且最近 5 轮中没有出现新的关键实体或主题变化。如果用户正在紧锣密鼓地讨论一个连续话题过早做摘要反而打断信息的连贯性如果话题已经结束、用户开始聊新内容那旧的对话就该立即被压缩。摘什么内容也是要动脑子的。一开始我用最朴素的 prompt请总结以上对话结果模型把天气问候和闲聊都摘进去了关键决策反而被稀释。后来我把摘要结构固定成四段式用户画像用户提到的身份、偏好、约束条件。核心需求用户明确提出的目标尤其是带数字、时间、范围的表述。达成的共识已确认的结论、方案、承诺。待办与疑问尚未解决的问题、需要用户确认的事项。写成模板大概是这样的请从以下对话中提取信息按四个维度组织摘要 1. 用户画像可推断的用户身份、偏好、习惯。 2. 核心需求用户的主要目标、关键要求保留数字和限定条件。 3. 已达成的共识双方确认过的结论、方案细节。 4. 待办与疑问未完成事项、需要进一步澄清的问题。 忽略寒暄和与主题无关的内容。每次生成新摘要后不会直接覆盖旧摘要而是把旧摘要和新增对话合并后再提炼。这个摘要的摘要过程看似多消耗了一次 LLM 调用但能防止信息越滚越臃肿。我一般限制摘要总长度在 500 token 以内超出就做二级摘要。2.3 检索模式的关键决策点检索模式需要回答三个问题用什么 embedding、检索哪些内容、怎么混排。embedding 选型。中小项目直接用现成的 embedding API 就行。我在云上部署用的是通用的 text-embedding 模型实测在中文语义相似度上表现不错。需要注意的一个坑是embedding 模型也有 token 上限通常是 512 或 2048超过了会截断。所以我在入库时强制切片每段控制在 300 字以内。检索内容范围。不是所有历史都适合向量化。我的做法是白名单机制只对包含具体实体订单号、产品名、用户 ID或决策性内容的消息做切片和向量化纯粹的闲聊直接排除。这一步能显著降低向量库的噪声。怎么与上下文组合。检索到的片段不能直接塞到 prompt 最前面那样模型会搞不清哪些是当前对话。我通常这样组织系统提示词 【相关历史片段】检索得到的标注来源时间 【最近 N 轮对话】 【用户当前问题】把检索片段放在最近对话之前模型能理解这些是较早的背景信息而后面的对话才是当前主线。一开始我把检索片段放在最后结果模型经常把它们当作对当前问题的直接回答输出质量很差。2.4 系统提示词对 context-mode 的配合很多人忽略了一点上下文模式切换之后系统提示词也需要跟着变。如果用窗口模式系统提示词里就要明确告诉模型你只能看到最近的信息如果用户问更早的内容请礼貌说明并引导用户重新描述如果用了摘要模式系统提示词要说明较早的信息经过压缩部分细节可能丢失。我踩过一个教训给模型开了摘要模式但系统提示词没说结果用户问我上周说的预算具体是多少模型看到摘要里有预算两个字就自信地编了一个数字。后来我在系统提示词里加了一句硬性规则当用户询问的信息不在当前上下文中时必须明确回答我没有找到相关信息禁止推测。 这一步把幻觉率降了差不多 60%。3. 实操过程与核心环节实现3.1 context-mode 调度器整体架构我建议把 context-mode 做成一个独立的调度模块不要跟业务逻辑耦合。架构分三层调度层接收用户消息计算当前对话的 token 数、轮数、主题变化决策使用什么模式。处理层执行摘要、窗口截取、向量检索、历史消息组装。组装层把系统提示词、历史片段、最近对话、当前问题按顺序拼接成最终 prompt。下面是我项目里调度器的核心代码骨架用 Python 写的伪代码性质但结构是完整的class ContextManager: def __init__(self, max_tokens6000, window_rounds8, summary_threshold15): self.max_tokens max_tokens self.window_rounds window_rounds self.summary_threshold summary_threshold self.summary_store {} # session_id - summary text def build_prompt(self, session_id, history, current_question): total_tokens estimate_tokens(history) rounds len(history) if rounds self.window_rounds and total_tokens self.max_tokens: # 全量或窗口模式合并处理 relevant_history history[-self.window_rounds * 2:] else: # 摘要模式 窗口模式 summary self.summary_store.get(session_id, ) recent history[-self.window_rounds * 2:] relevant_history self._merge_summary_and_recent(summary, recent) # 如果还是超长触发检索模式 if estimate_tokens(relevant_history) self.max_tokens: relevant_history self._rag_retrieve(history, current_question) prompt self._assemble_prompt(relevant_history, current_question) return promptestimate_tokens这个函数单独说一下。千万不要逐字调用模型的 tokenizer API那样太慢而且付费。我用的近似公式token数 ≈ 中文字符数 x 0.7 英文字符数 x 0.3。对 GBK 编码的中文文本来说这个估算误差在 15% 以内足够用来判断是否超限。生产环境里如果对准确性有要求可以对常用消息做一次真实 tokenizer 估算后缓存结果不用每次现算。3.2 摘要模式的运行流程摘要处理的完整流程我拆成四个步骤每一步都有明确产出判定时机当对话轮次超过 15 且距离上次摘要超过 10 轮时触发摘要任务。生成增量摘要把旧摘要 新增的 10 轮对话合并成一个文本块调用 LLM 按四段式模板生成新摘要。质量校验检查新摘要中是否保留了旧摘要里的关键数字和实体。我会写一个简单的校验脚本用正则把旧摘要里的数字提取出来跟新摘要比对如果关键数字丢失就重试一次。存储迁移把新摘要写入存储同时把已摘要的对话从待处理队列中清除。第 3 步是我觉得最值得借鉴的经验。LLM 在摘要时天然会丢失精确数字比如价格、日期、数量。用程序化校验去兜底比完全依赖模型自觉可靠得多。摘要存储我用的就是普通的数据库字段没有引入额外组件。每条会话记录加一个summary字段每次摘要更新就是一次普通的 UPDATE。只有当检索模式需要全局搜索时才用到向量库那是另一套逻辑。3.3 检索模式落地配置清单如果你决定上检索模式我建议按下面的顺序操作不要跳步数据清洗历史消息中过滤掉纯表情包、链接分享、系统通知。这些对理解用户意图没帮助。切片入库每段控制在 200~300 字段与段之间保留 10% 重叠避免语义断裂。embedding 批量生成建议离线批量跑不要线上实时生成耗时太长。存储选型数据量在百万条以下用单机向量库就够超过百万条再考虑分布式方案。检索阈值召回 TOP 3~5 条设定相似度阈值 0.75低于此分数直接丢弃。这里说下为什么相似度阈值要设成一个合理的值。我把阈值设得太低0.5时检索出来的片段很多是字面相关但语义无关的比如用户问怎么退款结果检出一条退款政策已更新的通知模型参考完之后也跑偏了。调到 0.75 之后虽然召回变少了但每条都是有价值的反而提升最终回答准确率。3.4 组装 prompt 的细节规范组装 prompt 的阶段看似简单实际坑很多。我要求团队遵循几条硬规范顺序固定系统提示词在最前然后是相关历史片段、最近对话、当前问题。顺序一旦定了就不要改因为模型对位置的敏感性很高。分隔符要醒目我在不同区块之间用###和分隔线划分并且告诉模型这些分隔符的含义。比如 【以下为历史信息】、【以下为当前对话】。这样模型能清晰地知道每一段的角色。结尾不要放多余的引导语。很多人习惯在 prompt 最后加一句请根据以上对话回答其实没有必要。用户的当前问题本身就是最强的引导加多了反而干扰。还有一个细节如果当前问题碰巧很长比如用户贴了一大段文字我建议把问题放在历史信息之前。一是避免长问题把历史信息挤出注意力窗口二是模型读问题时已经忘了前面的历史的话反而会更聚焦在问题上。这个 switch 不是所有场景都适用但当你发现模型开始看着历史说话忽略当前问题的时候值得一试。4. 常见问题与排查技巧实录4.1 模型失忆了怎么办这是 context-mode 上线后最常见的用户投诉明明之前聊过的东西模型好像完全不知道。排查顺序很重要先把出问题的会话完整拉出来看实际传给模型的 prompt 是什么一定要打日志。80% 的情况不是模型出了问题而是 context-mode 把关键信息裁掉了。我当时就靠这个定位了一个 bug窗口模式里我用的是最近 8 轮但用户问的那个需求发生在第 9 轮——差一轮就没到窗口里信息被裁了。把窗口从 8 调到 10 就解决了。如果 prompt 里有信息但模型没用好检查系统提示词是不是让模型过于关注最新消息。模型有一定的近因偏好越新的信息权重越高。我给系统提示词加了一条注意历史信息中同样包含关键内容请综合所有信息回答情况明显改善。如果以上都排除了才考虑模型能力本身的问题。换一个更强的基础模型测试对比输出差异。我建议在生产环境中每一轮请求的 prompt 内容都要落日志。这个日志文件看起来占存储但在排查问题的时候就是救命的稻草。没有它你只能靠猜测定位问题那效率太低了。4.2 摘要质量越滚越差的问题摘要模式跑久了之后会出现摘要的摘要信息逐级丢失的问题。我第一次发现是在一个超长客服会话里第 5 次摘要时原本的用户所在城市为成都需要发票变成了用户需要发票城市信息消失了。排查过程很有意思我检查中间每一代的摘要发现丢失不是集中发生的而是每代丢一点。旧摘要里的实体被新摘要在合并时优化掉了。解决方案是在每个新摘要生成后做一次实体完整性校验。我把校验逻辑展开一下从上一代摘要中提取所有实体关键词包括地名、金额、日期、产品名、数字。检查新一代摘要中是否包含这些实体。如果发现丢失就把丢失的实体列表附加到生成 prompt 中让 LLM 强制保留新摘要必须包含以下实体[成都, 发票]。如果补了一次仍然丢失就走降级策略——这部分信息直接用原文片段拼接在摘要后面不再依赖 LLM 二次提炼。这套机制上线后实体保留率从 70% 提升到了 95% 以上代价是约 3% 的额外 token 消耗值得。4.3 token 估算偏差导致的爆窗我用字符数 x 0.7的估算公式在纯中文场景下还好但遇到中英混排的场景偏差就大了。有一次用户贴了一个英文报错日志实际 token 数是估算值的 1.8 倍结果 prompt 超限模型直接报错。后来我把估算公式改成分段计算def estimate_tokens(text): chinese_chars sum(1 for c in text if \u4e00 c \u9fff) other_chars len(text) - chinese_chars return int(chinese_chars * 0.8 other_chars * 0.35)英文和技术文本每个字符约 0.3~0.4 个 token中文每个字约 0.7~0.8 个 token在大多数开源 tokenizer 下。修正之后估算偏差控制在 10% 以内。还要注意一个边界条件不要让 prompt 的实际 token 数恰好卡在上限附近。我在调度器里设置了85% 安全阈值估算 token 达到上限的 85% 时就触发压缩逻辑而不是等到 100%。这样即便估算有误差也留了缓冲。4.4 context-mode 的测试方案没有测试就上线的 context-mode 一定会出事故。我的测试方案分三层单轮单元测试每轮对话单独验证 prompt 组装是否正确。我用快照测试把组装结果存成 JSON 快照任何代码变更后跑一遍 diff防止无意的行为改变。多轮场景测试先用脚本自动生成一段包含 50 轮对话的会话记录内容覆盖闲聊、任务、知识咨询等类型。然后分别在四种 context-mode 下运行完整流程人工评估每一轮的回答质量和上下文连贯性。回归测试集把历史线上出过问题的会话全部收集起来做成回归样本。每次改造完之后跑一遍确保修复过的 bug 不会复发。我强烈建议把用户的真实对话脱敏后加入测试集。因为你自己构造的测试对话永远不具备真实对话的杂乱性和跳跃性只有真实数据才能暴露 context-mode 的短板。4.5 多会话隔离的陷阱如果你的产品同时服务大量用户每个用户有独立的会话上下文要注意不能让不同会话的摘要和检索结果互相污染。我最初在检索模式里犯过一个低级错误向量库查询时没有加session_id过滤条件结果 A 用户的客服会话里检索出了 B 用户的订单信息——这不仅是技术 bug还是隐私事故。检索隔离的强制原则向量库里每条数据都必须带session_id或user_id字段。所有检索查询必须带过滤条件代码里要强制校验而不是靠开发人员自觉。摘要存储的表读取时必须走带session_id的索引查询。我自己写过一个自动化的数据泄漏检测脚本随机抽 100 个会话检查返回的检索结果中是否存在非当前会话的片段有就触发告警。直到现在这个脚本还在 CI 流程里跑着一分钟能检查完一批数据成本极低但收益很大。5. 扩展思考与实践建议5.1 context-mode 与模型能力的关系你可能会问如果模型本身的上下文窗口足够大比如 200K是不是就不需要这么复杂的 context-mode 了我的回答是不是。窗口大只代表放得下不代表效果好。长上下文的注意力分散问题目前依然是模型架构的固有限制。Lost in the middle在 Claude 和 GPT 上同样存在只是程度差异。你让模型读 10 万 token 的对话历史它能记住开头和结尾中间部分基本等于没用。所以 context-mode 的核心目标不是节省 token这么简单而是让模型只看到它该看的信息。哪怕未来模型窗口扩大到 1M这个原则依然有效——因为真正决定回答质量的是信息的信噪比而不是信息的总量。5.2 扩展到 Agent 和工具调用场景如果你做的不是单纯聊天机器人而是带工具调用的 Agentcontext-mode 需要加一层工具调用记录的处理。工具调用的输入输出往往包含大量结构化数据API 返回的 JSON这些内容不能跟普通对话一样做摘要否则 Agent 会丢失关键状态。我的做法是把工具调用的结果分成两路精简化记录把完整 JSON 存一份到外部存储只在上下文里放一个精简摘要例如调用订单查询 API返回 5 个订单。按需回溯如果后续对话中 Agent 需要完整订单数据通过检索模式把完整结果取回来。这套设计避免了工具调用结果把上下文撑爆的问题同时保证了 Agent 在需要精确数据时能拿回原文。在多个 Agent 应用的实践中这个折中方案比全量塞入或直接丢弃都更稳。5.3 监控指标和踩坑清单最后给出一份我在生产环境中持续监控的核心指标你可以当成 checklist 用监控指标预警阈值说明平均每请求 prompt token超过 5000如果持续走高说明压缩策略可能失效摘要实体保留率低于 90%通过程序比对两代摘要的关键实体得出检索命中率低于 60%命中率低说明检索质量差或阈值设置不合理上下文切割触发率超过 40%说明默认窗口参数设置过小用户重问率超过 15%用户反复追问同一件事说明 model 失忆监控数据不需要做得很复杂记录原始日志、定时跑统计脚本、异常告警就够了。我见过不少团队在上下文管理上投入大量精力但完全没有监控导致问题雪崩了才发现。这是我把个人经验浓缩成一句话的建议先加日志再加监控最后才加策略。顺序错了后续所有优化都是盲人摸象。回想最开始的 AI 客服项目我用了整整三个月才把 context-mode 调到满意的状态。中间试错过无数回最深刻的教训是不要一上来就追求花哨的检索模式和复杂算法先把全量、窗口、摘要这三种模式跑通用真实的用户对话去暴露问题然后再逐步升级。context-mode 的本质不是一套花哨的架构而是对模型该怎么看世界的一种克制而务实的约束。它值得你花时间打磨因为它直接决定了你的 AI 应用是聪明还是失忆。