资讯动态

context-mode:用上下文模式切换解决AI任务漂移

发布时间:2026/10/20 21:01:39 来源:尧图企业网站定制
如果你也跟我一样每天要在编码、查日志、写方案、改文档这几个状态之间来回横跳那你大概率遇到过同一个问题同一个AI助手刚才还在帮我调代码转头跟我聊产品设计时说话方式、信息粒度、工具习惯完全没有切换过来。我一度以为是我的Prompt写得不到位后来发现哪怕每次都在开头强调“你现在是代码评审专家”“现在你是运维工程师”效果还是飘忽不定。这个问题的根源不在Prompt本身而在于“上下文机制”助手不清楚当前应该以哪种模式来消费、处理和输出信息。于是我自己动手做了个小项目名字就叫 context-mode。context-mode 本质上是一个轻量的上下文管理框架。它把一次工作会话拆成“模式”每个模式内部用结构化块描述任务目标、边界约束、素材来源、工具权限和输出契约。我最初只打算给自己用后来发现它也可以被封装成一个库嵌入到命令行工具或者编辑器插件里。如果你正在做AI辅助开发工具或者经常跟AI助手在多任务之间来回切换这篇内容应该能给你一个可参考的落地思路。下文我会把设计动机、五种上下文块、实现原理、工作流集成、踩过的坑以及使用边界都讲透。1. 先说说我为什么非得自己写一个“上下文模式”1.1 光调 Prompt根本解决不了“任务漂移”我先讲一个具体场景。某一天下午我让AI助手帮忙排查一个接口超时问题来回几轮之后终于定位到是连接池配置不对。这个过程里助手一直在给我输出堆栈、日志片段、排查步骤说话风格非常“诊断”。修完这个问题我紧接着让它重构同一个模块里的一个函数结果它开口就说“这个函数的异常处理看起来不够严谨建议先加日志再观察”。明明我是要重构它却还在用排错模式跟我说话这就是典型的任务漂移。一开始我以为是我临时指令没写清楚于是就在每次提问前加一句“以下是一个重构任务请给出重构方案”。但这只能管住前一两轮一旦对话变长模型的输出还是会慢慢滑回之前的模式。更麻烦的是排错过程中产生的日志和诊断信息残留在上下文里重构建议会被这些无关信息干扰。我试过开新会话可是新会话又把已经查清楚的问题背景给丢了AI要重新理解整个项目浪费大量时间。后来我意识到我需要的不只是更长的Prompt而是一套能够显式切换“思考方式”的机制。系统提示词只是告诉AI“你是谁、该干什么”但真实工作里不同任务连信息输入类型都不一样。代码修复要读diff和测试输出文档写作要读接口定义和用户反馈运维排查要看日志和监控数据。如果这些信息一直混在一起再好的系统提示也扛不住。1.2 我想要的“模式切换”不是把预设 Prompt 复制粘贴那段时间我研究过很多人的做法最常见的是准备一堆System Prompt模板写代码用一套写文档用另一套出了问题再换一套。这个思路方向上是对的但存在两个硬伤。第一信息是重复的。每套模板都要包含基本的行为约束比如“不要编造信息”“不要做破坏性操作”“回答要简洁”随着模板越来越多维护成本会指数级上升。改一个通用规则要把所有模板都翻一遍。第二模板之间是孤立的。代码修复和分析问题其实共享大量背景知识可两个模板各自带一份背景切换一次就好像把整个对话重置了一次之前的判断结论全丢了。我想要的模式切换应该是把一个任务涉及的东西都打包成一个“胶囊”。这个胶囊里面不仅包含一段系统提示还包括这个任务允许调用什么工具、需要参考哪类素材、输出格式是什么、有哪些红线不能碰。切换到某个胶囊AI就自动进入对应的工作状态切换回来上一个任务的关键结论还能继续用。context-mode 就是按照这个思路做的。1.3 谁适合读这篇内容简单说一下适用范围。如果你只是偶尔让AI帮忙写一小段代码那用不着搞这么复杂一条清晰的指令可能就够了。但如果你是下面这几类人会非常有共鸣一是做AI辅助开发工具、Agent框架或者命令行助手的开发者想给你的工具增加“模式感知”能力二是日常深度使用AI编程助手经常在多任务之间切换被上下文混乱折磨得不行的工程师三是在做知识库、文档工具希望AI能根据用户的当前场景自动调整输出风格和目标的人。这篇文章里的所有设计都是公开、可复用的。我不会把它包装成某个神奇框架更像是一份踩坑记录和设计说明你可以照着自己的项目去改。2. context-mode 的核心设计五种上下文块怎么组合2.1 为什么选“块”而不是“多套完整 Prompt”在设计数据结构的时候我先列了一堆需求然后发现如果每个模式都存一份完整的系统提示词写起来会非常冗长而且百分之七八十的内容是重复的。比如“不要捏造信息”“不要执行危险命令”这类基础约束每个模式都得写一遍。所以我把模式拆成了五个上下文块让不同模式共享公共块只覆盖差异部分。这个思路其实很像前端工程里的组件拆分基础能力抽成公共组件业务差异用参数注入。上下文块也是一样base块是所有人共用的底座task块定义具体模式的目标constraints块限死边界knowledge块描述素材来源tools块约束行为手段output块规定交付物。模式之间通过继承和override方式来组合新增一个模式往往只需要写十几行差异配置。2.2 五类上下文块的具体职责我用表格把这五个块讲清楚这是整个设计的核心。块名管的是什么问题具体包含什么task 块明确“为什么做”模式名称、任务分类、目标描述、触发关键词constraints 块明确“不能做什么”技术红线、安全边界、代码风格、覆盖优先级knowledge 块明确“基于什么做”参考文件、git diff、日志路径、历史窗口长度tools 块明确“用什么手段做”允许调用的命令/接口、禁止执行的操作output 块明确“交付成什么样”输出格式、章节结构、语言风格、是否需要代码示例task块是最容易被忽略的一层。很多人给AI写指令只写“做什么”却不写“当前处于什么阶段”。我给 task 块加了一个phase字段标记这是探索阶段、执行阶段还是验证阶段。同样是修 bug探索阶段的输出应该是一份诊断报告执行阶段的输出应该是一份改动方案。没有这个字段AI很容易把探索和交付混在一起。constraints 块的优先级一定要定义清楚。我见过很多配置里约束写得密密麻麻结果一执行就发现自相矛盾。现在我的规则是块的优先级高于普通对话output 块高于 constraints 块constraints 块的内部再按“安全 架构 代码风格”排序。这个顺序是在实际踩坑之后定下来的后面会细说。2.3 一个模式到底长什么样我用一个实际的code-fix模式来展示。这是最常见的一个模式负责“修复指定缺陷并保持既有代码风格”它的配置大致如下mode: code-fix inherit: [base] task: category: coding phase: execution goal: 修复指定缺陷保持既有代码风格不做无关重构 trigger_keywords: [bug, 报错, 单测失败, 卡住, 不生效] constraints: guardrails: - 不改变对外 API - 不删除未标注的废弃注释 - 不执行破坏性操作比如 drop table、rm -rf override_priority: security architecture style knowledge: sources: - $repo_path - $git_diff - $latest_test_output history_window: 120 tools: allow: [git, find, grep, pytest, go test] deny: [format, migrate, rebuild] output: contract: | 1. 问题结论 2. 改动文件清单 3. 每个文件的具体改法 4. 需要人工确认的疑点注意这份配置不是直接念给模型听的Prompt而是给“提示组装器”和“意图路由器”用的形式化定义。运行时系统会基于这份配置生成两样东西第一样是给大模型的系统提示片段第二样是一套用于判断该模式的信号列表比如关键词、目录匹配规则、允许命令列表。inherit: [base]是这个设计里最省钱的部分。base 模式里面放着所有人都得遵守的底线规则比如“禁止编造数据”“不确定时直接说不确定”“回答不超过 N 字”。code-fix模式只需要写自己特别的部分。我现在项目里有二十多个模式但每个模式的配置基本都是几十行到一百行左右没有一眼望不到头的巨型提示词。2.4 模式之间的继承与覆盖模式只有继承还不够还得想清楚覆盖规则。最常见的问题是子模式想改父模式的某个约束但改不动最后只能把父模式整个拆了复制一遍。我在 context-mode 里用的是“按字段名覆盖”的策略。比如 base 里面有一条history_window: 50code-fix 模式把它覆盖成 120这用的是同一个字段的简单替换。但有些字段是列表比如deny命令列表父模式写了[format]子模式又写了[format, migrate]如果直接合并就会多出父模式根本没有的限制。我最终的方案是给列表字段也加上操作符append、remove、replace默认是append这样既保留了父模式的基线又不妨碍子模式收紧约束。还有一个设计细节值得提继承关系不能太深。一开始我搞了三层继承base 继承 common然后 common 再继承 default结果查一个约束到底生效没有要翻三个文件。后来我把继承深度压到最多两层超过两层的场景就把公共部分抽成一个新块用组合代替继承。这一条建议强烈保留以后你就知道为什么了。3. 实现原理从 Prompt 拼装到状态机路由3.1 意图识别怎么决定切到哪个模式模式配置写好了下一个问题就是用户说了一句话系统怎么知道当前应该切到哪个模式我最初的做法非常粗暴拿trigger_keywords做关键词匹配命中哪个模式就切过去。结果发现完全不行因为用户说的话往往很模糊。比如“这个接口好慢”既可能是性能优化模式也可能是排错模式。后来我改成三路信号综合判断关键词信号消息里的显式词汇比如“报错”“单测失败”“帮我看下日志”按命中数量加权。上下文信号当前所在目录、最近打开的代码文件、最近的 git 状态。比如 open 的文件是一个_test.go那“失败”就更可能指向单测修复。主动切换信号用户直接执行了切换命令比如我输入context-mode switch doc-writer那就不用猜了无条件切换。三路信号最终加权算出得分得分最高的模式会成为候选但如果得分和第二名的差距小于一定阈值系统就会输出一个问题让用户确认“你是在修 bug还是在做代码审查”这样避免在模棱两可的指令下切错模式。比较值得注意的一个点模式的恢复时机。用户不会每句话前面都带一个“进入排错模式”的标记更多时候是在当前模式里连贯地做一系列操作。所以我给每个模式加了一个“有效期”默认是任务没结束就一直在当前模式只有出现高得分的新模式信号才弹出切换提示。这样既不会频繁误切换也不会因为连续对话而把模式感丢失掉。3.2 状态保存与恢复跨会话的任务记忆模式切换最怕的就是“切过去回来什么都不记得”。为了处理这个我在 context-mode 里设计了一个task memory层按模式粒度保存上下文摘要。实现上很简单每当进入一个模式系统会把与该模式最相关的信息压缩成一个摘要写入本地状态文件当切回这个模式时摘要会被重新拼装进提示词。比如我在 analysis 模式里查清了超时原因是连接池配置切到 code-fix 模式改代码切回 analysis 模式时系统会把“连接池配置导致的超时”这段摘要带回来而不是把当时所有原始日志都重新加载一遍。这里有个细节摘要不能只存结论还要存“为什么”。我吃过亏只把结论存进去比如“问题根因是连接池太小”结果切回来看上下文感觉像在看博客的摘要完全想不起当时为什么得出这个结论。后来每个摘要强制带上这个结构task_id: issue-123 mode: analysis conclusion: 连接池最大连接数太小导致高峰时新请求排队 evidence: - 线程池等待队列最多同时 40 个 - 平均等待时间在 15:30 之后超过 3s next_action: 把 maxPoolSize 改成 128 并压测验证这样切回来的时候AI 不仅能知道结论还能沿用当时的证据链不会重新推导一遍也不会得出不一样的答案。3.3 最小可运行实现示例我用 Python 做了个最小实现结构很直接一个引擎负责维护模式列表一个路由器负责决策一个组装器负责生成提示词。核心代码贴出来from dataclasses import dataclass, field dataclass class ContextMode: id: str inherit: list[str] field(default_factorylist) task: dict field(default_factorydict) constraints: dict field(default_factorydict) knowledge: dict field(default_factorydict) tools: dict field(default_factorydict) output: dict field(default_factorydict) class ContextEngine: def __init__(self): self.modes: dict[str, ContextMode] {} self.current_mode: ContextMode | None None self.memory: dict[str, dict] {} def register(self, mode: ContextMode): self.modes[mode.id] mode def switch_to(self, mode_id: str): if mode_id not in self.modes: raise ValueError(funknown mode: {mode_id}) if self.current_mode: self.memory[self.current_mode.id] self._snapshot() self.current_mode self.modes[mode_id] def build_prompt(self, user_input: str) - str: mode self.current_mode pieces [] pieces.append(self._resolve_block(constraints, mode)) pieces.append(self._resolve_block(task, mode)) pieces.append(self._resolve_block(knowledge, mode)) pieces.append(self._resolve_block(tools, mode)) pieces.append(self._resolve_block(output, mode)) memory self.memory.get(mode.id, {}) if memory: pieces.append(self._format_memory(memory)) pieces.append(user_input) return \n\n.join(pieces) def _resolve_block(self, block_name: str, mode: ContextMode) - str: inherited [self.modes[name] for name in mode.inherit] final_block {} for parent in inherited: final_block.update(getattr(parent, block_name)) final_block.update(getattr(mode, block_name)) return yaml.dump(final_block, allow_unicodeTrue)这段代码省略了很多边界处理比如覆盖列表的 append/replace 操作但核心思路已经很清楚切模式前快照当前记忆切过来后把新模式的所有块拼进 Prompt。实际使用中我还会在build_prompt里把当前 git diff、最近测试日志等按 knowledge 块里的 sources 配置动态读取出来再拼进去这样 AI 每次拿到的是最新现场而不是静态快照。3.4 它和普通状态机的区别如果你是后端出身可能会说这东西不就是一个状态机吗。确实它有状态机的外形有状态集合、有切换动作、有状态记忆。但跟普通状态机有两个明显差异。第一个差异是“状态内携带的资源量”。普通状态机一个状态通常只保存一个枚举值或者几行数据而 context-mode 的每个状态携带了任务描述、约束条例、动态素材引用、工具权限、输出协议相当于一个状态本身就是一个小型工作区。第二个差异是“切换的决策方式”。普通状态机靠明确事件触发比如click_event、error_event而 context-mode 的触发信号是自然语言加上下文加权只在置信度高的时候才自动切换低置信度时回退到人工确认。所以更准确的说法是context-mode 是一个“上下文状态机”状态节点很重转移条件由AI意图识别驱动。理解这一点后你设计插件或 Agent 的时候就有个清晰框架模式定义是节点切换逻辑是转移函数记忆层是会话存储。4. 在真实工作流里接进 CLI 和编辑器4.1 CLI 入口设计与常用命令框架做出来之后我第一件事是给它做一个命令行入口。原因很简单我的大部分操作都发生在终端里一个命令能完成模式切换比扔进配置文件再重新加载顺手得多。实际命令只有四个分别是switch、status、attach、run。context-mode status用来查看当前模式和最近快照context-mode switch analysis是手动切模式context-mode attach issue-123是把当前工作目录关联到一个任务 ID 上之后所有上下文摘要都会挂到这个 ID 下面context-mode run 帮我看一下这个报错是直接在当前模式下执行一句话相当于一个包装好的交互入口。context-mode switch analysis context-mode status context-mode attach issue-123 context-mode run 找出这个接口超时的根因我实际用下来attach命令最容易被忽视但使用频率最高。没有它上下文摘要就像一盘散沙有了它同一个任务的多轮操作会被完整地串起来。比如今天查了一遍没查完明天回来执行context-mode attach issue-123所有相关摘要和历史依据都在可以无缝继续。4.2 与代码编辑器的联动终端命令虽然方便但大多数工程师的主战场还是代码编辑器。我给 context-mode 写了一个很小的编辑器插件通过一个全局快捷键呼出模式切换面板作用就是把switch命令的映射做到编辑器里面。联动逻辑不复杂核心是两件事编辑器把当前打开的代码文件路径告诉 context-modecontext-mode 根据路径推测当前代码的类型和可能模式编辑器再把用户主动选择的模式回传给 context-mode 做强制切换。很多从命令行切到编辑器的用户问我为什么不用 LSP 这种重型协议我的回答是对于上下文模式切换这种低频事件一个轻量的 socket 或者回调钩子已经绰绰有余别为了一个切换动作把整个插件体系都搞复杂。比较需要注意的是模式状态一定要做成用户可感知的。我在编辑器状态栏放了一个MODE: code-fix的提示一开始有人觉得多余后来大家发现如果不在界面上看到当前模式很快就忘了自己处于什么状态容易拿着排错模式去做重构。最好的设计是你不用想就知道自己在哪里。4.3 实际跑一个任务从 issue 描述到代码改动我用一个真实的任务把流程串一遍。某天我接到一个 issue说“详情页偶尔加载很慢怀疑是缓存问题”。如果按老方法我直接把这行字丢给 AI它大概率会开始分析缓存策略。但在 context-mode 下流程完全不一样我执行context-mode attach cart-page-slow把这个任务挂到独立上下文。系统根据关键词“加载很慢”“偶尔”和当前打开的页面代码自动建议analysis模式。我确认。在 analysis 模式下我先让它跑一个诊断脚本拿到接口耗时数据定位到“首屏图片懒加载失效”这个根因。定位后我执行context-mode switch code-fix。系统把 analysis 模式的摘要和根因自动带过来。code-fix 模式里AI 只针对懒加载失效做改进不会顺手改掉其他代码。因为它有constraints块约束“不做无关重构”。改完代码我context-mode switch review让 AI 做一次只读审查检查我有没有引入新问题。整条链路下来每个模式都只做自己该做的事上一个模式的结论还是下一个模式的输入。这就是 context-mode 真正提升效率的地方不是让 AI 更聪明而是让 AI 的输出始终处在正确的任务轨道上。4.4 多人使用时的共享模式库个人用得顺手了之后我自然就想到了团队协作。于是我把所有模式抽成了一个共享配置仓库每个人可以克隆一份到本地也可以在本地覆盖自己的偏好。共享库要做两件事一是版本管理二是差异化覆盖。版本管理我用的是普通配置文件对比每次改模式之前先看一眼 diff避免有人悄悄改了什么基础约束导致其他人行为突然变化。差异化覆盖设计成覆盖文件就是每个人都有个user_overrides.yaml只允许改动非安全类的字段安全字段比如“禁止执行破坏性命令”必须由共享库统一维护。这里要提一个经验共享库里的模式必须删除“个人偏好型”内容。比如某个人喜欢让 AI 输出中文注释这种偏好不应该写进团队模式否则其他人用起来会觉得 AI 被“预设人格”绑架了。模式库要做的只是任务边界和输出契约各人口吻、语气偏好放到自己的覆盖层里。5. 踩坑实录这些设计教训让我改了三版5.1 坑一模式过细反而让人不想用第一版 context-mode 我做了三十多个模式粒度细到“重构代码”“重构配置”“重构测试”还有人嫌不够打算把“重构代码”再拆成“重构函数”和“重构类”。结果用下来发现模式越多切换成本越高最后大家都不愿意切换了直接用默认模式对待所有问题。这个教训让我意识到模式的定义粒度要跟“认知切换成本”匹配。如果两个任务之间的差异仅靠一段指令就能说清那就不应该拆成两个模式。模式之间至少要在“工具权限”或“输出格式”上有本质差异才值得单独存在。后来我把三十多个模式收敛成八个核心模式剩下的靠知识块和约束块微调体感反而更轻。还有一点给模式起名字要像给 API 起名字一样严格。第一版里有code-fix、bug-fix、fix-code三个名字不仅我分不清模型也分不清意图识别经常误判。后来统一成fix、analysis、review、docs这样一组语义清晰的动词识别准确率提升非常明显。5.2 坑二忽略“真实环境”模式沦为摆设早期版本接入编辑器之后我发现一个尴尬现象模式切换过去了但 AI 拿到的还是静态提示词完全没有读取当前项目的真实状态。比如切到fix模式它不知道当前 git 分支改了哪些文件也不知道最近一次测试失败在哪切到analysis模式它拿不到最新的日志。这样的模式就像一个空的文件夹只有壳没有肉。后来我强制给每个模式加上了knowledge.sources配置规定只有真正能拉取到现场数据的模式才允许注册。不能为了一个概念式的模式去拼一堆假数据源。可以把这想象成每一个模式都应该自带一张“任务命令”它在启动时要完成一组特定的数据装载动作。没有这个装载动作模式切换不如直接改系统提示词来得有效。5.3 坑三历史记录无限增长切换反而变慢另一个让我很头疼的问题是上下文臃肿。只要在某个模式里对话超过一定轮数系统提示词就会被历史摘要撑爆。我最初实现“任务记忆”的时候没有上限结果是一个任务做了一天到晚上时候光是拼装记忆片段就占了四五千 tokenAI 开始变得很慢还频繁引用一些过时的中间判断。解决方案有两个一个是给记忆块加 token 预算超出预算自动从最不重要的信息开始截断另一个是每轮对话之后不再保存全部历史而是只保存“结论、证据、下一步行动”这个三元组。很多长对话其实就靠这三个键值就能恢复工作状态那些“中间试探的内容”属于噪音可以直接舍弃。5.4 我沉淀下来的四条铁律项目做了三轮迭代之后我把经验浓缩成四条规则每次加新模式之前都会对照一遍。模式是给人用的不是只给模型用的。配置必须人类可读注释要写清楚不能为了炫技用一堆难懂的缩写。所有字段都要支持覆盖上下文不能锁死。任何一行配置都可能因为新的业务场景需要被局部调整支持覆盖才能让人愿意维护它。历史窗口必须按模式独立裁剪。排序、分析、修复三种模式对历史的需求完全不一样没必要把所有模式都塞进同一个上下文长度限制里。模式切换必须给出可见信号。无论是什么载体CLI 还是编辑器状态栏一定要让用户随时看到当前处于哪个模式否则很快就迷失了。6. 什么时候不值得用 context-mode6.1 简单任务不要硬套模式我虽然做了这个项目也很喜欢它但我必须诚实并不是所有场景都适合使用 context-mode。如果你只是让 AI 写一个快速排序函数或者翻译一段英文或者随口问一个概念直接说一句完整清楚的话就够了多引入模式层反而是负担。判断标准可以看三个条件这个任务是否跨越多个阶段探索、执行、验证是否需要在不同工作模式之间来回切换是否需要保留跨会话的任务记忆。三个条件至少占了两个才值得用 context-mode。如果只占一个优先用更轻的 Prompt 模板甚至不需要模板。6.2 低工具的消耗与建议那一个普通开发者应该怎么起步我的建议是不要一上来就做一个完整的状态机框架。先从一个配置文件加一个 CLI 命令开始把两三个最常用的模式定义出来比如fix、analysis、docs。用一段时间体会一下模式切换带来的差别觉得有用再往编辑器插件、共享库、动态工具权限这些方向扩展。另外模式库要经常清理。每个月抽一点时间删掉那些已经一个月没被用过的模式与其留着发霉不如删掉让整体配置保持轻盈。项目本身的价值在于“时刻知道自己在做什么”这一点需要保持住。6.3 还可以往哪些方向继续进化最后聊聊我脑子里还没完全想清楚的演进方向。一是把多模态信息也纳入上下文块比如截图、录屏、终端选择的文本片段让模式实体可以动态关联非文本素材。二是让模式自己学会演化比如系统从历史使用的模式里统计出个人习惯主动推荐一个更细分的模式。三是把团队共享模式库往“语义化”方向推把模式当成可组合的语义层而不是一份写不动的 YAML 文件。我在实际使用中最深的体会是AI 工具好不好用很多时候不在于模型本身强不强而在于我们有没有办法精准地描述“当前到底该怎么做”。context-mode 解决的问题就是这件事。它把模糊的对话状态变得结构化把“告诉 AI 该干什么”变成一种可维护、可共享、可迭代的东西。如果你现在正被同样的上下文切换问题困扰不妨从两三个模式开始搭一个小框架别贪多慢慢你会发现自己和 AI 之间的协作变得清晰很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑