
1. 从“context-mode”这个词说起它到底指什么第一次看到“context-mode”这个标题很多人会愣一下——它不像“XX管理系统”“XX爬虫框架”那样一眼能看出用途。我最初接触这个词是在做对话系统上下文管理的时候当时团队里有人提了一句“把上下文切成 context-mode 来管”我才意识到它其实是一个描述“上下文处理模式”的抽象概念而不是某个具体的库或工具。简单来说context-mode 指的是一套系统在处理“上下文”时所采用的策略与状态组织方式。这里的“上下文”可以是对话历史、请求链路中的环境变量、渲染管线里的状态快照、甚至是编译器在解析代码时维护的作用域信息。而“mode”强调的是同一套系统在不同场景下会切换不同的上下文处理策略每种策略就是一个 mode。它解决的问题很实际当上下文变得又长又杂时如何让系统知道“现在该看哪些信息、该忽略哪些信息、该以什么粒度去组织这些信息”。比如一个客服机器人用户第一句问“我的订单到哪了”第二句问“那能改地址吗”——第二句里的“那”指代的是上一句的订单如果系统没有正确的 context-mode它就会把“那”当成一个孤立词去理解答非所问。适合看这篇内容的人大概有三类一是正在做对话系统、Agent 编排、工作流引擎的开发者二是需要处理长上下文、状态机的后端工程师三是对“上下文工程”这个概念感兴趣、想搞清楚它和普通“传参”有什么区别的技术爱好者。不管你是哪一类下面我会从设计动机、核心机制、落地步骤到踩坑经验一层层拆开讲。提示context-mode 不是某个特定产品的专有名词不同团队对它的实现差异很大。本文讲的是我在实际项目中总结出的一套通用思路你可以把它当成设计参考而不是照搬的规范。2. 为什么需要 context-mode普通传参不够用的三个场景2.1 场景一多轮对话里的指代与省略做过对话系统的人都知道用户说话是“懒”的。第一轮说“帮我查一下北京到上海的航班”第二轮可能只说“那明天的呢”。这里的“那”和“明天的”都依赖上一轮的意图和槽位。如果系统只是简单地把每轮文本拼在一起丢给模型上下文会迅速膨胀而且模型很容易被无关历史干扰。普通做法是维护一个 messages 数组每轮 append 一条。但问题在于什么时候该截断、什么时候该摘要、什么时候该把某些轮次标记为“已解决”不再参与推理——这些决策就是 context-mode 要管的事。没有 mode 的概念代码里就会散落一堆 if-else最后没人敢改。2.2 场景二工具调用链中的环境漂移在 Agent 系统里一次任务可能涉及多次工具调用。比如先查数据库拿到用户 ID再拿用户 ID 去调订单接口再拿订单号去查物流。每一步的“上下文”都在变上一步的输出是下一步的输入但又不是所有输出都需要传递。我见过一个项目把所有工具返回值都塞进一个全局 context 对象结果到第五步的时候context 里已经有几十个字段其中大部分是中间产物。调试的时候根本分不清哪个字段是当前有效的。后来我们引入 context-mode把“采集阶段”“推理阶段”“执行阶段”分开每个阶段只暴露必要的字段问题才收敛。2.3 场景三长文档处理中的窗口管理处理长文档时上下文窗口是硬约束。你不能把整本书塞进去但你又不能随便截断因为截断可能丢掉关键信息。这时候 context-mode 可以定义几种策略滑动窗口模式、摘要压缩模式、关键段落召回模式。不同模式对应不同的取舍——滑动窗口保局部连贯摘要压缩保全局语义召回模式保精准命中。这三种场景的共同点是上下文不是越多越好而是越“对”越好。context-mode 的本质就是一套“上下文治理策略”让系统在不同阶段、不同任务下用最合适的方式组织信息。3. context-mode 的核心机制拆解状态、策略与切换3.1 上下文的三层结构在我自己的实现里我把上下文分成三层这样后面定义 mode 的时候才有抓手原始层raw layer最全的历史记录包括用户输入、工具返回、系统事件。这一层只追加不修改相当于日志。工作层working layer当前推理真正会用到的信息。它是原始层的一个“投影”可能经过筛选、摘要、重排。持久层persistent layer跨会话或跨任务需要保留的信息比如用户偏好、已确认的事实。这一层不随单次任务结束而清空。三层之间的关系是原始层是事实来源工作层是运行时视图持久层是长期记忆。context-mode 主要管的是工作层怎么从原始层和持久层里构建出来。3.2 mode 的定义一组策略的集合一个 mode 本质上是一组策略的绑定我通常用四个维度来描述它维度说明示例选取策略从原始层挑哪些内容进工作层最近 N 轮、关键词命中、工具调用链上游压缩策略对选中的内容怎么压缩原文保留、摘要、只留槽位优先级策略冲突时谁优先持久层事实 最近轮次 更早历史失效策略什么时候丢弃或降级任务完成后清空、超时后摘要把这四个维度组合起来就能定义出不同的 mode。比如“闲聊模式”可能是选取最近 5 轮、原文保留、最近优先、不主动失效“任务模式”可能是选取工具链相关轮次、槽位压缩、持久层优先、任务结束即清空。3.3 切换时机谁来决定用哪个 modemode 的切换不能靠猜必须有明确的触发条件。我实践下来触发条件分三类显式指令用户说“我们换个话题”或者系统检测到意图分类变化。状态变化任务从“信息收集”进入“执行”阶段或者工具调用返回了错误需要回退。资源约束工作层 token 数超过阈值强制切换到压缩模式。这里有个容易踩的坑切换太频繁会导致上下文抖动。我早期做过一个版本每轮都重新评估 mode结果同一段对话里 mode 来回切模型看到的工作层内容前后不一致回答质量反而下降。后来改成“最小驻留轮数”一个 mode 至少维持两轮才允许切换稳定性明显提升。4. 落地实现从零搭一个 context-mode 管理器4.1 数据结构设计先定义核心结构。我用 Python 伪代码示意重点看字段含义class ContextItem: def __init__(self, content, source, timestamp, priority, tags): self.content content # 实际内容 self.source source # user / tool / system / persistent self.timestamp timestamp # 用于时效性排序 self.priority priority # 数值越大越优先 self.tags tags # 用于选取策略匹配如 [order, logistics] class ContextMode: def __init__(self, name, select_fn, compress_fn, priority_fn, expire_fn): self.name name self.select_fn select_fn # (items, state) - selected self.compress_fn compress_fn # (selected) - working_set self.priority_fn priority_fn # (working_set) - ordered self.expire_fn expire_fn # (working_set, state) - working_set这里的关键是把策略做成函数而不是写死在 if-else 里。这样新增一个 mode 只需要注册一组函数不用改核心流程。4.2 选取策略的实现细节选取策略最容易写得太粗。我见过有人直接按“最近 N 条”取结果把系统提示词也截掉了。正确的做法是先按来源分组再在组内按规则取系统提示词永远保留不参与选取。持久层事实全部保留但可以按 tags 过滤。用户输入按最近轮次取但保留被后续轮次引用的那些需要做指代检测。工具返回只保留当前任务链上的已完成子任务的返回可以降级为摘要。指代检测这块简单做法是用关键词重叠如果当前用户输入里的名词短语在历史某轮出现过就把那轮标记为“被引用”。更稳的做法是维护一个实体表记录每个实体最后一次出现的轮次。4.3 压缩策略的取舍压缩不是越短越好。我试过把历史全部摘要成一句话结果模型丢失了细节回答变得很泛。后来改成分级压缩最近 2 轮原文保留。3 到 6 轮保留用户原话工具返回只留关键字段。6 轮以上摘要成“用户曾询问 X已告知 Y”的形式。这样既控制了长度又保住了近期细节。实测下来工作层 token 数能压到原来的 40% 左右而回答准确率只下降不到 5%。4.4 优先级与失效的配合优先级和失效要一起设计。举个例子持久层里有一条“用户是 VIP”优先级很高。但如果当前任务是“查询公开航班信息”这条事实其实无关不应该占工作层空间。所以失效策略里要加一条与当前任务 tags 不匹配的持久层事实降级但不删除。降级意味着它还在原始层需要时可以召回但不默认进工作层。5. 实测中的意外情况与排查链路5.1 问题一mode 切换后模型“失忆”现象从闲聊模式切到任务模式后模型突然不记得用户刚说过的名字。排查过程我先打印了切换前后工作层的内容发现切换时 select_fn 只选了工具相关轮次把用户自报姓名的轮次过滤掉了。根因是选取策略里“按 tags 匹配”写得太严格而用户姓名那条没有打上任务 tag。修复在选取策略里加一条兜底规则——最近 3 轮内的用户输入无论 tags 是否匹配都保留。因为近期用户输入往往包含任务的关键约束不能纯靠 tag 过滤。5.2 问题二压缩后指代关系断裂现象历史摘要成“用户询问了订单”但下一轮用户说“那帮我改一下”模型不知道改什么。排查摘要函数只保留了意图丢掉了实体订单号。而指代检测依赖实体表实体表又依赖原始层——但工作层里已经没有实体了。修复压缩时强制保留实体槽位即使其他内容都摘要掉。具体做法是在 compress_fn 里加一个“实体提取”步骤把订单号、日期、地址这类槽位单独拎出来以结构化形式附在摘要后面。5.3 问题三高频切换导致性能抖动现象每轮都重新计算 mode导致响应时间波动大。排查mode 评估本身不慢但 select_fn 里做了全量历史扫描历史越长越慢。修复给原始层加索引按 tags 和时间建倒排。选取时先查索引缩小范围再在候选集里做精细匹配。另外加上“最小驻留轮数”减少不必要的重算。优化后 P99 延迟从 800ms 降到 220ms。注意排查这类问题时一定要把“工作层实际内容”打日志。很多人只打原始层结果看不出 mode 到底选了什么排查效率极低。6. 几个容易踩的坑与我的应对建议6.1 不要把 mode 和“状态机”混为一谈状态机管的是“系统处于什么阶段”mode 管的是“当前看哪些上下文”。两者有关联但不是一个东西。我见过把两者合并实现的结果状态一多mode 也跟着爆炸。建议分开状态机决定流程走向mode 决定信息视图。6.2 持久层写入要谨慎持久层是跨任务的写进去容易清理难。我的经验是只有用户明确确认的事实才写持久层比如“我的收货地址是 X”。模型推断出来的偏好先放工作层多次验证后再考虑升级。6.3 给每个 mode 配一个“降级模式”任何 mode 都可能遇到异常选取为空、压缩失败、优先级冲突。这时候要有一个保底 mode通常是“最近 N 轮原文”保证系统不至于完全没上下文可用。6.4 测试要覆盖 mode 切换边界单元测试里我专门写了一组“切换序列”测试连续切换 5 次检查每次工作层是否符合预期。这类测试能抓到很多偶发问题比如切换时旧 mode 的缓存没清干净。7. 这套思路还能怎么扩展context-mode 这套东西往小了说是个上下文管理工具往大了说是一种“信息治理”的思维方式。我在实际项目里还把它用在了两个地方一是代码生成助手把项目结构、最近修改、相关文档分成不同 mode生成时按需组装二是运维告警聚合把原始告警、历史相似告警、当前变更记录分层管理减少误报。如果你正在做 Agent 或者对话系统建议先把“工作层”这个概念立起来哪怕一开始只是简单地按最近轮次取也比全量塞进去强。等业务复杂了再逐步引入 mode 和策略函数。别一上来就设计大而全的框架那样很容易过度工程。最后分享一个我踩过的坑早期我试图让模型自己决定用哪个 mode结果模型经常选错而且不可预测。后来改成规则触发为主、模型判断为辅——规则负责硬边界比如 token 超限必须压缩模型只在模糊场景下给建议。这样既保住了可控性又保留了一定的灵活性。