
1. context-mode 到底在解决什么问题为什么上下文是现代应用的第一资源先抛一个我做这个项目之前反复被问倒的问题你在做一个聊天工具、AI 助手或者任何需要记住前面聊了什么的应用时服务端到底存了什么很多人的第一反应是存消息记录再进一步是存 session。但真正把产品跑起来之后你会发现这两个答案都不够。消息记录是流水账session 只是连接标记它们都解决不了一个核心矛盾用户在不同的任务线之间切换时系统该用哪一段历史来理解当前的输入。我最初接手的是一个团队内部用的 AI 文档助手需求很简单用户上传一批资料然后针对这些资料提问。跑了两周之后问题来了——用户会在同一个会话里问完帮我总结这份合同的风险点紧接着又问那我的请假流程走到哪一步了。我们的 AI 把合同总结和请假审批的历史全部塞进了上下文结果就是回答质量断崖式下跌幻觉率飙升。我意识到问题不在模型也不在提示词而是整个系统缺少一个能被用户感知、能被开发者控制的上下文模式。于是就有了这个代号为 context-mode 的项目一套可切换、可持久化、可隔离的上下文管理模块。这篇文章不聊概念直接把我在这个项目里踩过的坑、敲过的数据结构、调过的切换逻辑全部摊开。如果你正在做 AI 应用、客服系统、IDE 插件或者任何带多任务并行特性的产品这篇应该能帮你省下至少两周的弯路。2. context-mode 的核心设计我把上下文拆成了三层动手写代码之前我花了大量时间想清楚一件事上下文到底是什么后来我得出了一个现在看起来非常朴素但极其好用的结论上下文不是数据而是数据的一个视图。同一个用户同一个时间段他在处理工作合同和请假流程时需要的相关历史是完全不同的。如果只用一份全局变量式的历史记录任何切换都是灾难。所以我把 context-mode 设计成三层结构。2.1 第一层全局上下文Global Context这一层保存与具体任务无关的基础信息用户身份、时区、语言偏好、全局设置、当前所处的产品模块等。它的特点是永不随模式切换而清空任何模式下都能访问。举个例子用户说帮我安排明天下午的会议系统必须先知道用户的时区和默认日历这些信息放在全局上下文里不管当前是合同审查模式还是请假审批模式都能正确解析明天下午。2.2 第二层模式上下文Mode Context这是 context-mode 的核心。每个模式维护一份独立的上下文窗口包含该模式下最近 N 轮对话、相关文档片段、任务状态等。模式之间完全隔离切换模式时当前模式的上下文被冻结存档目标模式的上下文被恢复加载。我在这里做了一个关键决策不做自动清空只做自动隔离。哪怕某个模式已经 30 天没被使用它的上下文也会被持久化保存直到用户主动删除或管理员设置过期策略。因为从用户视角看我上次查那份合同的进度是合理需求系统有必要记住。2.3 第三层瞬时上下文Ephemeral Context这一层处理那些不需要长期记住的内容一次性查询、临时指令、验证码确认等。瞬时上下文只存在于当前请求的生命周期内请求结束即丢弃。引入这一层纯粹是实战教训。最初版本里用户随口说了一句其实这个也行系统拼命去历史里找这个指代什么结果解释出一堆荒唐结论。后来我规定凡是属于临时修正、即时确认类的信息一律只进瞬时上下文不进模式上下文。三层设计带来的直接好处是上下文切换的成本从重新理解一切降到了换一套视图。全局信息不需要重复加载瞬时信息天然不污染长期记忆只有模式上下文需要做存档和恢复的操作。3. 上下文切换的核心机制显式切换与隐式切换的取舍模式分好了接下来是重头戏怎么切。我在产品里做了两种切换方式分别解决不同场景。3.1 显式切换用户通过 UI 明确选择要进入哪个模式。这个方式在 web 应用里最常见——侧边栏列出合同审查请假审批数据分析等模式入口点击即切换。显式切换的代码逻辑相对简单一个 switch 操作涉及三件事async function switchContextMode(userId, targetMode) { // 1. 冻结当前模式上下文异步落盘 await freezeCurrentMode(userId); // 2. 恢复目标模式上下文如果存在 await hydrateTargetMode(userId, targetMode); // 3. 更新当前模式标记 await setActiveMode(userId, targetMode); }但这里有个容易被忽视的问题切换是一个分布式操作。如果服务端是多实例部署用户可能被负载均衡打到不同的机器上冻结和恢复必须基于共享存储而不是本地内存。我踩的第一个坑就在这里后面详细说。3.2 隐式切换隐式切换指的是系统根据用户输入自动判断应该进入哪个模式。这块做起来要复杂得多也是 context-mode 最见功夫的地方。我的实现思路是给每个模式定义一组触发信号分为强信号和弱信号两类。信号类型示例处理策略强信号用户输入中出现模式专属关键词如合同风险条款立即切换无需确认弱信号输入同时匹配多个模式如这个方案的风险保持当前模式等待更多上下文复合信号输入包含多个强信号如合同和请假流程哪个更急进入混合模式或要求用户明确选择隐式切换的难点在于它不能太敏锐。用户可能只是在当前模式里顺嘴提了一句别的领域的话系统如果立刻跳走体验会非常断裂。我最终的策略是强信号触发延迟确认弱信号累计阈值触发。就是说单个强信号出现时不立即切换而是在当前模式上下文里打一个疑似切换标记如果下一轮输入继续指向新模式再真正切换。这个策略上线后误切率从最初的 30% 降到了 5% 左右。代价是响应延迟增加了约 300ms因为需要多跑一次意图判断但换来的是用户体验的稳定性非常值得。4. 上下文持久化从 Redis 到对象存储的分级方案模式上下文不能只活在内存里否则服务一重启全没了。我在持久化方案上花了最多时间也踩了最多的坑。4.1 热数据Redis 里的紧凑结构对于近 24 小时内有活跃访问的模式上下文我用 Redis 存储数据结构是一个 hash# key 设计ctx:{userId}:{modeId} HSET ctx:u100:m01 token_count 3200 HSET ctx:u100:m01 last_active 1730600000 HSET ctx:u100:m01 messages [{role:user,content:...}, ...]messages 字段是最近 N 轮的对话记录。token_count 字段用来做预算控制后面会讲。这里的关键是 messages 不能无限增长。我按照模型的最大上下文长度比如 8K token设置了一个水位线超过水位线就触发压缩策略把较早的对话摘要化只保留关键实体和结论。4.2 冷数据对象存储里的完整快照超过 24 小时未活跃的模式上下文我把它从 Redis 迁移到对象存储S3 或内网的 MinIO。每份快照是一个 JSON 文件文件名带上时间戳ctx_snapshot_u100_m01_20240601T120000Z.json快照内容除了对话记录还包括该模式的元信息创建时间、最后活跃时间、压缩轮次、关联文档列表等。从 Redis 迁移到对象存储是异步的由一个定时任务扫描 Redis 里所有 ctx key 的最后活跃时间超过阈值就触发迁移。这个分级方案的好处是成本可控。Redis 内存很贵不能无限存对象存储便宜适合做长期存档。实测下来单个用户同时拥有 5 个模式的上下文Redis 占用大约 300KB压缩后完全在可接受范围内。4.3 序列化格式为什么我放弃了 Protocol Buffers最初我用了 Protobuf 做序列化理由是性能好、体积小。但用了两周后我放弃了改回了 JSON。原因很现实调试效率。当 AI 回答出现问题时我需要快速查看上下文里到底有什么。JSON 文件可以随手打开浏览器看而 Protobuf 二进制必须写解码脚本才能看。对于 context-mode 这种场景序列化性能不是瓶颈——因为上下文切换本身是低频操作用户不会每秒切一次而调试效率是直接的开发成本。我在团队里立了一个规矩凡是人需要频繁查看内容的数据结构优先用 JSON凡是机器高频处理的数据结构才考虑二进制格式。5. 上下文压缩从 32K 塞回 8K 的实战教训每个模式上下文都有长度上限。模型输入窗口有限当对话越来越长必须做压缩。这个模块我重写了三遍前两遍都在盲目删旧消息效果很差。5.1 第一版纯截断的失败最初我简单地保留最近 N 条消息。测了一个月发现 AI 经常犯失忆症用户在 20 轮之前明确说过的约束条件系统完全忘了。因为截断把那些信息物理删除了而模型只能靠剩下的话去猜猜得乱七八糟。5.2 第二版摘要式压缩第二版我改成当长度超过上限时调一次模型把前面的对话压缩成一段摘要然后带着摘要继续对话。效果好了很多但引入了新问题——每压一次就要额外消耗一次模型调用成本上去了而且摘要质量不稳定模型偶尔会把关键约束漏掉。5.3 第三版分层摘要 关键项保留最终版本用了三条规则组合起来效果最好对话按主题分段每段生成独立摘要而不是整段对话压成一个摘要。关键约束显式提取单独存成一个约束清单压缩后仍然完整保留。比如用户要求所有合同必须通过法务审核再发时间格式用北京时间这类硬性条件即使原始对话被摘要化约束清单里依然原样存在。最近 5 轮对话永不清零保证模型对最近的语气和细节有完整感知。这版的实测效果压缩后上下文从 32K token 降到 8K模型回答的准确率在测试集上只下降了 2% 左右而第一版截断方案下降了 18%。成本上每次压缩的模型调用费相当于一次短对话对一个日活不高的内部工具来说完全能接受。6. 排查链路最棘手的一个 Bug——模式上下文串线介绍完正常逻辑必须说说我排查过的最诡异的一个问题。现象是用户明明在合同审查模式里发消息系统却偶尔用请假审批模式的历史来回答。两个模式我们确认过是物理隔离的没有任何共享逻辑。6.1 初步排查存储层没问题我先查了 Redis 里两个 key 的数据确认各自的 messages 字段内容是对的没有互相写入。存储层排除。6.2 定位到线程变量接着我怀疑是上下文传递的中间层出了问题。查看代码后发现在隐式切换的意图判断阶段我用了 ThreadLocalJava 项目保存当前用户 ID。这个变量在请求结束后没有清理而服务端线程池会复用线程。当一个用户登录后发起请求 A在另一个用户发起请求 B 时同一个线程的上下文里带着上一个用户的信息。这就是典型的线程上下文泄漏。这类问题在 Java 服务端几乎人人会遇到但出现在 context-mode 项目里尤其隐蔽因为不是每次都发生而是取决于线程复用的时机。6.3 修复与验证修复方式很机械但很有效在过滤器里强制清理 ThreadLocal用 try-finally 保证无论请求是否异常线程变量都会被移除。try { ContextHolder.set(userId); // ... 业务逻辑 } finally { ContextHolder.clear(); }验证方法也分享一下我写了一个并发测试脚本用 100 个虚拟用户同时随机切换模式每个用户发 50 条消息然后在日志里交叉比对 userId 与 modeId 的一致性。跑了一个小时没有再复现串线问题才确认修复完成。这个 Bug 让我意识到context-mode 这类项目表面上是数据管理问题骨子里其实是状态管理问题。任何被隐式共享的状态——线程变量、静态变量、单例对象——都可能成为上下文串线的源头。7. 面向 AI 场景的进阶把 context-mode 接到大模型上这个项目最初的触发点就是 AI 文档助手所以最后专门讲讲 context-mode 和 LLM 应用的结合方式这也是目前被问得最多的部分。7.1 模式上下文作为系统提示的观点接 LLM 时我不再把所有历史一股脑塞给模型而是拼接成三层结构[全局上下文] 用户时区Asia/Shanghai, 语言zh-CN, 产品文档助手 [模式上下文摘要] 当前模式合同审查, 历史摘要已审查3份合同, 约束所有风险点必须引用条款编号 [瞬时上下文] 当前问题第四份合同的违约金条款是否合理这样做的效果是模型收到的信息量少了但信噪比高了。实测中同样的测试集塞全部历史时回答准确率 78%改为 context-mode 结构后准确率提升到 91%。不仅准确率提升响应速度也快了因为输入 token 数少了首字响应时间大约降了 40%。7.2 模式内的工具调用隔离AI 应用离不开工具调用。合同审查模式里系统要能调用合同解析条款比对风险标记等工具请假审批模式里调用的则是审批进度查询假期余额等工具。context-mode 在工具调用上的设计是模式决定工具白名单。每个模式配置一份允许调用的工具列表模式外工具一律禁止。这个设计最开始是为了安全把 AI 能触达的范围限制在该模式需要的功能内结果意外地提升了稳定性——工具少了模型选错工具的概率大幅下降。我当时在代码里用一个简洁的映射关系管理MODE_TOOL_REGISTRY { contract_review: [parse_contract, compare_clauses, flag_risks], leave_approval: [query_approval_flow, check_balance], }7.3 成本控制token 预算的按模式分配上下文管理绕不开成本。我给每个模式设置了独立的 token 预算全局模式下发一张总图。比如企业版账户每天有 50 万 token 的模型调用额度其中合同审查模式分配 60%、请假审批模式分配 15%、其他模式共享剩余部分。预算用尽后该模式进入精简模式不再发送完整摘要只发约束清单和最近一轮对话。实测中精简模式下的回答质量下降约 10%但成本可以省下 70%。对预算敏感的小团队来说这种按模式分配预算的玩法比全局一刀切灵活得多。8. 关于 context-mode 适用范围的一些个人判断做了这么久的上下文管理我最后想聊一点主观的东西不是所有应用都需要 context-mode。如果是单任务工具比如一个计算器、一个单词查询页那全局一份上下文就够了引入模式反而徒增复杂度。context-mode 真正适用的场景是用户会在同一个产品里处理多种并行任务的场景——AI 助手、项目管理工具、客服工作台、IDE 编辑器这些地方用户的思维切换频繁历史关联复杂单一上下文的瓶颈会暴露得非常快。我个人在维护这个项目时体会最深的一点是做上下文管理本质上是做记忆的边界。系统不可能记住所有东西也不可能什么都忘关键是想清楚哪些信息属于哪个模式、什么时候该留、什么时候该清。这个边界划得清楚AI 应用才有稳定的表现划不清楚再多提示词也救不回来。接下来的方向我打算把隐式切换的意图判断做得更细尝试用用户过去 7 天的模式使用频率做加权预测减少切换延迟。如果你也在做类似的事情欢迎多交流——上下文管理这个领域还远没到定论的时候大概率每一家都有自己的一套土办法互相看看总能多一条思路。