
说实话我之前对context-mode的理解很长一段时间都停留在把用户消息拼进 prompt 再发给大模型这种最朴素的阶段。直到做过几个带多轮对话、带工具调用、甚至带长期记忆的 Agent 项目被上下文溢出、Token 爆炸、任务做到一半失忆这些问题反复毒打之后我才正式把上下文管理当成一个独立的、需要单独设计的核心模块来处理。这个模块业内习惯称之为 context-mode。在 LLM 应用工程里context-mode 不是一个花哨的功能开关而是决定应用能否在真实生产环境里活下来的地基。大模型的输入窗口再长也长不过用户连续几天的使用记录上下文窗口再多也装不下一个 Agent 在长任务执行中产生的全部中间过程。如果不做设计应用就会面临三个非常典型的问题一是历史对话无限膨胀Token 成本失控二是真正关键的信息被淹没在大量无关聊天记录里模型召回率下降三是当对话超过窗口上限时要么粗暴丢弃早期内容导致失忆要么全量截断导致当前任务也被误伤。Context-mode 要解决的就是这些问题。这篇文章想分享的是我在实际项目中落地 context-mode 的完整思路包括它的核心设计逻辑、Token 预算怎么算、历史上下文怎么分层、三种动态切换模式怎么触发以及一套可以直接抄作业的代码流程。适合正在做 LLM 应用、Chatbot、Agent 项目尤其是被多轮对话上下文问题困扰的工程师参考。对于自己搭模型玩的本地炼丹党也有一定借鉴价值。1. 整体设计思路为什么 Context-Mode 不是简单拼字符串1.1 我踩过的第一个坑全量拼接导致 Token 飞涨我先讲一个真实经历。第一个项目是一个企业知识库问答机器人当时觉得大模型的上下文窗口动辄几万 Token完全够用于是很偷懒地把所有历史对话全部塞进 system prompt每轮都全量发送。上线初期一切正常但用了一周之后一个活跃用户往下拉对话列表就会发现聊天记录越来越长我们的后台监控里单次请求的 Token 消耗从最初的 2000 多涨到了接近 5 万账单肉眼可见地往上飙。更难受的是模型开始出现前言不搭后语的情况明明用户五分钟前才说过自己用的是 Windows 系统模型却在前一个问题上回答 Linux 的配置方案。原因很简单信息太多了模型真实的注意力都被高频词带走早期关键信息反而失焦。所以我在做 context-mode 设计时第一原则就是上下文不是越多越好而是越精越好。不是把所有东西都塞给模型而是在每一轮请求前主动想清楚这一轮任务真正需要哪些信息。这是一个从被动拼接到主动调度的思维转变。1.2 三种常见方案对比全量、滑动窗口和混合式社区里常见的上下文管理方案大致有三类。第一种是全量保留也就是我前面踩坑的方案。优点是实现简单、信息无损缺点是成本快速失控且长文本中关键信息被稀释模型有效注意力面积有限。适合 demo 级应用不适合生产。第二种是滑动窗口。只保留最近 N 轮对话更早的一律丢弃。优点是算法简单Token 占用稳定缺点是一刀切逻辑容易误删长期任务里的关键背景。比如一个用户三天前提供过一份采购清单附件今天问那个清单里第三项的供应商联系方式是多少滑动窗口早就把三天前的记录冲掉了模型只能回答我没找到相关信息。这种体验很伤用户。第三种就是我认为最适合生产环境的混合式架构也是 context-mode 的核心思想。它把上下文分成三层系统指令层固定不变的应用行为规范、工作记忆层当前正在进行任务的细节、长期记忆层跨越会话周期的用户偏好、事实标签、历史摘要。每一层有不同的留存策略、更新频率和 Token 配额。context-mode 这个名字的mode含义就在这里——它不是一个静态结构而是可以根据当前会话状态切换成不同的模式比如深入问答模式、多步任务执行模式、轻量闲聊模式。每种模式下三层上下文的分配比例和调度策略是不一样的。1.3 为什么上下文分层比单纯压缩更可靠刚开始做上下文压缩的时候我试过直接把历史消息丢给大模型生成一段摘要然后把摘要塞进 prompt。单看思路没什么问题但实际操作了两个星期发现一个致命细节摘要过程本身引入的信息损耗是不可控的。比如用户随口说的一句价格不是问题质量第一在摘要里很容易被概括成用户关注质量但如果这是一个销售场景价格不是问题这个否定信息其实极其关键模型需要关注的不是重视质量而是做报价方案的时候不要过于纠结预算。所以 context-mode 的做法不是把历史消息全部压成一段摘要而是按信息的重要程度分级保留。能压缩的压缩不能压缩的关键原文必须保留。比如用户提供的账号信息、时间节点、金额数值、明确的指令性语句这些属于高保真关键上下文必须原文保留而寒暄、语气词、冗余重复的表述属于低价值上下文可以压缩或直接丢弃。这就是为什么混合式设计比单一压缩方案更可靠——它把信息有损压缩控制在合理的范围内避免错杀关键细节。2. Token 预算与优先级分层先把账算清楚再动手2.1 设计 Token 预算的基本公式如果说上下文分层是 context-mode 的灵魂那么 Token 预算管理就是骨架。我一般会在代码里定义一组常量把每一轮请求允许的最大 Token 数拆分成明确的组成部分。拆法并不复杂核心是一个配额公式MAX_PROMPT_TOKENS MODEL_CONTEXT_WINDOW * 0.7为什么只取 70%因为我需要预留 30% 给模型生成回复的空间。如果使用最大上下文窗口为 128K 的模型我实际可用的 prompt 预算是约 89K而不是把 128K 全部塞满。如果设置成 90% 甚至更高模型很可能输出到一半就触发截断尤其是长文档总结、长代码生成这类输出场景生成 Token 消耗波动非常大没有余量会很被动。在这个大前提下我再把 89K 预算继续拆成四块系统指令System固定占 2K~4K。包括角色设定、行为约束、输出格式、安全限制。无论什么模式这是最优先保证的不允许被挤占。用户当前输入按实际情况动态计算一般不超过 4K。如果用户一次性贴了很长的文档可以考虑额外分配但要警惕单条消息吃掉整个上下文的情况。关键上下文片段根据模式动态分配一般占 30%~50%。历史摘要与长期记忆剩余预算一般占 20% 左右。2.2 四层优先级什么内容必须进上下文有了总预算下一步就是给不同类型的内容排优先级。我在实践中把上下文内容优先级分为 P0 到 P3 四层P0必须包含系统指令、当前用户最新消息、正在执行的工具调用返回结果、明确的约束条件比如只要 JSON 输出。缺失会导致本轮任务直接跑偏。P1尽量包含当前任务相关的关键事实比如用户提供的账号、订单号、时间、数值参数、上一步的中间结论。这些内容一旦缺失模型会给出错误的猜测性回答。P2按预算包含早期对话中与当前主题弱相关但有背景价值的部分以摘要形式存在即可。比如用户三天前提过我们在做一个跨境电商项目之后一直围绕物流问题提问那段背景可以压缩成一句话摘要。P3默认排除寒暄、重复表述、已过期的状态信息、权限校验日志、调试信息等。这些东西留在上下文里只会占据宝贵的 Token 预算。每次构造 prompt 时我都会跑一遍这个优先级逻辑。先说结论宁可牺牲 P2 的历史细节也绝不动 P0 的当前任务信息。这个取舍原则在开发中帮了我很多次。有一次用户正在让 Agent 逐步执行数据处理流水线连续调用了七八个工具步骤中间某个环节报错。如果此时简单地把早期步骤摘要掉模型就不知道自己已经执行到哪一步了会从零开始重跑或者跳过关键步骤。而 context-mode 会把已完成步骤和当前步骤标记为 P1 甚至 P0确保任务有连续性。2.3 用 Token 计数器做硬性校验分配预算不能只靠感觉差不多必须有硬性的计数校验。我在代码里引入一个简单的 tokenizer 封装不要求精确到字节级只要能和模型的分词器基本对齐即可。如果是 OpenAI 系模型直接用tiktoken如果是开源模型可以用transformers的 tokenizer。每次组装完 prompt先计数如果超出 MAX_PROMPT_TOKENS就按优先级从低到高逐级裁剪直到满足条件。实际测试中我发现很多 Token 超限问题不是出在历史消息而是出在工具返回结果。有一次 Agent 调用一个搜索接口接口把一整页 HTML 原样返回了光这部分就占了几千 Token。后来我在工具层加了个后处理对返回结果做截断和结构化提取问题立刻缓解。context-mode 要管的不仅是模型输入侧还包括工具输出侧这是一个很容易被忽视的隐藏成本点。3. 动态模式切换Context-Mode 的核心调度机制3.1 三态模式活跃态、压缩态、冻结态如果只是做预算分配那它本质上还是一个静态的上下文拼接器。真正让 context-mode 具备智能特质的是它的动态模式切换机制。我在项目中实现了三种会话模式。活跃态Active会话保持在短时间内上下文以原文形式完整保留用于即时问答和连续对话体验最好Token 消耗也最高。压缩态Compressed当会话变长或出现时间间隔后将早期内容转化为结构化摘要保留近期原文Token 占用中等。冻结态Frozen长时间未活跃的会话只保留核心长期记忆用户画像、几个关键事实标签其余全部归档不参与 prompt 组装几乎不占 Token。模式之间的切换不是主观决定的而是靠一组条件触发。我把它做成了一个独立模块每个会话状态由state字段标记每轮请求到来时先检查当前状态再决定是否切换。3.2 触发条件一Token 阈值触发最简单也最可靠的触发条件是 Token 占用上限。每轮对话结束后系统会计算当前会话的原始上下文总 Token 数当它超过某个预设阈值比如总预算的 80%自动把状态从活跃态切换到压缩态。当一个会话的摘要 Token 也超过阈值时进一步切换为冻结态只保留长期记忆。这个阈值设计有一个细节不要设成 100%留 20% 的缓冲。因为从触发切换到完成压缩之间需要时间如果等到完全满载再处理下一轮请求可能会因为瞬间溢出而出错。我一般设 80%既能保证切换过程平稳又不会过早压缩导致频繁的摘要计算。3.3 触发条件二时间窗口触发第二个触发条件是时间窗口。用户可能连续几小时在一个会话里深入交流然后隔了两天又回来继续问。这时候如果还把两天前的原文全部塞进上下文不仅浪费 Token而且容易造成语义割裂感——用户今天的诉求可能已经完全变了。我采用的做法是每隔 20 分钟检查一次会话活跃度。如果距离上一条消息超过 30 分钟就把中间状态标记为可压缩。距离超过 24 小时的会话直接进入冻结态。这样设计的好处是即使用户在前一天的会话里聊过怎么登录后台今天回来问的却是报表导出失败这两个话题在上下文上完全没有强关联保留昨天的全部原文就没有必要。当然如果检查到两个话题的相关性比较高比如今天的问题里明确引用了昨天的内容昨天说的那个导出按钮在哪我会把触发状态回退从冻结态临时切回压缩态额外取出旧会话的关键部分。3.4 触发条件三多轮意图突变检测这个触发条件我最想推荐因为它能解决一个真实痛点用户在同一个会话里连续问了很多问题突然从闲聊切换到要写一段 Python 代码。如果没有意图突变检测系统很容易把闲聊的摘要堆积进去挤占代码生成需要的上下文空间。实现并不复杂。我在每轮收到用户消息时做一次轻量意图分类类别包括闲聊/问答/代码生成/工具调用/分析总结等。同时维护一个滑动数组记录最近五轮意图。当最新意图与前五轮中超过 70% 的意图类别不一致时判定为意图突变立刻触发上下文重建把当前意图类别相关的历史原文保留其他类别的历史内容降级为摘要。这个机制在实际使用中效果非常明显尤其是混合型项目里用户前脚在问业务知识后脚让帮我把刚才的讨论整理成周报如果周报生成只需要讨论摘要而不需要逐字对话记录意图检测就能自动缩短上下文提高输出质量。3.5 切换的粒度控制不要整场会话一把切模式切换最容易犯的一个错误是整场会话一把切。意思是一旦进入压缩态就把所有历史原文全部变成摘要不留任何原文。这不行。我一开始也是这么干的结果发现用户经常会追问你刚才说的那个方案细节是什么而摘要里根本不会保留方案细节于是模型就只能含糊其辞。后来我改成句子级/条目级粒度。进入压缩态时不是直接对整段对话生成一段摘要而是把历史消息按信息块拆分每一块单独判断如果是关键事实含数字、名称、时间、明确指令保留原文如果是推理过程压缩成结论如果是寒暄或者重复直接丢弃。这样从活跃态进入压缩态之后关键原文仍然在上下文中只是冗余表述变少了。效果可以说是立竿见影——用户追问细节时模型的回答准确率明显回升。4. 实操一个最小可用的 Context-Mode 实现流程4.1 工具选型别急着上框架很多朋友做这类功能第一反应是上 LangChain、LlamaIndex 这些框架。我的建议是第一版先自己写不要上框架。原因很简单context-mode 的核心逻辑其实只有几十行代码可以表达清楚自己手写反而更能理解每个环节为什么存在。框架提供的封装虽然方便但出问题的时候定位困难尤其是 Token 裁剪和模式切换这种偏自定义的逻辑框架反而成了束缚。我的推荐组合是Python 后端 Redis 存储会话状态 tiktoken 做 Token 计数 OpenAI 兼容接口或任意本地模型服务。如果你用的是本地模型替换成 transformers 的 tokenizer 就行整体架构完全不受影响。Redis 在这里的定位是会话状态存储。每一个会话conversation_id对应一个哈希表字段包括state当前模式、recent_text近期原文JSON 格式、summary摘要、long_term长期记忆标签、intent_history意图滑动数组和last_active最后活跃时间。选 Redis 而不是直接存文件是因为它支持过期时间天然适合做会话的自动冻结而且读写性能好不会成为高并发下的瓶颈。4.2 核心流程组装前先调度整个 context-mode 的执行流程我在代码里拆成了四个阶段状态检查拿到 conversation_id 后先从 Redis 读出会话状态判断当前处于什么模式。模式评估根据 Token 阈值、时间窗口、意图突变三个条件决定是否要从当前模式切换到另一个模式。如果切换执行对应的状态迁移动作。上下文组装按优先级分层从系统指令、当前输入、近期原文、摘要、长期记忆中选取内容拼接成最终的 prompt。Token 校验用 tokenizer 计数如果超限按 P3 到 P1 的顺序裁剪直到满足预算。我给这段流程整理了一份伪代码你可以直接参考着实现# 简化版 context-mode 调度核心 def build_prompt(conversation_id, user_input): state redis.hgetall(fsession:{conversation_id}) # 1. 模式评估是否切换 new_state evaluate_mode(state, user_input) if new_state ! state.get(state): state migrate_state(conversation_id, state, new_state) # 2. 按优先级组装上下文 segments [] segments.append((system, load_system_prompt())) # P0 segments.append((input, user_input)) # P0 segments.append((recent, state[recent_text])) # P1 segments.append((summary, state[summary])) # P2 segments.append((longterm, state[long_term])) # P2 # 3. Token 校验与裁剪 prompt assemble(segments) over count_token(prompt) - MAX_PROMPT_TOKENS if over 0: prompt trim_by_priority(prompt, over) # 4. 更新会话状态 update_state(conversation_id, user_input, prompt) return prompt全部逻辑都在一个函数里完成没有魔法。你完全可以把它改造成任意语言版本。4.3 状态迁移与摘要的实现细节状态迁移是 context-mode 里最需要小心的地方。活跃态切到压缩态时要对近期原文做一次信息拆分和摘要计算。这个过程我建议异步执行避免阻塞当前请求。可以单独起一个后台任务或者用 Redis 的过期后回调机制没这个能力的话也可以用定时扫描。摘要计算这一步我推荐给大模型下这样的指令——不要只用一句话概括而是输出一个结构化的 JSON包含三部分key_facts关键事实原文数组包含数字、名称、时间、conclusion简短结论、outdated已过期信息丢弃即可。这样做的好处是压缩后的摘要保留了关键事实原文后续组装上下文时可以把key_facts当成 P1 直接使用而不会被摘要过程弄丢细节。冻结态的迁移就更简单了从摘要中提取长期记忆标签写入long_term字段然后清空recent_text和summary。比如摘要里出现用户是产品经理、关注数据看板、负责三个项目这类信息可以抽成三个标签存起来。冻结会话重新激活时系统优先加载long_term到上下文中再根据需求决定是否恢复详细摘要。4.4 一个完整的本地可运行示例为了让你更直观地感受整个流程我写了一个更具体的示例。假设后端是一个 Flask 应用调用 OpenAI 兼容接口。核心代码如下# app.py 简化版示例 from flask import Flask, request, jsonify import redis, json, time import tiktoken app Flask(__name__) pool redis.Redis(hostlocalhost, port6379, db0) enc tiktoken.encoding_for_model(gpt-4o) MAX_PROMPT_TOKENS 8000 MODAL_BUDGET 3200 def count_token(text): return len(enc.encode(text)) def make_completion(messages): # 这里替换成你自己的模型 API 调用 import openai resp openai.chat.completions.create( modelgpt-4o, messagesmessages, temperature0.7 ) return resp.choices[0].message.content app.route(/chat, methods[POST]) def chat(): body request.get_json() conversation_id body.get(conversation_id) user_input body.get(message, ) key fsession:{conversation_id} state pool.hgetall(key) if not state: state { state: active, recent_text: json.dumps([]), summary: , long_term: json.dumps({}), intent: json.dumps([]), last_active: str(time.time()) } pool.hset(key, mappingstate) # 判断是否超 Token 阈值触发压缩切换 recent json.loads(state[recent_text]) recent.append({role: user, content: user_input}) raw_token_count count_token(json.dumps(recent)) if raw_token_count MODAL_BUDGET and state[state] active: # 触发压缩过渡只保留最近三轮原文其余做摘要 keep_recent recent[-6:] compress_payload recent[:-6] summary_text summarize(compress_payload) # 调用模型生成结构化摘要 state[summary] summary_text state[recent_text] json.dumps(keep_recent) state[state] compressed pool.hset(key, summary, summary_text) pool.hset(key, recent_text, json.dumps(keep_recent)) pool.hset(key, state, compressed) # 组装 prompt messages [{role: system, content: SYSTEM_PROMPT}] if state.get(summary): messages.append({role: system, content: f历史摘要{state[summary]}}) for msg in recent[-6:]: messages.append(msg) # Token 校验超限裁剪 total count_token(str(messages)) while total MAX_PROMPT_TOKENS and len(messages) 2: # 优先丢 system 之外的第一个历史消息 messages.pop(1) total count_token(str(messages)) answer make_completion(messages) recent.append({role: assistant, content: answer}) # 只保留近四轮原文防止无限膨胀 pool.hset(key, recent_text, json.dumps(recent[-8:])) pool.hset(key, last_active, str(time.time())) return jsonify({answer: answer})这段代码故意做了大量简化删减了意图突变检测和时间窗口触发但骨架已经足够说明问题。核心就是先评估状态再压缩/组装最后做 Token 校验。4.5 实操中的埋点与观测代码能跑只是第一步。在真实环境里context-mode 到底有没有生效、生效后对回答质量有没有影响需要一个观测维度。我在项目里加了几个关键日志每一轮的state值、raw_token_count、压缩次数、被裁剪掉的条目数、模型回复的字数。汇总到监控面板之后发现一个很有意思的规律压缩态的会话平均响应延迟比活跃态降低了近 40%因为 prompt 变短了模型生成等待时间也相应减少。而回答质量并没有明显下降因为关键事实被高保真保留了。还有一个值得关注的点摘要计算本身的延迟消耗。压缩态触发时如果直接用大模型对长文本做摘要一次可能耗时 3~5 秒虽然对单次请求来说不算致命但在高并发下会成为瓶颈。我后来做了两个优化一是摘要生成异步化用户这一轮先拿旧摘要顶着压缩结果后台更新二是摘要输入做了截断只取原始文本的前 3000 Token 和末尾 1000 Token优先保证开头结尾的信息密度。两个优化叠加后压缩操作对用户请求的实际影响几乎降到了零。5. 常见问题与现实排查Context-Mode 落地避坑指南5.1 问题一摘要了等于没摘要Token 还是超限这是最常见的困惑。很多人的代码逻辑看起来完全没问题但 Token 就是压不下来。排查时我先怀疑的是摘要是不是走样了。有次我抓日志发现摘要模块输出的 JSON 格式字段里key_facts把原文本的 5000 字几乎原样复制了一遍完全没有起到压缩作用。原因是我交给模型的指令里写了保留关键事实原文模型把原文理解成了保留整段原文。解决方案是给摘要模型一个更硬的输出约束——key_facts数组里每个元素不能超过 50 个字且最多输出 5 个元素。这样哪怕模型想多写输出格式也把它卡死了。另一个容易忽略的点是裁剪逻辑写错位置。有些代码是在组装 messages 之后才做 Token 裁剪裁剪时把用户当前输入给误删了导致模型收不到最新消息。我在代码里专门加了一条保护规则messages列表中第一个和最后一个元素不允许被裁剪。最后一个其实是助手回复但当前输入在组装时已经放到了最后的 user 消息所以这条规则能保证当前输入绝不丢失。5.2 问题二长期会话的上下文串扰所谓串扰就是一个会话里的早期历史信息突然干扰了当前问题的回答。最典型的现象是用户两周前问过如何申请发票系统在长期记忆里存了一条用户关注发票。两周后用户问这个季度的成本怎么核算模型突然蹦出一句您可以在发票管理系统里查看。这就是长期记忆标签过粗导致的问题。我在排查中发现长期记忆不应该只存话题标签还应该存时效性和关联条件。比如发票这个标签时效性是已结束关联条件是仅当用户提到报销/发票/开票时激活。这样在上下文组装时只要当前用户输入没有触发关联条件长期记忆标签就不加载。实现上不难给每个标签加一个keywords字段做一个简单的关键词匹配即可。5.3 问题三模式切换过于频繁有些场景下用户的问题长度波动极大一会儿扔来一篇几千字的长文一会儿只发继续。如果 Token 阈值设得太敏感会话就会在活跃态和压缩态之间来回横跳导致摘要计算非常频繁系统性能白白浪费用户体验也差。我后来加了一个状态切换冷却时间cooldown同一个会话在 5 分钟内最多只能切换一次模式。即使触发条件满足了只要上次切换时间距现在不足 5 分钟就维持原模式不变只是把待切换的状态打一个标记等到冷却结束后再执行。这个细节虽然小但能显著减少无意义的重复计算。5.4 问题四知识库检索模块与 context-mode 打架做带知识库的问答系统时经常遇到一个矛盾知识库检索出来的相关文档块与 context-mode 维护的会话历史都在抢同一份 Token 预算。我的做法是给知识库文档块单独划分一个预算区间比如 30%不与会话历史混用。当文档块本身内容过多时优先从历史摘要里裁剪而不是裁文档块。因为对知识问答这种场景来说知识库内容的相关性要远高于聊天历史而在闲聊场景知识库检索通常根本不会触发。把这个区分写清楚之后系统在两种场景下的表现都变好了不再出现为了保留聊天记录把真正有用的检索文档顶掉的问题。5.5 问题五如何评估压缩对回答质量的影响很多人做完 context-mode 之后找不到一个客观的指标来衡量压缩到底有没有损失关键信息。只凭几个人肉测试感觉还行是不够的。我推荐一个我一直在用的方法构造一组回溯性问题提前在对话历史里埋入一些关键事实比如一串订单号、一个具体日期、一个价格数字然后在一段时间后模拟进入压缩态让模型回答这些回溯性问题。如果模型能回答出精确数字和名称说明关键事实保留成功如果只能回答个大概甚至完全答错就说明压缩策略过于激进。把这组问题做成自动化测试集每次修改 context-mode 的压缩策略后跑一遍能非常直观地看出策略改动带来的质量波动。在具体标准上我的经验是关键事实的准确率不能低于 90%。如果低于这个值说明你为了省 Token 牺牲了太多用户核心体验得不偿失。6. 从能用到好用最后几点优化心得做 context-mode 做到后期技术层面其实已经没有太多的秘密了真正拉开差距的反而是细节优化。这里分享几个我自己在项目里实践过的小技巧。第一个技巧是在系统指令层面加入上下文时间感知。模型本身不知道当前时间但 context-mode 知道。在组装 prompt 时注入一句当前时间是 2025 年 6 月 25 日下午 3 点历史摘要中的信息可能已过期请优先参考用户最新消息。这句话能有显著效果尤其是在处理用户隔了很久重新激活的冻结会话时模型会更倾向于接受用户当前的修正信息而不是固执地沿用旧数据。第二个技巧是对关键上下文做显式的防丢标记。如果有 P0 级别的信息可以在组装时把它单独作为一个system消息插入而不是混在user历史里。因为模型对system消息的遵循度通常高于普通历史消息。我在一个数据清洗项目里把用户反复强调的保留空值列这个指令从历史对话中提取出来单独放到 system 消息中模型遵守指令的比例一下子从 70% 提到了 95%。context-mode 的价值就在这里——它不只是管理 Token还在管理模型的注意力把重要的事情放到模型更容易注意到的地方。第三个技巧是后台预压缩。不要等到用户发起请求时才去压缩历史。我采用的做法是每轮对话结束后立刻在后台判断当前会话是否接近压缩阈值如果接近就异步生成摘要并更新状态。这样用户感知不到任何延迟而每一轮请求的上下文都已经是预压缩后的状态。这个优化对交互体验的提升非常明显。最后一个心得是关于测试数据建设的。很多项目做 context-mode测试时用一两条样例感觉差不多就上线了结果在真实用户的长会话里翻车。最佳实践是从第一天起就收集真实的匿名化对话数据尤其是那种超长会话用这批数据回放来验证模式切换的准确性。我经手过的两个项目都是前期没有回放数据上线后被一个 50 轮以上的长会话直接击穿。后来把回放机制补上了再也没出现过类似的低级事故。context-mode 这个东西说起来不过是管理上下文、分配 Token、切换模式十来个字但真正做进去你会发现它本质上是一个信息调度系统。它需要你对模型的行为特性有感知对业务的真实对话模式有统计对信息的重要程度有判断。这不是一个只靠堆 prompt 技巧就能做好的模块而是一个值得花心思反复打磨的基础能力。希望这篇文章里分享的设计思路、代码骨架和踩坑经验能帮你在做自己的 LLM 应用时少走几段弯路。