尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

像管理代码一样管理AI上下文:context-mode实战指南

像管理代码一样管理AI上下文:context-mode实战指南 如果你也和我一样每天在 AI 编程助手前面反复粘贴项目背景、技术栈约定、文件路径然后过十分钟发现它又开始答非所问那这篇内容应该能帮你省下不少时间。我最近把一套叫context-mode的上下文管理模式做成了一个小工具专门用来解决“AI 记不住上下文”这个老毛病。它的核心思路很简单把上下文当成代码一样管理——分文件、定作用域、按优先级合并、能回滚、能复用而不是每次开新会话都从零开始口述一遍。这篇文章会完整拆解我为什么做这个东西、它解决了什么问题、核心设计怎么来的、关键代码怎么落地以及我在实际使用三个月之后踩过的坑。无论你是在折腾 AI 辅助编程还是纯粹对“如何管理给模型的信息”这件事感兴趣都可以参考我的方案然后自己做一套顺手的东西出来。1. 为什么要把上下文管理当成一个正经工具来做1.1 我在 AI 编程里反复踩的“失忆”问题先说说我最初遇到的场景。我每天的工作流里有一大半时间是在和 AI 助手结对写代码。它的使用方式很简单开新会话粘贴需求它写代码我 review再让它改。问题就出在“开新会话”这几个字上。每次新开一个会话我都得重新告诉它项目的技术栈是什么、代码入口在哪、数据库模型放在哪个目录、接口返回结构有什么约定、哪些文件改不得。这些话我一天要说上好几遍而且说得并不短。哪怕我很有耐心地把项目背景写清楚聊到第二轮第三轮它还是会出现一种很典型的情况用错了接口名、改了不该改的公共模块、或者把上一次会话里已经否定的方案又重新提出来。最让我崩溃的一次是我让 AI 优化一个 FastAPI 接口的性能第一轮给足了背景信息它也给出了正确的优化思路。结果我在同一个会话里接着问了几个不相关的问题把它的注意力带偏之后再让它继续优化它居然开始引用一个早就不存在的旧路由名称。我回头翻聊天记录才明白最初的背景说明早就滚出了它的上下文窗口模型只能靠当前这段对话里的零散信息去猜。这个经历让我意识到一件事不是 AI 变笨了是我的输入组织方式有问题。它的上下文窗口是有限的如果我不能把最重要的信息稳定地放在合适的位置它就会在长对话里慢慢“失忆”。1.2 不加管理的上下文为什么会越写越偏这里有个概念叫上下文漂移context drift我是在反复翻车之后才真正理解它的。简单说模型在长对话里的注意力会被最近的几轮消息牵引最初设定的规则如果一直没有被强化它的影响力就会越来越弱。就像你给一个新同事入职当天讲了一遍项目概况他没记住后面干了两周所有操作都基于头一天的错误理解又没人及时纠正于是越偏越远。还有一个更隐蔽的问题中间环节的“错误假设”。比如 AI 在某个瞬间以为某个配置文件是放在根目录的后面所有回答都会默认这个结构。如果你没有在每一轮对话里把正确路径重新贴一遍它根本不会意识到自己错。所以很多人的体感是“AI 聊天可以干活不靠谱”本质上是因为干活需要的上下文比较长而大多数人根本没在管理这个东西。我在意识到这一点之后开始尝试各种土办法把项目背景写在一个固定的笔记里开新会话时手动复制粘贴把常用说明做成占位符写进我的输入法快捷短语甚至试过给 AI 助手添加一个固定的“开场白”模板。这些办法都有点用但都很零碎。真正让我下决心做context-mode的是一个常见的需求组合我需要同时维护多个项目每个项目有自己独立的背景知识而我又经常在项目之间来回切换。指望靠手动复制粘贴来管理这些内容既不现实也一定会出错。1.3 context-mode 到底要管哪些东西我最终设计的context-mode并不是简单地把“整段聊天记录保存下来”而是把需要交给 AI 的信息分成三类来管理。第一类是稳定的背景包括项目描述、技术栈、代码目录结构、接口风格约定、团队偏好等等。这类信息变化很慢一个月可能都不会变一次。第二类是可变的目标就是当前正在做的具体任务比如“优化 /api/tasks 接口的响应时间”以及验收标准“QPS 提升 20%不能改变返回结构”。这类信息可能几个小时就要换一次。第三类是临时的经验是我在项目过程中总结出来的一些教训比如“这个模块有历史包袱不要动”“那个接口的字段命名容易混淆记得看清楚”。这三类信息如果混在一起AI 就会被大量无关的历史信息干扰。context-mode做的事情就是把它们分到不同的作用域里按规则组装每次只给模型当前最需要的那一份。2. context-mode 的核心设计上下文种子、作用域与合并策略2.1 上下文种子把背景、约束、偏好固化成文件我把基本的管理单元叫“上下文种子”英文就是 context seed。为什么叫种子因为和种子的性质很像体积很小但包含了一棵完整植物的所有关键信息。只要在每次新会话时把它种下去后面长出来的对话就是稳定、符合预期的。一个种子就是一个 TOML 文件里面用[context]字段组织内容。下面是我全局配置的一个简化例子# ~/.config/context-mode/base.toml [meta] name global-default [context] persona 你是一名资深 Python 后端工程师。回答问题直接先给结论再解释原因。 style 使用中文回答。代码使用 Python 3.11 语法必要时给出完整可运行示例。 output_format 1. 先总结本次需要的改动 2. 按文件分组列出具体修改点 3. 不要输出与任务无关的内容 4. 长段代码直接给出文件路径和关键片段不要贴全文。 这个文件只放“我对所有项目通用”的偏好不会出现任何具体项目的信息。真正属于某个项目的内容必须放到那个项目自己的目录里去。2.2 作用域与优先级全局、项目、任务怎么叠加context-mode把上下文分成三个作用域全局、项目、任务。对应关系如下作用域存放位置内容举例合并优先级全局~/.config/context-mode/base.toml角色设定、通用回答风格、输出格式要求最低项目项目根/.context/project.toml项目描述、技术栈、代码结构、禁止事项中任务项目根/.context/tasks/任务名.toml本次目标、验收标准、关注点最高优先级的设计逻辑很直白任务级信息离“当下”最近最应该被模型重视项目级信息是任务发生的环境全局信息则是兜底的通用偏好。当它们之间出现冲突时优先级高的覆盖优先级低的。举个例子。全局种子说“使用中文回答”项目种子说“代码注释使用中文对外文档使用英文”任务种子说“本次只需要写代码注释不需要写文档”。合并之后模型实际接受的指令就是本次用中文写代码注释文档部分忽略。三级叠加之后每一条都是具体的、可执行的而不是一堆互相矛盾的要求。我特别强调一点不要把项目信息写进全局文件。很多人在实际使用中最容易犯的错就是图省事在全局配置里塞了几个项目的描述结果切换项目时 AI 脑子里全是上一个项目的背景输出直接跑偏。全局文件就只能放“放之四海而皆准”的东西。2.3 合并策略两条上下文冲突时听谁的合并不是简单地把三段文本拼起来而是要做“字段级别的合并”。同一字段存在多份时高优先级覆盖低优先级不同字段之间则直接叠加保留。我简化一下合并的规则先按优先级对种子排序然后遍历每个种子的[context]字段。如果某个键是之前出现过的用新的值替换如果没有出现过就直接追加。这样最终产出的是一段无重复、无矛盾的完整提示词。再补充一类特殊字段文件引用指令。我从实践里发现与其把整个 README 或整个代码文件的内容塞进上下文不如告诉模型“你应该先读哪些文件”让 AI 编程助手自己去读。这就是种子里include和exclude两个字段的价值。# .context/project.toml [context] project_description 当前项目是一个基于 FastAPI 的任务调度服务。 技术栈Python 3.11 / FastAPI / Redis / PostgreSQL。 代码入口在 app/main.py自定义业务逻辑集中在 app/services/ 下。 include [ app/routes/tasks.py, app/services/scheduler.py, README.md, ] exclude [ migrations/, tests/temp/, ]合并时exclude优先于include即使某个文件同时出现在两个列表里只要被 exclude 命中模型就不该去碰它。这个规则的现实意义很大比如你不想让 AI 在重构任务里去读迁移文件几万个文件扫描起来费 token 不说还容易产生危险的建议。3. 手把手实现一个可用的 context-mode 命令行工具3.1 技术选型为什么用 Python Click做这个工具我第一反应是选 Python理由很实际核心逻辑只是“读 TOML、合并字典、拼字符串、复制到剪贴板”这种 IO 加文本处理的任务Python 写起来最快。Python 3.11 以后标准库自带tomllib解析 TOML 不再需要第三方依赖这又少了一个安装负担。命令行框架我用的是 Click它已经非常成熟写子命令、处理参数、输出帮助信息都很顺手比手撸argparse干净得多。如果你问我为什么不用 Node 或 Go我的答案也简单它们都完全能做但不是最省事的。Node 那边你需要额外引toml解析库和剪贴板库Go 编译出来虽然是个零依赖的二进制很酷但开发调试成本比 Python 高。这种小工具开发效率才是第一位的。唯一需要注意的是 Windows 环境的剪贴板操作原生 API 在不同版本上有些不一致。我的处理方案是直接用pyperclip库它内部帮你适配了各种平台实际用下来很稳。项目源码的结构我放在下面方便你直接参考。3.2 目录结构与核心代码我的项目目录大概是这样的context-mode/ ├── ctx/ │ ├── __init__.py │ ├── loader.py │ ├── merger.py │ └── cli.py └── pyproject.tomlloader.py负责按作用域加载所有种子文件核心代码如下# ctx/loader.py from pathlib import Path import tomllib from dataclasses import dataclass dataclass class ContextSource: scope: str # global / project / task priority: int # 合并优先级数值越大越靠前 path: Path # 源文件路径 data: dict # 解析后的 TOML 数据 class ContextLoader: def __init__(self, project_root: Path | None None): self.project_root project_root or Path.cwd() self.global_dir Path.home() / .config / context-mode self.task_name None def set_task(self, task_name: str): self.task_name task_name def load_all(self) - list[ContextSource]: sources [] pair_list [ (global, 1, self.global_dir / base.toml), (project, 2, self.project_root / .context / project.toml), ] if self.task_name: task_path ( self.project_root / .context / tasks / f{self.task_name}.toml ) pair_list.append((task, 3, task_path)) for scope, priority, path in pair_list: if path.exists(): with open(path, rb) as fp: data tomllib.load(fp) sources.append( ContextSource( scopescope, prioritypriority, pathpath, datadata, ) ) return sourcesmerger.py负责合并去重输出最终的纯文本提示词# ctx/merger.py from ctx.loader import ContextSource def merge_context(sources: list[ContextSource]) - str: # 1. 按优先级排序 ordered sorted(sources, keylambda s: s.priority) # 2. 字段级合并相同键用高优先级覆盖 merged: dict[str, str] {} for src in ordered: ctx src.data.get(context, {}) for key, value in ctx.items(): if isinstance(value, str): merged[key] value.strip() # 3. 组装 include/exclude 为指令 has_include any( include in src.data.get(context, {}) for src in ordered ) instructions [] for src in ordered: ctx src.data.get(context, {}) if ctx.get(exclude): instructions.append( 禁止查看或修改以下路径 、.join(ctx[exclude]) ) if has_include and ctx.get(include): instructions.append( 建议优先阅读以下文件 、.join(ctx[include]) ) # 4. 拼装为分段提示词 blocks [] for key, value in merged.items(): if key in (include, exclude): continue block f【{key}】\n{value} blocks.append(block) if instructions: blocks.append(【文件范围】\n \n.join(instructions)) return \n\n.join(blocks)cli.py则是命令入口# ctx/cli.py import click import pyperclip from pathlib import Path from ctx.loader import ContextLoader from ctx.merger import merge_context click.group() def ctx(): context-mode: 像管理代码一样管理 AI 上下文。 ctx.command() click.argument(task, requiredFalse) def use(task): 切换上下文模式。指定 task 表示只加载当前任务上下文不指定则只加载全局项目上下文。 loader ContextLoader(project_rootPath.cwd()) if task: loader.set_task(task) sources loader.load_all() if not sources: click.echo(没有找到任何上下文种子文件请先创建 base.toml 或 .context/project.toml) return text merge_context(sources) pyperclip.copy(text) click.echo(f已合并 {len(sources)} 个上下文源并复制到剪贴板。) for s in sources: click.echo(f - [{s.scope}] {s.path}) ctx.command() def status(): 查看当前目录下能加载到哪些上下文源。 loader ContextLoader(project_rootPath.cwd()) sources loader.load_all() if not sources: click.echo(当前目录未发现上下文种子。) return for s in sources: ctx_keys list(s.data.get(context, {}).keys()) click.echo(f[{s.scope}] {s.path}) click.echo(f 包含字段: {, .join(ctx_keys)}) if __name__ __main__: ctx()这套代码已经够我日常使用了。use命令负责把合并好的上下文复制进剪贴板我直接粘贴给 AI 助手就行status命令帮我快速确认当前项目里到底配了哪些种子排查问题时会用。3.3 配置模板与使用示例用一个实际的终端会话演示整个过程会更清楚。$ cd ~/work/task-scheduler $ ctx status 当前目录未发现上下文种子。第一次进来项目还没有任何配置。我手动建一个$ mkdir -p .context/tasks $ vim .context/project.toml $ vim .context/tasks/perf-fix.toml然后切到任务上下文$ ctx use perf-fix 已合并 3 个上下文源并复制到剪贴板。 - [global] /home/me/.config/context-mode/base.toml - [project] /home/me/work/task-scheduler/.context/project.toml - [task] /home/me/work/task-scheduler/.context/tasks/perf-fix.toml此时剪贴板里已经有了一段完整的分段提示词大致长这样【persona】 你是一名资深 Python 后端工程师。回答问题直接先给结论再解释原因。 【style】 使用中文回答。代码使用 Python 3.11 语法必要时给出完整可运行示例。 【output_format】 1. 先总结本次需要的改动 2. 按文件分组列出具体修改点 3. 不要输出与任务无关的内容 4. 长段代码直接给出文件路径和关键片段不要贴全文。 【project_description】 当前项目是一个基于 FastAPI 的任务调度服务。 技术栈Python 3.11 / FastAPI / Redis / PostgreSQL。 代码入口在 app/main.py自定义业务逻辑集中在 app/services/ 下。 【task_focus】 本次任务只关注 /api/tasks 接口的响应时间优化。 【文件范围】 建议优先阅读以下文件app/routes/tasks.py、app/services/scheduler.py、README.md 禁止查看或修改以下路径migrations/、tests/temp/把这段内容直接贴给 AI 助手作为新会话的第一条消息刚才那个“失忆”的问题基本就不会再出现了。这是我实际使用中效率提升最明显的地方原来每次开新会话要花三五分钟重新交代背景现在一条命令加一次粘贴十秒钟搞定。4. 实际使用三个月后的踩坑记录与细节调优4.1 上下文越堆越长token 预算与裁剪策略用了一段时间之后我发现了新的问题种子文件写得越来越多导出的上下文越来越长。最开始只有三四段后来项目级描述写了大量细节任务级又追加了一堆过程记录导致单次导出的 token 数量从大约 2000 涨到了 8000 多。模型处理超长输入时一是费用变高二是注意力会被稀释中间或者后面的内容容易被忽略。我实测下来超过 4000 token 之后AI 对尾部禁令的遵守程度明显下降。于是我做了三个调整。第一个调整是给上下文设预算线。我在use命令里加了一个粗估 token 数的方法超过 3500 token 就警告。估算方法很简单中文按 1 个字约等于 0.6 到 1 个 token 来算英文按 4 个字符约等于 1 个 token 来算不求精确只要大概量级对就行。第二个调整是把“临时事实”和“稳定背景”分开。所谓临时事实比如某次调试中发现的问题、某个接口当前的异常表现这类信息不应该沉淀在 project.toml 里否则它会随着时间越积越多还会误导后续的任务。我把它放到当前任务的文件里任务结束就清理。第三个调整是用 include 指令替代直接贴代码内容。过去我会把某个核心模块的代码全文粘到种子里现在只写一行“建议优先阅读 app/services/scheduler.py”让 AI 助手自己去读文件。这个改变极大节省了 token同时模型的准确率反而更高了因为它是从完整的源码文件里获取信息而不是从我截取的一段代码里。4.2 多任务并行时的作用域污染一次完整的排查链路多项目并行使用时我碰到过一个特别隐蔽的问题完整的排查思路写下来也许能帮你省几个小时。现象是一个已经配置好的项目 A切到项目 B 之后让 AI 助手完成任务它的回答里还频繁引用项目 A 的文件路径和模块名。第一反应我以为是 AI 助手缓存的问题重开会话也没解决。排查的第一步我先用ctx status查看项目 B 当前加载了哪些上下文源。结果发现项目 B 的.context目录压根是不存在的也就是说它没有自己的项目种子。第二步我直接把剪贴板里的合并文本贴到一个文本编辑器里从头读了一遍。这里关键信息出现了project_description的内容写的竟然是项目 A 的介绍。为什么会这样因为当项目 B 没有自己的 project.toml 时ContextLoader会静默跳过项目作用域只加载全局种子。而全局种子里的项目描述是我当初犯懒把项目 A 的信息顺手写进了全局 base.toml。于是项目 B 拿到的上下文里项目背景完全是错的模型自然按项目 A 的语境来回答。根因查清楚之后修复很直接。我把全局 base.toml 还原成只放通用角色和输出偏好把项目 A 的描述迁移到它自己的.context/project.toml同时给use命令加了一个启动校验执行时如果项目根目录下没有 project.toml就打印一行提醒而不是静默加载全局配置完事。这个坑给了我一个很深的教训上下文工具的作用域设计得再合理使用者的纪律跟不上一样白搭。全局文件是一个“公共空间”绝不允许被某个具体项目的内容污染。后来我又补了一个ctx clear命令在切出某个项目前把当前上下文语境清掉避免旧项目的残留信息影响新项目的会话。4.3 与 AI 编程助手配合时的输出格式约束还有一类问题和工具无关但和提示词内容强相关。很多 AI 助手在回答代码问题时习惯先解释一堆原理、再给优化建议、最后才写代码。对于学习来说这很好但对于干活来说它会让 review 成本变得很高。我的解决办法是在全局种子里增加一段输出格式要求也就是前面那个output_format字段的来源。加了之后效果非常明显回答从“大段说明 尾部代码”变成了一种更紧凑的结构先是结论然后是每个文件的改动点清单长代码只给文件路径和关键片段。这个格式是我实测下来最适合进入代码 review 流程的样式。这里有个细节值得说一下格式约束一旦写进全局种子就不建议在任务级种子反复重复。我见过有人每个任务文件里都写一遍“请先总结再解释”既费 token又容易和全局配置冲突。正确做法是全局只写一份项目或者任务有特殊展示需求时才用高优先级覆盖它。5. 再往后走扩展方向与落地前的几个问题5.1 我这个工具可能的扩展方向context-mode目前已经很够我用但它的设计留了不少扩展空间。一个比较自然的方向是支持多角色上下文。现在种子里的 persona 是固定的但实际开发中同一个项目可能需要切换不同的视角让 AI 以架构师身份做模块设计、以测试身份列测试用例、以运维身份检查部署脚本。可以把这些角色拆成不同的种子文件通过一个ctx use architect perf-fix这样的参数同时指定角色和任务。另一个方向是自动检测项目类型并生成种子。比如检测到目录里有pyproject.toml且包名是 FastAPI 相关就自动把常见的 Python 后端背景填进去。这个能降低初次配置成本前提是把自动生成的内容和手工维护的内容明确分开否则自动化写出来的东西质量不可控。还可以做 git 分支联动。比如切到feature/optimize-tasks分支时自动激活perf-fix任务上下文切回main时自动清除。目前我用的是手动执行ctx use自动化之后会更顺滑适合重度使用者。5.2 团队落地前我建议你先想清楚这些问题如果你想把这个思路引入团队我认为有几个问题应该提前有答案否则很容易变成新的形式主义。第一是版本控制和安全边界。种子文件一定会想放进 git 仓库方便团队成员共享。但种子文件里一旦出现数据库地址、密钥、内部服务名它就会成为一次安全事故。我的做法是在README里显式约定所有敏感配置一律用环境变量插值比如连接串写成${DATABASE_URL}文件和 TOML 里不允许出现真实的密钥如果你准备长期给团队用还可以加一个 CI 检查扫描种子文件的敏感模式。第二是种子文件的维护责任。谁来维护全局 base.toml项目描述更新了谁去改 project.toml如果不指定责任人这个文件很快又会变成一个谁都在写、谁也不负责的垃圾堆。我这边是跟架构评审走项目级种子变更必须走一次代码评审时机就是项目结构大调整的那一次提交。第三是模型能力边界。不同的 AI 助手模型对上下文长度和指令遵循能力不一样哪怕你的工具能输出一万字模型未必能全部有效利用。所以把种子文件当代码来收拢尽量控制在 3000 token 以内的做法是最稳的。我个人用下来的体会是context-mode最大的价值不是这个工具本身有多厉害而是它逼着我把“到底应该让 AI 知道什么”这件事想清楚了。过去我总觉得上下文乱是模型的问题现在回头看大部分情况是输入方自己没有管理好。如果你也想试一试我建议先别急着写代码把你平时反复粘贴给 AI 的那几段话收集起来整理成三个文件手动合并一周。如果发现真的能省时间再照着这篇文章的思路把它做成工具也不迟。
返回列表