
做 AI 应用开发这几年我被问得最多的一个词就是context-mode。大模型本身不笨但它总在关键时候“失忆”——前一轮还聊得好好的下一轮就把你之前交代的条件抛到脑后。这其实就是上下文模式没设计好。今天这篇文章我想把 context-mode 从原理到落地完整拆一遍包括上下文窗口怎么配、消息结构怎么组织、长会话怎么管理、踩过哪些坑。内容不挑框架适合正在做智能助手、Agent、客服机器人和 RAG 应用的工程师参考。1. context-mode 到底是什么先搞清楚三个不同场景我最早接触“context-mode”这个词发现它在不同社区里含义不太一样。如果你不做功课直接搜资料很容易被绕晕。先把这三个常见场景理清楚后面实操才不会跑偏。1.1 从“记性不好”说起context 的本质无论哪个 context-mode本质解决的都是同一个问题让模型在生成内容时能看到它“该看到”的背景信息。大模型本身没有记忆。它就像一个读过很多书但从不做笔记的临时工你问它什么它只能根据当下这瞬间收到的信息来回答。所谓上下文就是你在这次请求里给模型塞进去的全部文本——包括用户问题、历史对话、系统设定、检索到的资料等。context-mode 就是一套管理这些上下文信息的工作模式。它管三件事哪些信息该放进上下文以什么顺序和格式放进去上下文太长放不下时如何取舍。这三件事处理得好不好直接决定你的应用是“聪明助手”还是“人工智障”。我见过太多项目模型选得挺强提示词写得也还行最后就败在上下文管理这层用户一问复杂问题就崩。1.2 context-mode 在三种典型场景中的三种形态第一类LLM 应用框架里的上下文模式。比如你在 LangChain、LlamaIndex 或自研 Agent 框架中看到的 context-mode一般指对话记忆组件的开关与策略是简单拼接历史消息还是做摘要压缩还是走向量召回。这类模式解决的是多轮对话“记住前面聊什么”的问题。第二类AI 编程工具里的上下文模式。比如 Cursor、Windsurf 等编辑器里的 context是告诉模型“你现在要基于哪些文件、哪些规则来写代码”。你选中的代码片段、当前文件、项目规范都会被塞进上下文中。这类模式解决的是“模型知道它正在改什么代码”的问题。第三类基础设施层面的上下文服务。比如 API 网关、模型代理层里的 context pipeline负责在请求到达模型之前动态注入、改写、压缩、路由上下文。这类模式解决的是工程化问题——让不同上游服务给模型喂信息时有统一的规范和治理手段。你自己做应用时通常不会是单一形态很可能是三种形态的组合。比如你做了一个客服机器人底层 LLM 框架里开了上下文记忆模式中间可能接了一个 RAG 管道来做知识检索再往上层可能还有一个统一网关在管理最终拼给模型的 prompt。这一整条链路都算 context-mode 的范畴。2. 核心原理上下文窗口、Token 和消息结构想用好 context-mode先得把底层几个概念搞明白。这几个概念不深但很多人是在踩坑之后才回头补课的。2.1 上下文窗口不是越大越好每个模型都有一个上下文窗口context window单位通常是 token。比如 32K、128K、200K。窗口意味着模型单次请求能“看到”的文本上限。很多人有个误区以为窗口越大越好最好把所有东西全塞进去。实际上窗口越大有三个代价成本更高。按 token 计费的 API上下文每多一截费用就多一截。哪怕用户只回了一个“好”你每次请求也得为前面那几千 token 的历史消息买单。延迟更高。模型处理更长的输入首字返回时间明显变长。用户不会等太久体验从“秒回”变成“读条”。注意力被稀释。长上下文中关键信息反而容易被淹没。尤其当你的历史对话里有多轮寒暄、废话、歧义内容时模型抓重点的能力会显著下降。所以 context-mode 的第一原则是不要满窗运行。留出余量给模型“呼吸”的空间。我一般建议如果模型窗口是 32K日常请求实际塞进去的内容控制在 24K 以内剩余部分留给模型生成回复同时也避免超限报错。2.2 消息结构system、user、assistant 谁是老大在大多数模型 API 里上下文通过消息数组传递每个消息带一个 role。常见的三个角色是 system、user、assistant。context-mode 的很多问题根源出在对三个角色的使用不规范上。system 消息是给模型设定身份和规则的。它的优先级通常最高但它也不是圣旨。模型遵循 system 的力度跟 system 内容的写法、长度、位置都有关系。实践中我发现system 消息控制在 1000 token 以内、用明确短句写规则遵循度最高。写得像论文一样长反而后面几段规则经常被模型忽略。user 消息是用户输入。这里有一个容易踩的坑很多人把检索到的背景资料、系统自动生成的内容也塞进 user 消息里。这会导致模型分不清“这是用户说的”还是“这是系统给的资料”在多轮对话里就可能产生混乱——模型可能把上下文资料当成用户的指令去执行。assistant 消息是模型之前的回复。多轮对话中历史 assistant 消息必须原样回传不能改。你一旦中途改写或润色历史回复模型的续写能力会下降因为它在延续一个“从未真正说过”的回复风格。更合理的做法是用 system 消息固定身份和规则用 user/assistant 消息保留真实对话历史用额外的工具/检索结果消息有的 API 有专门角色没有的话可以统一放在 user 消息里加前缀标记来注入参考资料。角色职责清晰context-mode 才不会乱。2.3 Token 清账怎么看上下文到底装了多少你往上下文里塞了东西但看不见摸不着怎么知道装了多少这里需要两个工具tokenizer 和日志。tokenizer 是把文本切成 token 的工具。不同模型分词方式不同同一个中文句子在不同模型里 token 数可能差两三倍。所以计算上下文长度时别用“字数”估算直接用目标模型自己的 tokenizer 数。日志则是记录每次请求实际 token 消耗的地方。我看项目质量先看有没有记录 prompt_tokens、completion_tokens、total_tokens 这三个字段。没记录这三个字段的项目基本谈不上上下文治理。我自己的习惯是在入口处写一个简单的统计函数把每次请求的 token 消耗、上下文构成信息打出来。上线前用真实数据跑几轮基本就能摸清你这个应用“正常会话平均吃多少 token”“最长会话能吃多少 token”。有了这个基线后面的上下文管理策略才有数据可依。3. 实操基于 context-mode 搭建一个不乱聊天的智能问答理论讲完直接上一套可以照抄的实践方案。我用一个常见的智能客服问答场景做例子说明如何一步步把 context-mode 落到位。3.1 基础配置示例三种不同复杂度方案方案一最简单只传当前问题和 system 规则。适合一次性问答比如翻译、改写、知识解答不需要记忆。system: 你是一名客服助手。回答用简体中文简洁不超过100字。不确定时直接说需要人工。 user: 我的订单物流显示异常怎么办这种模式没有 context-mode 的“记忆能力”但它足够稳不会出现上下文污染。很多工具类应用其实用这种就够没必要强行加多轮记忆。方案二中等复杂把最近 N 轮对话拼进上下文。适合客服、闲聊型助手记忆最近内容的性价比最高。system: 你是一名客服助手。回答用简体中文简洁。 user: 我的订单物流显示异常怎么办 assistant: 您好请问您方便提供订单号吗我帮您查询一下。 user: 订单号是 A1001。 assistant: 好的看到您的订单因天气原因延迟预计明天送达。 user: 那我如果急着用可以取消订单吗注意这个方案真正传给模型时是不是要把全部历史都传过去不是。只传最近 4 轮到 8 轮就够了。人也不记得三天前的每句话模型没必要背负全部历史。方案三复杂记忆分层。短期记忆用最近历史消息长期记忆用摘要或向量召回。适合 Agent、复杂业务系统。记忆分层的核心思路是把上下文拆成“永久层、工作层、归档层”。永久层是 system 规则每次都在工作层是最近对话正常拼接归档层是老对话的压缩总结只在需要的时候通过检索等方式取回。这个结构很像人脑的记忆机制——近事记得清旧事靠回忆核心人格永远在线。3.2 消息组装策略连续对话不乱套的秘诀很多初学者的困惑是历史消息到底应该拼成什么样传给模型我提供一个经过验证的组装公式messages [] # 固定层系统设定 messages.append({role: system, content: system_prompt}) # 工作层最近几轮对话 for item in recent_history: messages.append({role: item[role], content: item[content]}) # 当前层本次用户输入 messages.append({role: user, content: current_input})注意几个细节系统设定在最前面最近对话按顺序排在中间当前问题在最后。这个顺序不能乱模型对紧挨着当前问题之前的内容关注度最高。另外当有检索资料时我习惯了加一个独立的“资料区”retrieved_info search(user_query) if retrieved_info: info_block 参考资料\n trimmed_info messages.insert(-1, {role: user, content: info_block})把检索资料用单独的 user 消息块放在当前问题前边并明确标注“这是一份资料而非用户原话”能有效避免模型把资料当指令。这招我用了很久很稳。3.3 上下文太长放不下三种主流裁剪策略不管窗口多大总有超限的时候。超限的应对方式业内主要有三种策略按复杂程度从低到高排序。第一种截断。简单粗暴地只保留最近几轮对话。实现最简单效果也还行因为大多数场景里最近内容确实最重要。但如果对话早期有关键信息比如用户已经说过“我只要红色的”截断后模型就忘了后面就会输出错误颜色。第二种摘要压缩。每积累到一定轮数就调用一次模型把之前对话提炼成一段摘要之后用摘要替代原始历史。if len(history) 10: summary_prompt f请将以下对话压缩为200字以内的摘要保留关键约束\n{history} summary llm.generate(summary_prompt) condensed_history [{role: system, content: f历史摘要{summary}}]摘要压缩适合需要长期记忆的场景。它的问题是摘要本身会有信息损耗而且每次压缩都要额外花一次模型调用。一个优化细节是压缩时把“用户说过的事实类约束”和“对话寒暄过程”分开处理事实类约束要完整保留寒暄过程可以大幅压缩。第三种结构化归档 检索召回。把历史对话按主题或时间切片存到向量数据库里当前请求只召回相关的片段。这是 RAG 的思路也是长期记忆最可靠的做法。实现成本最高但当前上下文始终保持可控不会因为会话过长而无限膨胀。我的建议上线初期用截断兜底观察真实对话长度分布发现确实有大量超长会话后再逐步升级到摘要压缩或结构化归档。不要一上来就上重武器很多项目的复杂度其实是自找的。4. 常见问题与排查实录我踩过的五个大坑context-mode 相关的问题线上线下加起来我见过太多案例了。这里直接整理成速查表每个都是真实场景里的教训。4.1 模型“失忆”了历史对话根本没用上症状用户说“我刚刚告诉过你了”模型却毫无反应。排查顺序先看日志确认历史消息有没有真正拼进 messages。我见过一半以上的“失忆”案例是代码里根本没把历史传进去或者传了但被缓存覆盖了。再看拼进去多少轮。如果只保留 1 轮活轮很多信息确实丢了。可以看下 token 统计确认历史占比是否正常。再看历史消息格式。用户消息、助手消息是否严格交替role 是否正确。模型对格式异常很敏感一个错位的 role 就能让整段上下文失效。最后看模型输出里是否参考了 system 中的历史摘要。如果摘要压得太狠关键约束丢失也会表现为“失忆”。解决手段优先确保日志链路可见再调整轮数和摘要策略。永远先查“事实是否进入上下文”再查“模型是否提取到”。4.2 上下文超限报错窗口不够用了症状用户聊久了直接报 context length exceeded。这类问题的根子在于上下文无上限增长。最有效的解法是“设上限”在代码里加守卫逻辑if total_tokens max_context_tokens * 0.8: history trim_or_summarize(history)guard 逻辑要放在组装消息之前而不是等 API 报了错再去处理。阈值可以按模型窗口的 80% 设置留 20% 给生成回复的 token 数。另外一个容易忽略的点多轮会话中 system 消息 tool 返回内容也会持续膨胀。如果接了外部工具注意工具返回的内容也要做裁剪。有的工具一次返回几十 KB 的 JSON几轮下来就直接爆窗了。4.3 上下文污染检索资料把模型带偏了症状模型开始引用用户没提过的东西甚至把一份参考资料的错误信息当结论输出。这种问题在接 RAG 后特别常见。根因是检索回来的资料里混入了和当前问题无关或低质量的内容而模型没有能力区分“资料里的客观内容”和“应该基于资料得出的回答”。排查方式把传给模型的消息数组完整打出来人工检查资料块内容。很多时候你会发现检索召回 TOP 5 里只有第 2 条是相关的其他 4 条全是噪音。模型为了“雨露均沾”就把噪音也卷进回答里了。解决思路提高召回过滤门槛向模型明确“如果资料与问题无关直接说明无法回答”更稳妥的做法是把检索和阅读分开——先用模型判断该不该检索、检索什么再基于检索结果回答而不是每次无脑全量检索。4.4 场景漂移聊着聊着就跑偏症状用户前面还在问订单问题后面开玩笑说“那你帮我把头发剪了”模型真的开始正经讨论发型。这种问题表面看是“模型没守住边界”底层是上下文管理模式没有给模型明确的“当前任务锚点”。解决方案是加一个动态 system 追加current_task detect_task(current_input) # 用分类器或模型判断用户意图 messages[0][content] f\n\n当前任务{current_task}。只处理与当前任务相关的问题无关话题请回复这个问题我暂时无法处理。这个技巧等于给模型立了一面“边界墙”明显减少闲聊跑偏的情况。不过注意意图检测本身也会有误差所以 task 判断结果最好也放进日志里方便观察误判率。4.5 同一问题重复回答上下文太多时反而盲区症状用户明明已经问过 A 问题模型又完整答了一遍 A像没看到之前的内容一样。这个问题的典型原因有两个。一个是消息距离太远A 问题被挤出工作层只存在于压缩后的摘要里摘要写得太粗时模型根本不知道之前答过另一个是来源定位差上下文里有多轮相似问题模型分不清哪个才是“当前要答的那个”。应对办法与其依赖模型自己辨别不如在上层做话题检测如果检测出用户重复提问把之前那一问一答的完整对作为高亮内容插到当前问题正上方保证模型必定看到。这个方法在多轮复杂场景里特别管用。5. 日志、评估与工具选型context-mode 的运维之道配置写好了还得能观测、能度量。很多团队在 context-mode 上翻车不是方案不对而是完全看不到内部状态出问题时只能靠猜。5.1 必备日志每次请求至少打三样东西我要求自己维护的项目里不管多小都要把三样信息打出来context 结构概览、token 消耗明细、关键参数快照。context 结构概览包括system 多少 token、历史消息多少轮多少 token、检索资料多少 token、本次用户输入多少 token。token 消耗明细就是 API 返回的 prompt_tokens、completion_tokens、total_tokens。关键参数快照包括用了哪个模型、上下文裁剪策略、是否命中缓存、当前阈值。日志格式建议用 JSON 单行方便后续接入日志平台。这不需要额外复杂基础设施一个 logging 函数就能搞定但它的价值会在排查问题时成倍放大。5.2 评估方法别只看回答好不好要看上下文设计对不对context-mode 改得好不好不能凭感觉。我常用的评估方式是“一正一反”两套用例正向用例正常长对话。聊 10 轮以上中途引入关键约束“我喜欢蓝色系”在最后几轮验证模型是否记住。填对话期望回答中应该包含蓝色系相关处理。反向用例对话边界测试。用户问与任务无关的问题期望模型礼貌拒答而不是跑偏用户重复提问期望模型不再重复展开用户提供互相矛盾的指令期望模型指出矛盾而不是盲目执行。这类用例不用多20 条左右就能覆盖大多数 context-mode 的回归风险。每次改动上下文章先跑一遍正向用例和反向用例再上线上流量这是最稳的习惯。5.3 工具选型什么时候上框架什么时候自研很多项目一上来就上 LangChain 或者 LlamaIndex 的 context 模块这本身没有错但你要想清楚框架能帮你省时间但也会用它的模式限制你的思路。如果你只是做一个中等复杂度的问答助手自研一个小而美的 context 管理器往往比套框架更可控。当项目复杂度上来后我建议引入专门的可观测性工具。比如 Langfuse、LangSmith、Arize Phoenix 这类 LLM 治理工具都能看到每次请求的完整 prompt、token 分布、模型回复有的还支持对比评估。它们做的事情本质就是“让 context 透明化”这比任何技巧都重要。一句话总结工具选型的教训你的第一套 context-mode 不需要任何复杂框架一个消息组装函数 一个日志函数 一套测试用例就能顶住早期全部需求。框架是复杂度到了再上的不是新手入门就该上的。6. 实操心得与一个小技巧context-mode 的最佳实践清单最后分享几个我在大量项目里沉淀下来的习惯。第一上下文设计永远晚一步做。先让功能跑通用最简单的单轮模式跑通主流程等真实用户进来了看日志里 token 消耗和对话轮次分布再决定要不要上记忆、上哪种记忆。过早设计上下文大概率方向是错的。第二system 消息里永远要有“兜底句”。比如“如果信息不足直接告诉用户你不知道不要猜测”。模型在有足够上下文但信息依然不够的时候倾向于编造内容。一句兜底规则能显著降低编造率。第三所有裁剪策略都必须保留“用户明确表达过的事实约束”。压缩对话时别把“我不想用顺丰因为收件地址不方便”这种关键约束压没。我的做法是压缩模板中强制加一步“提取用户要求清单”把约束单独列出再压过程对话。这能解决很多长期记忆失真的问题。第四也是最想让你记住的一点context-mode 没有银弹。单轮、多轮、长时记忆、知识库检索它们的适用场景完全不同。我看到太多项目最开始的失败就是试图用一个“万能上下文方案”解决所有问题。实际最稳的路径是知道你的应用最核心的对话形态是什么然后为这个形态把上下文做到极致其他场景留给兜底逻辑去处理。我之前做过一个客服系统最开始的复杂记忆方案反而天天出错。最后改成“双轨制”简单问题走单轮模式复杂问题才挂多轮上下文。上线后错误率直接降了一半。这个例子每次想跟人分享都在提醒我context-mode 不是越复杂越好匹配业务场景才是唯一标准。这篇文章写给正在或准备做 LLM 应用的人。没有宏大的架构也没有炫技的工程方案就是希望你在面对 context-mode 这个词时不只是知道一个概念而是有了一整套可以落地的思路和避坑地图。