每天跟大模型对话,最熬人的往往不是模型回答得够不够好,而是“把该说的背景说清楚”。context-mode 这个名字听起来挺学术,但它本质上就是一个帮我管理 AI 对话上下文的命令行小工具,核心解决两件事:该贴什么,以及不该贴什么。这篇文章会从需求讲起,拆到设计思路,再给出一套可以直接抄走的实现方案,适合每天折腾提示词、维护私有知识库,或者喜欢给自己造轮子的人。
1. 它到底解决什么问题:长对话的“失忆症”
1.1 痛点的日常画像
我在很长一段时间里,和 AI 助手的协作方式是极其原始的:新开一个对话,先把项目背景粘贴一遍,再把相关代码粘贴一遍,最后把需求粘贴一遍。一顿操作下来,几千字的提示词已经把模型搞晕了,它经常抓着某一小段信息反复问,而忽略掉更重要的目标约束。
更麻烦的是,我们平时用的聊天窗口默认是“零状态”的。每次新会话,模型只认识你当前输入的内容,之前聊过的需求细节、踩过的坑、约定好的命名风格,统统不记得。于是我就陷入了一个循环:重复补上下文、等待模型理解、发现漏了关键点、再补一遍。这种“失忆症”非常消耗精力,尤其当我同时在几个项目之间切换的时候,脑子里要维护的上下文太多了。
后来我试过把常用背景知识写进一个文档里,每次手动复制。效果有一点,但太粗糙:有时把整篇 README 丢进去,模型被无效信息带偏;有时只贴一段精简说明,又缺少了关键文件的约束,回答质量明显下降。问题的本质不是信息不够,而是没有一套机制,让上下文能按需、按量、分场景地注入。
1.2 context-mode 的一句话定义
于是我就想,能不能把“上下文的组装”从前端对话里抽出来,做成一个独立模块?这就是 context-mode 的雏形:一个通过命令行控制的小工具,把当前项目的关键上下文打包成结构化文本,并根据当前要干的活,决定打包的粒度、范围和格式。
它可以理解成“给模型做一张记忆导卡”。它不是把所有记忆都搬过去,而是按模式决定只带哪一层:是要快速了解项目轮廓,还是带着完整代码去改 bug,又或者是只给一个需求说明让人工评审。模式一变,注入的内容就跟着变,相当于给 AI 对话加了一个“上下文开关”。
这套思路对个人知识库同样适用。我经常把自己写的笔记、规范、常用脚本整合成上下文包,在需要不同工具来处理不同任务时,直接“切模式”,而不是反复复制粘贴一堆互不相干的内容。它把原本零散的上下文维护工作,变成了一种可重复、可版本化的结构。
2. 设计和架构:怎么把上下文封装成“包”
2.1 模式的定义与优先级
第一个要设计的,是“模式”这个核心概念。我一开始以为模式越多越好,后来发现贪多嚼不烂。最终我保留了四种最常用的模式:lean 模式、code 模式、review 模式和 explain 模式。
- lean 模式:只注入项目简介、目录结构和最关键的三五个文档。适合刚接手项目、或者只想快速了解全局。
- code 模式:注入源码目录、测试目录、当前分支的 git diff。适合实现功能或者排查报错。
- review 模式:注入变更文件列表、关键代码片段和评审标准。适合代码 Review,重点突出差异。
- explain 模式:注入指定文件的内容、相关依赖说明和提问目标。适合让模型解释某段逻辑。
每个模式下面用 include 和 exclude 来定义范围,再用 max_tokens 设置上限。这样优先级就很清楚:先由模式决定“要什么”,再由预算决定“给多少”。如果多个模式匹配同一个文件,就按配置的优先级去重,避免上下文重复。
设计时我还给每个模式预留了一个“覆盖参数”。比如 code 模式下,用户可以用 append 临时追加一个不在 include 里的文件,而不需要修改配置文件。这样既能守住基本规范,又保留了灵活性。
2.2 上下文包的生成链路
context-mode 生成上下文包的流程有五步,每一步都是独立的,方便单独调试:
- scan:遍历指定目录,拿到文件列表、最近修改时间、git 分支和 diff 概要。
- parse:对文件做轻量解析,提取文档标题、代码里的 TODO、函数定义摘要、配置里的关键字段。
- filter:按当前模式的 include、exclude、max_tokens 做过滤,必要时对超大文件做截断。
- assemble:把这些信息组装成带标题分层的 Markdown,并在开头生成一个“目录索引”。
- status:估算整体 token 数,输出使用摘要,如果超出预算就给出警告和裁剪建议。
这五步里最花功夫的是 parse。不是每个文件都要完整读一遍,而是要抓“关键特征”:比如一个 Python 文件,我只需要函数名、类名、装饰器和顶部注释;一个 Markdown 文档,我只需要标题、列表结构和结论段落。这样既能保留主要信息,又不会让上下文包膨胀到无法直接粘贴。
组装之后的格式大概是:第一层是项目背景,第二层是文件路径清单,第三层是关键代码片段,第四层是当前任务与约束。模型的注意力是有限的,把“任务目标”放在最后,就能让它更大概率实现目标——这个细节在实际测试中效果非常明显。
2.3 为什么这样设计
设计这套东西,我最大的原则就是“与对话历史解耦”。聊天窗口里最容易失控的就是历史越滚越长,很多无关的老问题会干扰新任务的判断。context-mode 把它彻底拉开:我不需要在一场对话里完成所有事,每次打开新窗口,只要执行 context build 生成新上下文,AI 就能以当前状态继续工作。
另一个好处是上下文包本身可以被“diff”。我改过一个文件之后,重新生成一次上下文,就能看到哪个文件进入了包、哪个被过滤了、token 预估涨了多少。这种可追踪性让上下文不再是一个黑盒,我可以明确知道模型到底看到了什么,而不是保存模糊的揣测。
还有一个容易被忽略的优点:上下文包可以保存、分发、复用。我经常把自己的一段报错现场打包成一个“求助上下文”,发给同事时,他不用再问我一堆基础问题。对协作来说,这种方式比截图+聊天记录高效得多。
3. 实操:从零搭建一个可用的 context-mode
3.1 基础命令与配置文件
我用的实现语言是 Python,因为它处理文本和遍历文件最方便,也不依赖复杂的编译环境。核心命令只有四个:
context build --mode code context show --mode lean context use --mode code --target app.py context audit --path ./contexts其中 build 负责生成上下文包,show 负责预览,use 会在生成后直接把上下文写进剪贴板,audit 会扫描并清理没用的旧包。日常用得最多的是 build 和 show,因为我可以先看一眼结果再决定要不要往对话里贴。
配置文件我放在用户目录下,命名为 context.yaml。下面是一份精简版:
project_root: ~/work/myapp default_mode: code modes: lean: include: - README.md - docs/architecture.md - ./* exclude: - node_modules/** - .git/** max_tokens: 1500 include_git_diff: false code: include: - src/** - tests/** exclude: - dist/** - build/** max_tokens: 6000 include_git_diff: true diff_max_lines: 300 review: include: - ./** exclude: - vendor/** - .cache/** max_tokens: 4000 include_git_diff: true diff_max_lines: 150 explain: target_mode: file max_tokens: 3000配置文件里每个字段都有它存在的意义。max_tokens 是硬性约束,避免我贪心塞太多内容导致模型“消化不良”;diff_max_lines 是专门控制 git diff 引入的行数,因为 diff 往往是上下文爆炸的主要来源。这里没有用特别复杂的语法,两层结构就够了:先映射项目,再映射模式。
3.2 算 token 的小经验
组装上下文包之前,得先做一个 token 估算。中文和英文的 token 密度不一样,按字符数直接估会误差很大。我整理出一个粗略公式:
token 估算值 ≈ 中文字符数 × 1 + 英文字符数 ÷ 4
因为当一个上下文包以中文为主时,大约 1 个汉字对应 1 个 token;代码和英文说明则差不多 3 到 4 个字符一个 token。这个公式不是绝对准确,但用来判断“会不会超限”足够用了。
我在 build 命令里加了提示,当估算值超过 max_tokens 的 80% 时,它会建议把 include 中的某个大文件换成它的摘要文件。实际用下来非常有效:与其让模型读一个 800 行的大文件,不如给它 30 行摘要加上阅读路径,模型反而更愿意按路径去查。
3.3 与编辑器和 AI 客户端的联动
命令行工具的一个加分项是能和编辑器结合。我平时用 Neovim,所以做了个简单映射:在普通模式按<leader>cc就等于执行 context build --mode code;按<leader>cl就等于 context use --mode lean。这样我在写完代码后,直接生成上下文,再转到对话窗口粘贴,整个过程只要几秒。
在更复杂的场景下,我还会用一个监听脚本,当某个目录里的文件变化超过阈值,就自动触发 build 并生成一份 diff 摘要。这个摘要不是直接发给模型,而是放进一个固定目录,供后续对话手动引用。这样我既不会频繁打断模型,也能保证关键时刻有最新的上下文可用。
另外,如果用的是带插件的编辑器和 AI 客户端,支持外部命令的场景就更友好。比如在 VS Code 里配置一个 Task,执行 context build 之后自动把内容写到临时文件,AI 插件直接读取该文件即可。这种方式比手动复制粘贴更准确,也更容易做成团队标准。
3.4 三个真实场景跑一遍
场景一:收到一个报错。我先执行 context build --mode code --target app.py,生成的包里有变更文件、diff 和报错行为描述。我把这个包贴进对话,模型几乎不会再问“项目背景是什么”,而是直接给出修复建议。
场景二:换到另一个项目,需要快速了解架构。我执行 context build --mode lean,只生成目录和基础文档摘要。这个包很短,扔给模型后,它就能对项目定位、模块关系给出比较清晰的解释,省去了通读文档的时间。
场景三:做代码 Review。我执行 context build --mode review,它会自动获取未合并分支的 diff,并提取涉及修改的函数列表。我只需要补充一句“请重点关注线程安全和边界条件”,模型就能切入正题。
4. 常见问题与排查实录
4.1 典型问题速查表
实际使用过程里,我遇到过不少问题。有些是工具本身的 bug,有些是我自己的使用习惯误入歧途。下面这张表是最典型的几个:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 上下文包被截断 | max_tokens 设置过大或 parse 时没做截断 | 调低 max_tokens,优先保留文件路径和摘要 |
| 模型回答偏离目标 | 关键代码被 exclude 规则误伤 | 打开 show 预览,调整 include/exclude 范围 |
| diff 内容太多 | 分支改动量大,diff_max_lines 生效 | 缩小 diff 范围,改用 review 模式而不是 code |
| token 估算偏差大 | 中英文比例异常 | 用字符级统计代替简单计数,复核公式 |
| 生成的上下文重复 | 新旧模式同时匹配同一文件 | 检查模式 priority,确保只有一个模式胜出 |
| 粘贴后格式混乱 | 生成的 Markdown 层级太深 | 减少标题层级,多在编辑器里用预览模式检查 |
这些坑看起来很基础,但每一条我都踩过。最遗憾的是 diff 内容过多那一条:有一次一个分支改了上千行,我把整个 diff 都塞进去,模型预测的注意力几乎全被无关改动占用,真正的 bug 反而没抓住。后来引入了 diff_max_lines 才缓解,关键是要意识到“给模型的不一定是越多越好”。
4.2 我踩过的三个坑
第一个坑是配置文件里写了exclude: src/utils/**,却忘了它的优先级比 include 高。结果哪怕我在 code 模式下清楚列出了 utils 里某个文件,它还是不会进入上下文包。排查了很久才发现,规则设计的顺序决定了成败:default 先应用 exclude,再叠加 include,最后按 max_tokens 截断。
第二个坑是 git diff 在未提交的状态下会把一堆临时代码也带进去。我用它去让模型“解释当前实现”,结果模型把还没写完的功能当成了最终状态。从那以后,build 命令里增加了--from参数,可以指定一个基准分支,比如context build --mode code --from main,保证上下文包只包含相对主分支的有意义差异。
第三个坑是“上下文过期”。我用 context-mode 生成的包保存了一段时间,等真的把它贴给模型时,项目里的文件结构已经变了。这个问题的根源不是工具,而是流程:生成了上下文却不及时使用。现在我会在 status 输出里记录一个“generated_at”时间戳,超过一定时间就主动提示重新生成。
4.3 配置不再当玄学用的几条经验
经过半个多月的调整,我把配置逐渐收敛成了一套比较稳定的风格。现在总结一下几条最实在的经验。
第一,永远给“当前任务”单独拉一个 section。上下文包最容易缺的就是目标说明,因为工具只能提取静态信息,动态任务目标还得人写。我习惯把任务描述放在配置文件的 task_prompt 字段下,生成的时候自动合并到 Markdown 末尾。
第二,头文件摘要比正文更值得贴。处理大型项目时,一个几百行的源文件里真正让模型需要的,往往是头部注释、导入关系和顶层函数名。解析时只抽这些内容,既能大幅减 token,又能保留足够信息。对文档类文件,我则优先提取标题和结论段落,“为什么这样做”这种原因描述反而要谨慎,保留太多容易让模型产生偏见。
第三,不要把上下文包当作永久存档。它应该是一个“易耗品”,随取随扔。我现在每周定期清理contexts/目录,只保留几个关键场景的模板例子。这样既减少混乱,也逼着自己每次用最新状态重新生成,而不是拿个旧包凑合。
5. 后续扩展与我的使用体会
5.1 顺着这个思路还能做什么
用久了之后,我发现 context-mode 的能力其实不止“给模型贴上下文”这么简单。同样的机制完全可以服务知识库检索:先按主题把文档分组,再用模式控制检索精度,最后拼装成答案。整个人工流程没有变,只是把对话里的“临时拼凑”换成了“标准组装”。
也可以做多项目分享。我在两个办公区域各有一台工作电脑,以前习惯手动同步各种知识碎片,现在直接把 context.yaml 和上下文包目录放进一个私人 Git 仓库,换设备后拉下来就跑。关键是运行环境和配置完全一致,不用再考大家一次。
另外,“上下文版本化”是一个我接下来想实现的特性。目前我只在包生成时记录时间戳和 git 版本,如果能做成每次 build 自动归档一份快照,那么将来做回溯分析时就有据可查:某个 bug 在哪次代码变更后被引入,模型在某次回答参考了哪个版本的上下文。这对复杂项目来说价值很大,因为它把对话质量和代码历史真正关联起来。
5.2 落地后的真实体验
这套小工具的代码量并不多,核心逻辑加起来不到五百行,但它帮我省掉的重复粘贴时间,远超我先前的预期。最大的变化是我不再害怕“新开对话”,因为我不需要重新背一遍项目背景,也不需要担心模型忘记之前的约定——只要先跑一遍 build,一切都清清楚楚。
有一个小细节给了我很多启发:模型对上下文的处理方式和人很像,给太多无关细节,它反而容易忽视真正的目标。context-mode 让我重新审视了自己的表达习惯,也让我明白了所谓的“上下文管理”,本质上是帮模型也帮自己理清楚优先级。先把问题上层结构梳理好,剩下的事情,工具和模型都能高效推进。
说到底,工具只是一个借口,最核心的还是我给每个任务划定清晰的边界。context-mode 帮我把这个边界固化下来,尽可能地避免低级错误。之后如果哪天你发现自己也陷入了“反复粘贴”的怪圈,不妨试着写一个类似的脚本,先不求功能完整,只求能自动生成一份可预览的上下文包,改善效果会用对话质量直接告诉你。