Context-Mode详解:如何让大模型在多轮对话中不失忆
发布时间:2026/10/29 8:43:19 锦皓数字建站

前阵子帮一个做智能客服的朋友调模型他特别纳闷同一个大模型接口为什么别人的机器人能记得用户二十分钟前说过“我要改签七月一号的航班”自己的机器人却像金鱼一样转头就忘我让他把 context 相关代码发过来问题果然出在 context-mode。直译过来就是“上下文模式”听着像个小开关实际上它直接决定了大模型回答的上限。这几年跟大模型打交道的人越来越多你会发现凡是做 AI 应用、做智能体、做多轮对话产品的团队迟早都会被这个词卡一下。模型本身其实没有记忆它每次回答都像第一天上班的临时工手里只有你递给它的材料。你递过去的历史对话越全它记得越多可窗口就那么大递太多会被截断递太少又容易失忆。context-mode 本质上就是“递材料的方式”。选对了回答又稳又省选错了用户一边骂一边流失。今天我就结合自己做客服机器人、知识库助手、多轮对话工具时踩过的坑把这件事讲透。1. 先搞清楚context-mode 到底在管理什么1.1 大模型不是“记性好”而是“材料好”很多刚接触 AI 应用的人会把大模型想象成人脑以为它天然记得上一轮聊了什么。实际上你每次调用模型接口传进去的只是一段文本模型只根据这段文本生成回复请求结束后它什么都不留下。这一点是理解 context-mode 的起点。打个比方你请了一位专家到会议室帮忙这位专家非常专业但有个怪癖——他只保留当下几秒钟的记忆。你每问一个问题都要把前因后果写在纸条上递给他。如果纸条写得全他能给你高质量建议如果纸条只写了半句话他就只能瞎猜。这就是 context-mode 的处境它决定了你往纸条上写什么、写多少、按什么顺序写。在日常产品里我们说的“上下文”其实是一个组装出来的东西。系统指令、用户历史提问、AI 历史回答、外部检索到的资料、用户画像标签都要塞进同一个 prompt 里。context-mode 就是管理这套组装过程的状态机。它要回答三个问题哪些信息必须带哪些信息可以丢信息太长时怎么压缩1.2 context-mode 的三块核心拼图第一个是窗口Window。每个模型都有上下文窗口上限通常用 token 数量表示。窗口越大能塞进去的材料越多但成本也越高响应时间也越慢。这里说的 token 可以理解成模型读词的“最小单位”英文一个词大概一到两个 token中文一个汉字大约一到一个多 token具体因模型而异。第二个是消息队列Message Queue。历史对话是一条条消息组成的队列策略决定哪些消息保留、哪些消息删除、最多保留多少轮。常见的做法是只保留最近 N 轮对话但这有一个隐藏风险早期出现的用户需求可能被挤掉。第三个是压缩策略Compression Strategy。当历史对话太长、窗口快满的时候不能简单地一刀切全丢掉而是要用摘要或者关键信息抽取来压缩早期内容把“原来的十轮对话”变成“三句话摘要”既节省空间又保留核心信息。这三个部分不是独立的而是层层嵌套的关系。窗口是总量上限队列决定取舍压缩负责提高信息密度。真正成熟的 context-mode 方案都是在这三者之间做权衡。很多项目翻车不是因为模型不强而是这三块里至少有一块没设计好。2. 三种主流上下文模式选之前先想清楚代价2.1 全量带入模式最省事也最容易爆最朴素的做法是把历史上所有对话一股脑全塞进 prompt一次请求里既有第一轮的用户消息也有每一轮 AI 回复直到塞满窗口为止。这种模式的好处是实现简单不需要额外逻辑模型能看到最完整的信息坏处也显而易见费钱、费时间、容易爆窗。我见过一个小型知识问答应用上线之后每过一段时间就出现“请求失败”。查下来发现是用户对话太长全量带入后 token 数超过了模型限制后端直接把请求拒了。这种模式适合对话轮次很少、单次输入比较短的内部分析工具不适合面向 C 端的聊天产品。全量模式还有一个隐蔽问题信息过载。模型确实能看到所有历史但注意力会被无关内容分散。用户前面聊了十分钟天气后面突然问订单状态模型反而容易忽略真正关键的订单号。历史越长信噪比越低回答质量并不一定比精简后更好。2.2 滑动窗口模式只保留最近 N 轮滑动窗口是应用最广的模式。它的逻辑很简单只保留最近 N 轮对话比如最近 6 轮或 8 轮更早的直接丢弃。这样做的好处是 prompt 长度可控、成本稳定、不会爆窗实现成本也很低。但滑动窗口有个经典问题关键信息不一定都在最近几轮里。我调过的购票机器人就遇到过这种事故用户在第一轮说了“七月一号从上海到北京”聊到第十轮时问“帮我看看还有没有靠窗位置”模型居然回复“请问您要从哪里飞到哪里”。因为最早的出发日期早已被滑出窗口模型彻底丢了最重要的槽位。所以滑动窗口不能只按“轮数”粗暴切割至少要把结构化信息单独拎出来放到固定区域。比如把用户已经确认的出发地、目的地、日期、票型维护成一个 memory slot记忆槽位每轮都放在 prompt 顶部再配合最近几轮对话当补充。这样既享受滑动窗口的低成本又避免关键上下文丢失。2.3 摘要压缩模式用信息密度换空间摘要模式是对滑动窗口的一种升级。它的核心思路是当历史对话太长时先把早期内容交给模型压缩成摘要再把摘要放到 prompt 里保留最近的原始对话。这样模型既能“记得”很久以前的事又不会占用太多 token。实际操作中摘要可以是单层也可以是层级。单层就是一次性把前 20 轮压成 3 条要点层级则是每隔一定轮次做一次摘要再把旧的摘要继续压成“摘要的摘要”。层级结构适合超长会话但实现复杂度成倍增加而且摘要层数多了之后细节丢失风险也很高。我自己推荐第一版项目先做单层摘要等跑通之后再考虑多级。摘要不是冗长复述而是要提炼“用户明确的需求”“已经确认的偏好”“还未解决的问题”“所有不能丢的数字和编号”这四类信息是摘要模板里必须出现的部分我在第三章会给出具体模板。2.4 三种模式怎么选一张表说清模式保留信息成本实现复杂度适合场景全量带入最完整最高随对话增长失控低短对话、内部工具滑动窗口只保留最近轮次稳定可控低通用客服、意图清晰场景摘要压缩早期浓缩最近原文中等需额外模型调用中长会话、需要长期记忆的产品并没有绝对的最好只有适合。大多数产品最终都会走混合方案系统指令固定放最前面结构化记忆槽位放第二位摘要放第三位最近原始对话放最后。这就是我在后面案例里采用的 context-mode 结构。3. 把 context-mode 落地的三个关键参数3.1 Token 预算不是拍脑袋要算明白很多人设计 prompt 时只看“有没有超过模型上限”这是不够的。上下文模式的第一步是给每个区域划预算预算总量 模型窗口上限 - 系统指令 - 用户本次输入 - 预留输出。举个例子模型窗口是 8000 token系统指令写了 500用户这次输入大约 300模型回答需要预留 1000。那么可用于历史消息和摘要的区域就是 8000 - 500 - 300 - 1000 6200 token。如果历史消息超过 6200就必须裁剪或压缩。这里有一个新手容易忽略的坑输出预留不能给太少。模型生成时输入和输出是共享同一个窗口的如果输入塞得太满模型只剩几百 token 输出长回答会被硬生生截断用户看到的就是“话没说完”。我一般会把输出预留放到总窗口的 15% 到 25%宁可少放点历史也别把模型的嘴堵住。预算定好之后建议把数值做成配置项而不是散落在代码里的魔法数字。不同模型窗口不一样同一模型不同版本也可能调整集中配置能让你在切换模型时只改一处。3.2 裁剪策略从后往前遍历而不是从前往后确定预算后如何从消息列表里挑出要保留的内容最稳妥的做法是从最新一条消息开始倒着往前遍历把能装进预算的消息留下一旦超出预算就停止更早的一律丢进“待压缩区”。def build_context(system_prompt, messages, max_history_tokens6200): context [{role: system, content: system_prompt}] remain max_history_tokens latest [] for msg in reversed(messages): cost estimate_tokens(msg[content]) if cost remain: break latest.insert(0, msg) remain - cost return context latest这段代码里有几个细节值得说。第一reversed(messages)保证我们优先保留离现在最近的对话因为最近的内容通常相关性最高。第二estimate_tokens不能用 Python 的len(str)中文按字符数算会和真实 token 数差很多。应该用模型对应的 tokenizer 或者离线工具估算宁可多留一点余量。第三latest.insert(0, msg)是为了保持消息的原始顺序。如果直接用 append 再接reversed的遍历结果消息顺序会反过来模型会看到“先回答后提问”的荒谬顺序回答质量直接崩掉。3.3 摘要要提前做而不是等爆窗再做很多人的第一版方案是等 token 快满时才调一次摘要结果摘要调用本身也要占 token容易诱发二次截断。我的经验是给一个阈值比如当历史消息占预算的 70% 时就提前触发一次压缩把早期消息压成摘要把最近 6 轮保留原文。def summarize_and_trim(messages, budget, summarize_fn): if estimate_tokens(messages) budget * 0.7: return messages keep messages[-6:] # 最近 6 轮保留原文 to_summarize messages[:-6] # 更早的送去压缩 summary_text summarize_fn(to_summarize) return [{role: system, content: 历史摘要 summary_text}] keep摘要调用时我用的提示词模板大致长这样请把以下对话压缩成最多5条要点必须保留四类信息 1. 用户明确提出的需求 2. 已经确认的偏好或禁忌 3. 尚未解决或待确认的问题 4. 所有数字、日期、订单号、人名等不可编造的信息 请用陈述句不要出现“用户说”“用户表示”之类的转述词。为什么不要“用户说”因为转述词会占用 token而且模型看到摘要后容易陷入第三人称叙事回答风格变得很怪。直接写“出发日期2025-07-01需要靠窗座位”反而更干净也更容易被后续轮次引用。3.4 别让不同用户的上下文串台context-mode 不只要管“内容”还要管“归属”。在多用户、多会话场景下必须用 session_id 或者 conversation_id 做隔离。我接手过一个线上事故用户 A 问了自己的订单结果用户 B 的请求里居然带着 A 的历史记录幸好测试时发现得早否则就是严重的隐私泄露。实现上所有上下文状态都要挂在会话维度存储不能放在全局变量里。Redis 里用session_id作为 key每次请求先取该会话的上下文处理完再写回去。如果是多实例部署还要注意并发锁和过期时间避免同一会话两个请求同时读写导致消息丢失。4. 实操案例客服机器人如何靠 context-mode 把答非所问率降下来4.1 项目背景和问题定位这个项目是一个出行票务客服机器人用户会在对话里完成查票、锁票、改签、退票等操作。一开始的需求方只给了模型最高权限所有历史消息全量带入结果上线之后差评率飙升。我翻了几十条日志归纳出三类典型问题第一用户在第 5 轮之后模型开始忘记出发日期反复反问“请问您的出发地是”体验非常割裂。第二一些用户在前几轮已经确认了“退票”后续又问“改签手续费多少”模型居然还认真回答完全没意识到这两个操作是冲突的。第三长对话的响应越来越慢token 费用也在肉眼可见地涨。第一类问题就是滑动窗口没做好的典型表现第二类问题说明模型缺少对“决策状态”的记忆第三类则是全量带入的成本问题。想清楚之后我决定给这个机器人重新设计一套混合 context-mode。4.2 重新设计上下文结构我把它分成了四个固定区域{ session_id: 1627829345, system_prompt: 你是票务客服。当前日期是2025-07-01。禁止擅自改价所有涉及退款必须让用户确认。, memory_slots: { 出发地: 上海, 目的地: 北京, 出发日期: 2025-07-01, 票型: 成人票, 状态: 已锁票未支付 }, history: [], summary: }system_prompt 放业务规则永远不参与裁剪memory_slots 放用户已经确认的关键槽位每轮更新history 放最近 6 轮原始对话summary 放更早内容的压缩摘要。这样组装出来的 prompt 顺序是system_prompt → 当前记忆槽位 → 历史摘要 → 最近对话。memory_slots 的更新是关键。每次用户消息进来先让模型做一次轻量抽取判断这条消息里有没有新的槽位信息如果有就写入 memory_slots。这个过程我单独用一个小模型调用输出 JSON不给用户直接看到。比如用户说“改成后天晚上走”抽取结果就是“出发日期2025-07-03”然后覆盖原来的日期。因为有了这张“小抄”即使最早的对话被滑出窗口关键槽位也不会丢。4.3 落地流程与关键代码整个流程可以简化成三步第一步读取 session_id 对应的上下文第二步用当前用户输入更新 memory_slots第三步按预算拼装 prompt 并调用模型。更新槽位时我用的不是直接拼接而是让模型结构化输出。代码大致是def extract_slots(user_input, current_slots): prompt f 根据用户输入更新出行槽位只输出 JSON不要解释。 当前槽位{json.dumps(current_slots, ensure_asciiFalse)} 用户输入{user_input} 可更新字段出发地、目的地、出发日期、票型、状态 response call_llm(prompt, max_tokens200) return json.loads(response)这里要注意模型输出的 JSON 不一定每次都合法所以解析失败时要保留原槽位并用正则把肉眼可见的日期抽出来兜底。我在生产环境还加了一层“人工规则”像“改签”“退票”这类动作词出现时直接把状态槽位强制更新不等模型判断。拼装 prompt 这一步我沿用了前面说的预算函数。系统指令固定 300 token槽位区动态变化但不会超过 200摘要区预留 1500最近 6 轮预留 3000输出预留 1500整体控制在 8000 窗口之内。因为槽位是关键我宁可压缩摘要也不会把 slot 区省掉。4.4 实测结果和调优过程这个方案上线跑了三周效果很明显。此前答非所问率大约在 12% 左右上线后降到 4% 以下与“出行日期”“改签”“退票”相关的反问次数减少了约 70%。token 成本也有下降因为全量带入变成混合模式后单次请求的输入 token 平均降了接近四成。不过中间也出过两个小插曲。第一个是采购摘要模型时选了个便宜的模型结果摘要质量差经常把“不想要靠窗座位”记成“想要靠窗座位”导致用户投诉。后来我把摘要模型切换成较大的模型虽然单次摘要调用贵一点但总体费用反而更低因为后续回答错误率下降用户重试次数少了。第二个插曲是状态槽位更新过于激进。用户说“我不去了”模型直接把出发地更新成空值后来原行程信息全被清掉。这个问题的解法是给槽位加“置信度”只有明确出现“改成”“预订”“出发是”这类意图词时才覆盖否定表达只标记状态不清空历史槽位。如果你也要做类似记忆功能这一点一定要提前想清楚。5. 常见问题排查与避坑记录5.1 模型总是忘记系统指令如果你发现模型聊着聊着不遵守规则先检查系统指令有没有被裁剪。很多实现会把 system_prompt 和其他消息一起放进滑动窗口结果最早的指令被挤掉。修复方式很简单系统指令必须放最前面并且永远不参与预算计算。另一个容易忽略的问题是系统指令过长。有些团队把产品规范、FAQ、安全说明全堆进系统指令导致指令本身超过两三千 token。模型对长指令末尾部分的注意力会下降关键规则反而被忽略。我建议把系统指令控制在 500 token 以内只留最高优先级的规则其余内容放进内部知识库按需检索。5.2 摘要导致关键信息失真摘要模式最容易踩的坑是“丢数字”。模型在做自然语言摘要时经常会把日期、单号等具体数值改写成“最近”“下午”“那个订单”之类的模糊词。我的解法是在摘要模板里明确写“所有数字必须原样保留”并且单独在槽位区再存一份结构化数值两边互为备份。如果摘要已经失真还能通过日志回溯。建议每次生成摘要都存一份原文的 hash方便排查时定位是哪一轮摘要弄丢了信息。这个排查成本很低但能救命。5.3 token 统计不准确很多新手用len(text)算 token中文环境下误差可以达到一倍以上。不同模型有不同 tokenizer线上调用前要先用目标模型的 tokenizer 离线估算最好做到 5% 以内的误差。我自己的习惯是每次请求结束后把实际消耗 token 数写回日志和预估数对比如果差异超过 10%就调整估算系数。5.4 多轮会话并发串台排查别人代码时我见过不少“上下文张冠李戴”的问题。最典型的表现是用户 A 的消息里出现用户 B 的历史对话。原因基本都是没有做会话隔离用了全局的上下文变量。解决思路很明确状态全部放 Redis 或数据库key 为 session_id每次更新上下文时检查版本号防止并发覆盖。5.5 问题速查表现象可能原因解决方法模型遗忘早期关键信息滑动窗口切掉了早期内容增加记忆槽位区单独保存关键字段系统指令执行不稳定指令被裁剪或指令过长系统指令固定置顶且不参与裁剪回答被截断输入 token 占用过多输出预留不足给输出预留 15% 以上窗口摘要出现错误日期/订单号摘要模型能力不足或模板未要求保留数值换更强模型模板硬性要求保留数字多用户上下文串线缺少 session_id 隔离所有上下文按会话 ID 存储并加锁Token 超限频繁报错未做预算控制和动态裁剪按窗口上限分区预算并做降级策略6. context-mode 在产品层的延伸玩法6.1 让用户看得见上下文技术上的 context-mode 解决的是“模型有没有记忆”但产品层面还要解决“用户相不相信它有记忆”。我见过很多 AI 产品模型明明记得很清楚用户却因为看不到状态而反复重复需求。比较好的做法是在对话界面展示“当前记忆”面板把已经确认的出行日期、商品规格、用户偏好列出来同时提供“编辑”和“清除记忆”的入口。这不仅是体验问题也是信任问题。用户一旦发现 AI 能准确说出他之前提到的订单号信任感会明显提升如果 AI 显示了一个错误记忆他也可以手动修正而不是被迫重新说一遍。6.2 从技术方案到产品认知context-mode 本质上是一种“记忆设计”。它和我们人类的记忆很相似短期记忆要鲜活长期记忆要凝练而“忘记什么”往往比“记住什么”更重要。做产品时完全可以把这套逻辑迁移到交互规则里明确告知用户“当前会话会记住什么”。提供“新对话”按钮本质上就是清空短期记忆。在关键节点展示摘要让用户确认“我理解得对吗”。允许用户对记忆做增删改而不是让模型单方面决定。这些设计都不是炫技而是在用 context-mode 的思路管理用户预期。一个能把记忆做好的 AI 产品往往比单纯堆模型参数的竞品更容易留住人。6.3 一点个人建议如果你正在做一个大模型应用我建议从最简单的滑动窗口开始先把预算边界算清楚把 session_id 隔离做好再逐步加记忆槽位和摘要。不要一上来就追求“记忆永不丢失”那是违背成本和技术规律的。先让用户感受到“这个 AI 记得我刚刚说过的话”然后把“关键信息不丢”作为底线最后再谈长期记忆。我个人觉得调 context-mode 最有成就感的时刻不是看指标曲线多漂亮而是看到真实用户说了一句“它居然还记得”。别小看这句话背后全是 budget、窗口、裁剪、摘要和状态管理的细节。希望这篇能帮你少走点弯路。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。