
前几天我把折腾了两周的一个终端小工具整理成了 v0.1 正式版项目名字就叫 context-mode。它要解决的事情一句话就能说清让我在终端里调用大模型 API 时不再被“上下文丢失”和“每轮重复贴背景资料”这两件事反复折磨。用过命令行 AI 助手的人应该都有体会对话一旦开多前面说过的内容基本就成黑盒了。切个 shell 窗口、重启个进程、或者只是隔了一晚上模型就完全不记得你昨天让它读的是哪个目录、遵循的是哪套规范。context-mode 这个项目本质上就是给这类场景加了一层可配置的“上下文管理模式”让你明确告诉工具这一轮任务是干净的还是要带上项目记忆是要读当前目录的文件还是只保留会话历史。如果你也在做类似的终端 AI 工具、自动化脚本或者只是被“模型老是忘记前面的设定”搞到头大这篇文章应该能给你一些可以直接抄走的思路。我会从设计背景、四种模式怎么选、具体配置怎么写再到真实调试中踩过的坑尽量一次讲透。1. 为什么会有 context-mode我踩过的上下文碎片化坑先说动机。我平时的工作流里大概有三分之一的时间是在终端里跟大模型 API 打交道写代码片段、整理日志、做文本批处理、给项目生成文档。工具换了好几个但问题一直是同一个——上下文管理太粗放。1.1 三个典型场景把我逼疯了第一个场景是多窗口并行处理。我在 A 窗口让模型分析一份配置文件的格式在 B 窗口让它写一段解析逻辑的 Python 函数。看起来两个会话井水不犯河水但实际上它们需要共享同一个认知背景这个配置文件是什么项目里的、字段含义是什么、最终要产出的模块长什么样。没有共享上下文A 窗口的分析结果没法直接喂给 B 窗口我得手动复制粘贴。第二个场景是“干净请求”和“历史请求”混在一起。有时候我只是想快速问一个与当前项目完全无关的问题比如“这个正则表达式是什么意思”但工具默认会把之前十轮对话全部塞进去。占了 token 不说还容易把模型带偏。反过来有些任务是强依赖上下文的比如“按我们刚讨论的接口规范继续生成代码”如果工具把历史清了等于从头开始。第三个场景是长上下文被截断。模型有 token 上限对话一长最早的设定系统提示、项目规范反而被挤掉了。最后模型开始胡言乱语不是因为能力不行而是它真的“看不见”最关键的约束。这些场景听起来不复杂但要在一个工具里同时解决靠简单地把所有内容拼进 system prompt 是行不通的。所以我干脆自己写了一套工具把上下文拆成几种有明确语义的模式让使用者在调用时主动声明“现在需要什么级别的上下文”。1.2 context-mode 的定位与解决思路context-mode 不是要替代大模型本身而是做一个中间层你发请求之前它会根据你选择的模式从不同的来源收集上下文素材按规则组装成一次请求的 payload。本质上是把“上下文”从隐性的对话记录变成显性的、可配置、可复用的资源。打个比方普通聊天工具里的上下文像是一个“临时便签本”说一句记一句翻到后面前面的字就模糊了。context-mode 想做的是一套“档案管理系统”四类档案盒有的放项目规范有的放全局偏好有的放会话记录有的留白。你要调用哪个档案盒里的内容由模式决定。这个思路的好处是你不用再依赖对话工具自带的“记忆窗口”去猜。一切上下文来源都是显式的文件、目录、数据库记录看得见、摸得着出了偏差也能查。2. context-mode 的整体设计四种模式与上下文优先级项目取名叫 context-mode核心就是这个 mode 的划分。我最终没有设计成特别复杂的无限级模式而是固定了四种按上下文由少到多排列。2.1 四种模式的适用场景先看这四种模式分别是什么模式名上下文来源适合场景none只有当前输入临时问答、测试、不想要任何历史干扰session当前会话的对话记录连续多轮讨论、逐步完善同一个任务project项目文件 会话记录代码生成、文档编写、配置文件修改global全局记忆 项目文件 会话记录跨项目复用个人偏好、遵守长期约束none 模式我最初觉得没必要后来发现它在“每轮固定输出格式”的场景里非常有用。比如我用脚本批量调 API 处理 100 行日志每一行都是独立请求前一次的结果不能影响后一次这时候就必须强制清空上下文否则模型会以为日志之间存在关联。session 模式就是普通会话但它有一层细节会话记录被存储为结构化 JSON而不是纯文本。每条记录分为 role、content、time 三个字段方便做时间衰减和摘要折叠。这块后面讲 token 预算时细说。project 模式是平时用得最多的。它会扫描当前目录下的特定文件比如 README、package.json、pyproject.toml、.context.md 等把这些内容作为项目级上下文塞进请求。这样我哪怕开一个全新的终端窗口只要在那个项目目录下运行命令模型也能知道“我在跟哪个项目打交道、用了什么依赖、遵循什么规范”。global 模式则是把所有项目的通用记忆拉进来。比如我个人的代码风格偏好、常用的输出格式要求、不想让模型反复询问的默认信息都放在一个全局文件里。用 global 模式时它会在 project 基础上再叠加这一层。2.2 上下文组装与优先级规则多种上下文来源放在一起不能无脑拼接。我花了最多时间设计的就是组装顺序和优先级。context-mode 内部把一次请求的最终上下文拆成四层系统层包括系统提示词、工具预设的 JSON 格式约束这部分永远在最前面。全局记忆global 模式下才插入放用户长期偏好。项目知识project/global 模式下插入来自项目目录扫描和 .context.md。会话历史session/project/global 模式插入按时间顺序排列但最旧的消息可能被压缩。组装的时候有一个硬性约束所有层加起来不能超过 token 预算。超出预算后不是随机丢弃而是按“优先级从低到高”开始裁。最先裁掉的是最旧的会话历史其次是项目知识中低权重的内容而系统提示词和全局记忆是保底的永远不会被裁掉。这个设计解决了我之前遇到的最大痛点系统提示词被挤出上下文。以前用别的工具聊久了模型的指令遵循能力会肉眼可见地下降后来发现是早期 system prompt 已被翻滚出了窗口。现在 context-mode 把系统层钉死在最前面无论会话多长它都在。2.3 Token 预算与自动摘要机制Token 预算不是简单设一个最大值就完事。我参考了很多长上下文方案后才定下这套规则。一次 API 请求的总 token 开销 输入上下文 token 输出 token。输出 token 需要预留否则模型生成到一半被截断。我一般按照max_tokens参数来预留默认2048。如果模型总上下文窗口是8192那么输入预算就是8192 - 2048 6144。这个计算逻辑在工具里是自动完成的但你得知道为什么要这样算。我之前遇到过一种情况明明上下文窗口写的是 8192我塞了 7000 个 token 的输入结果还是报超限错误。一查才发现输出预留没算进去模型要生成 2048 token加起来就是 9000 多自然爆了。自动摘要机制是另一个关键。当会话历史超出预算的 40% 时context-mode 会启动一个“折叠”动作把最早的一部分消息发给模型让它生成一段摘要然后用摘要替代那些消息。这个触发阈值我调过很多次最终定在 40%——太低会导致频繁调用模型做摘要浪费时间和费用太高又会在摘要生成完成前就把窗口塞满。40% 是一个在成本和可靠性之间相对均衡的点。折叠后的消息会保留摘要文本和最后一条原始消息确保对话连续性不至于断掉。你可以在配置里关掉这个功能但我实测下来开着它明显更稳。3. 实操落地从配置到跑通一个完整任务设计说得再多不如直接跑一个例子。这一节我从头到尾演示一遍用 context-mode 跑一个“让模型根据项目文件生成测试用例”的任务。3.1 安装与初始化context-mode 本身是一个 Python 写的 CLI 工具依赖最小只需要requests和pyyaml。安装方式很简单pip install context-mode装完先初始化它会自动创建一个配置目录context-mode init初始化做的事情是生成默认的~/.context-mode/config.yaml和全局记忆文件~/.context-mode/global.md。终端里会提示你输入默认的 API endpoint 和 key不想填也可以直接回车跳过后面手动写配置。3.2 配置文件详解配置文件是核心我贴一个我实际在用的精简版 YAMLmodel: name: gpt-4o-mini max_context_tokens: 8192 max_output_tokens: 2048 mode: default: project session: history_file: ./.context-mode/session.json fold_threshold: 0.4 project: scan_extensions: [.md, .py, .json, .toml, .yaml, .txt] max_file_size_kb: 64 include_dirs: [src, docs, config] exclude_dirs: [.git, node_modules, __pycache__, dist] custom_context_file: ./.context.md global: memory_file: ~/.context-mode/global.md priority: high这些参数我逐个解释下因为每个都踩过坑。model.max_context_tokens是模型理论上限不是输入上限别把它当输入预算直接用。max_output_tokens设多少取决于你想生成的答案长度。写代码任务我设 2048写长文档可能拉高到 4096。相应的输入预算会自动变小这是一个此消彼长的关系。scan_extensions是项目模式下要扫描的文件类型。我只列了文本类文件千万别把二进制、图片这类东西加进去纯属浪费 token。max_file_size_kb防止某个文件过大把预算一下子吃光。我遇到过项目里有个自动生成的 JSON 数据文件1MB 多扫描一次直接爆预算。现在超过 64KB 的文件会被跳过并在结果里给出一条提示不会默默忽略。include_dirs和exclude_dirs控制扫描范围。默认排除.git和node_modules是必须的否则扫描一次要卡半天token 倒是其次文件太多会导致工具运行得很慢。custom_context_file是我强烈建议你用的东西。项目模式下如果目录里存在.context.md文件它会作为优先级最高的项目知识被放进上下文。你可以在这个文件里写“本项目是一个 FastAPI 应用遵循 REST 规范使用 PostgreSQL代码风格使用 Black”之类的内容让模型一进来就懂项目背景。3.3 命令行使用方式装好、配好之后实际运行命令很直接# 使用默认模式配置里设的 project context-mode run 基于项目结构生成用户登录接口的测试用例 # 明确指定模式 context-mode run 介绍一下当前项目的目录结构 --mode project # 干净请求不附带任何项目文件 context-mode run 解释一下装饰器的作用 --mode none # 带会话历史的多轮对话 context-mode run 把刚才写的测试用例改成分 pytest 风格 --mode session # 全局项目模式 context-mode run 按我的个人规范补全这个模块的 docstring --mode global命令的关键在于--mode参数。实际运行时context-mode 会先在终端里打印出“本次组装了多少 token来自哪些来源”这样你一目了然。比如[context-mode] modeproject [context-mode] system: 486 tokens [context-mode] project: 372 tokens (README.md, .context.md) [context-mode] session: 0 tokens (empty) [context-mode] input total: 858 tokens | budget: 6144 tokens看到这个输出你就知道模型到底“看到”了什么。这比那些把上下文封装成一个黑盒的工具要透明得多。3.4 一次完整的调试过程实录拿“生成测试用例”这个任务实际走一遍。我当时的项目目录结构大约是这样的myproject/ ├── README.md ├── pyproject.toml ├── .context.md ├── src/ │ └── myproject/ │ ├── api.py │ └── models.py └── tests/在项目根目录运行命令。第一步,context-mode 会读取.context.md内容大概是我手写的项目背景说明然后扫描README.md和pyproject.toml提取项目描述与依赖再读取src/下的核心模块内容。所有内容都切成长度有限的文本块按顺序排好。第二步工具把系统和项目上下文组装好计算 token 数。我注意到models.py里有几个 Python dataclass 定义得比较长占了不少篇幅但这对“生成测试用例”这个任务是基础知识值得占用预算。于是我没有强行压缩它而是把api.py中的路由处理部分用更短的描述替代这部分我在.context.md里写了“api.py 提供 REST 接口使用 FastAPI具体逻辑见文件”扫描到的完整内容反而被截断了。这个取舍在调试过程中非常常见。第三步请求发出去。模型返回的结果里参考了.context.md中声明的项目规范以及models.py中的字段定义生成的测试用例直接就能在项目里跑通几乎没有需要手工修改的地方。整个过程核心收获是写.context.md比扫描所有代码文件更划算。代码文件太细碎模型读完容易迷失重点而一份 200 字左右的项目说明书信息密度远远大于 2000 行代码。现在我写新项目的第一件事就是花十分钟写.context.md。4. 常见问题与排查技巧实录工具用了这么久遇到过的坑不少。下面按出现频率排个序每个都附带排查思路。4.1 高频问题速查表现象可能原因解决办法请求报 context 超限没有考虑 max_output_tokens 预留检查配置里的输出预留确保 input_tokens output_tokens max_context_tokens模型回答完全不按项目规范来project 模式没生效或 .context.md 不存在先跑一次命令看输出的组装信息确认 project 上下文是否被加载同一个问题在 A 目录和 B 目录回答不一样project 模式下扫描到不同文件在 .context.md 里写清项目所属领域减少对扫描结果的依赖会话越聊越乱早期约定逐渐失效摘要折叠把关键信息丢掉了把关键约定写到 .context.md不要只写在对话里模式设成 global 但没看到全局记忆global.md 文件路径错误检查配置中 memory_file 写的是绝对路径还是相对路径输入内容包含大量无用文件扫描范围没配好优先配置 exclude_dirs用 max_file_size_kb 限制大文件里面最有代表性的是第一个坑。我早期把max_context_tokens当成了输入上限在代码里写死“输入不能超过 8192”结果经常报错。后来改成“输入预算 max_context_tokens - max_output_tokens”问题一次解决。第二个坑也很有代表性。我有一次在项目里运行命令模型完全不知道当前项目是什么。我打开控制台输出发现 project 模式确实启动了但扫描列表里只有空目录因为那个项目刚初始化连 README 都还没有。从那以后我就养成了在每个项目里写.context.md的习惯。4.2 独家避坑心得第一不要过度依赖自动摘要。自动摘要能在 token 超限时救急但它本质上是“压缩”压缩就会丢细节。我见过不少场景折叠之后模型把之前确认过的某个字段名记错了。所以关键信息一定要放到.context.md或者全局记忆里那里是永久保留区不参与折叠。第二模式切换比想象中重要。我一开始只习惯用 project 模式后来发现有些简单问题根本不需要项目知识。模型读了一堆无关文件后反而会“想太多”给出过度设计。偶尔用 none 模式反而能让回答更简洁。这就像你问一个同事“现在几点”他不用先打开项目需求文档再回答。第三会话历史文件要定期清理。session 模式的 history 文件会越来越大虽然摘要折叠会控制 token 量但磁盘上的 JSON 文件体积会持续增长。我写了个简单的 cron 任务每周把超过 7 天的历史文件归档避免它变成几百 MB 的怪物。第四token 数的估算要符合实际模型。不同模型的 tokenizer 对中文、代码的换算率不同。我一开始按“一个汉字约等于一个 token”估算结果对中文代码项目偏差很大因为代码符号多、空格多实际 token 数比估算的高出 30% 左右。后来我加了本地 tokenizer 检测才把预算控制准确了。如果你不想引入额外的 tokenizer 依赖至少要在预算判断时留出 30% 的余量。第五把 context-mode 作为纯本地工具使用别让它依赖任何远程服务。上下文组装过程全在本地完成只有最终请求发给模型 API。这样做的好处是可以对每次请求的 payload 做完整的日志记录出了问题可以直接查看发送给模型的到底是什么排查效率提高很多。5. 这一点是我最想强调的如果你只从这篇文章里带走一个观点我希望是上下文不是越大越好而是要分层、可查、可管。context-mode 的核心收获不是那点代码而是把原本黑盒的上下文请求过程变成了一套有规则、有优先级、有预算的显式流程。我个人现在的工作流已经完全离不开这套模式了。每次接到一个新任务先判断属于哪一层——是临时问一句、写一段代码、还是需要全局习惯加持——然后选好模式执行。省掉的不只是重复粘贴背景资料的时间更是大量因为“上下文没对齐”而产生的返工。这个项目后续我打算扩展的方向有两个一是支持多模型后端不只是单一 API二是把上下文组装过程做成可视化让使用者能看到每一层内容的占比和来源。如果你也在折腾类似的东西欢迎直接拿这些思路去用。有些坑我已经替你踩过了你就不用再踩一遍了。