news 2026/10/29 8:00:27

AI编码助手上下文管理:context-mode让token预算与历史摘要可控

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI编码助手上下文管理:context-mode让token预算与历史摘要可控

我一直有个心得:AI 编码助手其实很挑“工作现场”。模型本身的能力差距没你想的大,真正拉开体验的,是你能不能在一个可控的范围内给它喂正确的信息。这套可控范围的做法,我习惯把它叫做 context-mode,也就是上下文模式。它不是某个工具里现成的按钮,而是一套围绕 AI 编码助手的上下文管理方案:谁来提供信息、提供多少、保留哪些历史、允许输出多长,全部用可切换的模式来约束。这篇文章适合正在用 AI 做开发、但经常被“模型聊久了就忘事”“上下文不够用”“输出跑偏”折腾的同学,也适合想做 agent 或插件开发的人参考。看完之后,你可以照着搭一套把 token 预算、历史摘要、项目扫描都梳理清楚的最小可用系统。

1. context-mode 到底在解决什么问题

1.1 AI 的“工作现场”就是上下文

先说一个我在每个新项目里都会遇到的场景。模型刚开始工作时明明很“灵”,能记住你们定义的函数名,会照着项目已有的目录结构来提建议;可只要连续对话几轮,或者项目里塞进来一个比较大的模块,它就开始变得很像一个拿到半张地图的临时工——回答开始泛化,调用名写错,风格跟前文不统一。原因不是模型失效,而是它的上下文被用光了:真正重要的约束、刚跑出来的报错、团队规范,被中间大段闲聊和无关文件挤出了窗口。

“上下文”在这里指的是每次请求时发给模型的那一堆文字,相当于 AI 临时“打开的工作现场”。人类打开 IDE 时能看到整个项目,但模型只能看到我们放进上下文窗口里的内容。窗口有硬性上限,短则几千 token,长则十几万 token,但不论多长,都架不住“什么都往里塞”。context-mode 的核心,就是把“现场长什么样”从失控变成可控。

1.2 我给 context-mode 定下的三种模式

我最终实现的 context-mode,并不是一个单纯改了改提示词模板,而是通过配置文件驱动的状态机。默认有 light、standard、deep 三种模式,分别针对不同任务类型。它们之间不是“多了一句口号”的区别,而是三个维度的完整切换:token 预算、项目信息扫描深度、历史保留策略。

模式典型场景输入预算项目扫描深度历史保留策略
light快速问答、生成提交信息、解释报错约 2000 token只给目录树和必要依赖信息最近 1 轮 + 摘要
standard日常开发、单文件改动约 8000 token目录树 + 与本次改动直接相关的文件最近 3 轮 + 摘要
deep跨模块重构、接口梳理、全局排查约 16000 token核心源码 + 测试 + 文档尽量全量完整会话历史

这些数值不是固定的,我自己的项目里会按规模调整,但配置逻辑是通用的:每一档只改三件事,token 预算、扫描范围、历史保留。这三件事直接影响模型“读到什么、记住什么、能写多复杂”。拿 deep 模式举例,它不是为了把上下文塞满,而是为了长任务下保证不丢关键信息;而 light 模式则是刻意让模型“少读一点”,让它在短平快任务里不被无关文件干扰。

1.3 为什么坚持“模式”而不是“自动压缩”

现在很多 AI 助手都提供自动压缩功能:聊到一定长度后,把之前的对话压成一段摘要继续往下走。我实验过一段时间后,还是决定在自己的工作流里降低自动压缩的优先级。自动压缩的出发点是好的,但系统替你做决策时会悄悄丢掉关键信息。它可能保留了你们对某个按钮交互的详细讨论,却把你上一条命令里“不要修改公共接口”这样的硬约束丢掉了。

手动切换 context-mode,看起来笨拙,却换来了可预期性:你清楚每一档会拼出什么类型的输入,你清楚在哪一步模型会丢掉旧内容,你可以在任务开始前先锁定模式。当然我也加了自动降级提醒,但它只做提醒,不做静默修改。上下文的所有变化都要可见,后面查日志、回溯问题才做得到。这套思路贯穿了后续所有设计。

2. 拆开上下文:四类信息和预算分配逻辑

2.1 一次请求里,上下文由哪四类信息组成

要实现 context-mode,第一步不是写代码,而是搞清楚每次请求中到底有哪些内容会占 token。我拆过一段时间的日志后,把上下文分成四类:

  • 系统提示词:告诉模型“你是谁、遵循什么规范”,例如“你是资深后端工程师”“项目统一使用切片编程风格”“所有 API 必须走网关”。
  • 项目信息:目录结构、关键配置、目标文件、相关签名、编译期装配信息。
  • 会话历史:过去几轮的用户消息和助手消息,模型要靠它理解“我们刚才聊到哪了”。
  • 工具执行结果:终端输出、测试日志、文件搜索结果、语法诊断、LSP 返回的错误信息。

这四类内容像拼图一样拼进同一个窗口。context-mode 做的事,就是为每一块规定上限和优先级。拿 standard 模式举例,我会在上下文中给四个区块分配大概的比例:

内容块输入预算占比说明
系统提示词约 10%固定在最前面,优先级最高,不能压缩
项目信息约 35%投入产出比最高,但要裁剪
会话历史约 35%只保留与当前任务相关的部分
工具执行结果约 20%保留最新报错和测试输出即可

这个比例不是绝对的。工具结果如果特别重要,比如编译报错是当前唯一线索,我会把历史的比例临时调低,把报错放大。记住一个原则:哪一块信息在决定这次任务的成败,哪一块就是老大。

2.2 准确估算 token,别用“字数除以 2”

做预算的前提是算得准。估算 token 这事,用“一个汉字约等于 2 个 token”之类的经验值很容易翻车。中文语义下,一个汉字可能被切成一个到三个 token,代码里一个短函数名可能只占一个 token,一行很长的字符串又可能被切得非常碎。最靠谱的做法,是用模型对应的 tokenizer。

以 OpenAI 系模型为例,用 tiktoken 库就能直接拿到准确数字:

import tiktoken def count_tokens(text: str, model: str = "gpt-4o") -> int: try: enc = tiktoken.encoding_for_model(model) except KeyError: enc = tiktoken.get_encoding("cl100k_base") return len(enc.encode(text)) if __name__ == "__main__": sample = "把这段文字作为样例,看看切出来多少 token。" print(count_tokens(sample))

注意,不同厂商的模型 tokenizer 不同,甚至同一家的不同版本也可能不同。Claude、Llama、Gemini 各有自己的分词方案,拿 OpenAI 的编码去估算别家模型,预算一定会漂移。我的习惯是:把所有准备进入模型的内容合并成一段文本,再用对应模型的计数器统计,最后拆回到四个区块里。每个区块超了预算,就在组装前先做裁剪,而不是等模型收到请求后傻乎乎地截断。

2.3 预算不足时,按什么顺序丢弃信息

context-mode 中最核心的不是技术,而是取舍。当仓库很大、历史很长、预算有限时,我遵循一套明确的优先级,从最高到最低:

  1. 项目硬性规则:命名规范、框架选型、接口约束、不允许使用的库。
  2. 当前报错和测试失败信息:这些是当下最直接的上下文。
  3. 准备修改的文件内容:模型要改的是它,它不进场就全靠猜。
  4. 与目标文件有依赖关系的文件:调用方、被调用方、类型定义。
  5. 目录结构和全局配置:让模型明白“东西大概在哪”。
  6. 对话历史里与当前任务无关的部分。
  7. 通用知识:排序算法、常见设计模式这类模型已经知道的内容,能不占就不占。

这个顺序最反直觉的地方,是很多人习惯把对话历史完整保留,却忽略了报错信息。实际上,模型的长处恰恰是通用知识,它缺的从来都是“你项目的本地事实”。所谓上下文优化,就是让本地事实尽可能多、通用废话尽可能少。

2.4 切换模式的三种触发方式

模式不能只靠手动切,否则很容易忘了切。我在实现里做了三种触发路径:

  • 手动切换:在终端敲ctx --mode deep,或者通过 IDE 快捷键切换。适合任务开始前先锁定模式。
  • 自动降级:在组装上下文时估算 token,如果超过当前模式预算的 90%,就自动降一档,并向用户打印一行提示:“context-mode: 8.4K > 8K,已切换至 light”。
  • 任务类型预判:通过文件变更范围判断这次任务是否涉及跨模块,比如同时改动了service/order.py和dal/order.go,就自动进入 deep;只改了一个页面组件,就退回 standard。

手动为主,自动为辅。自动预判不能太激进,我做批处理迁移时会把模式固定为 deep,防止中途为了省 token 自动切走。判断规则宁可保守,不要自作聪明。

3. 实操:从 0 到 1 搭一套最小可用工作流

3.1 先定义配置:一个 JSON 文件管住所有模式

要落地,第一步是把所有模式规则从代码里抽出来,放到独立配置文件里。我的做法是在项目根目录建.ctx/modes.json,随仓库一起管理。这样换电脑、加队友、换模型,都能复制同步。

我给出一个可以直接动手改的示例:

{ "default_mode": "standard", "window_size": 32000, "reserved_output_tokens": 2400, "modes": { "light": { "description": "快速问答、生成提交信息", "budget_tokens": 2000, "system_prompt_tokens": 500, "repo_scan_depth": 1, "history_rounds": 1, "history_summary": true }, "standard": { "description": "日常开发、单文件改动", "budget_tokens": 8000, "system_prompt_tokens": 800, "repo_scan_depth": 2, "history_rounds": 3, "history_summary": true }, "deep": { "description": "跨模块重构、接口梳理", "budget_tokens": 16000, "system_prompt_tokens": 800, "repo_scan_depth": 3, "history_rounds": 99, "history_summary": false } } }

说明几个字段。window_size是模型真实上下文窗口,reserved_output_tokens是为模型输出预留的空间,所以任何模式的budget_tokens最大不能超过window_size - reserved_output_tokens。如果窗口是 32000,预留 2400,那输入预算理论上限是 29600,我给 deep 设 16000 是在这个范围内故意留了余量。repo_scan_depth我约定:1 代表只给目录树;2 代表目录树加与目标文件直接相关的源码;3 代表核心源码、测试和文档全量扫描。

3.2 写一个上下文构造器,按模式拼装请求

配置文件有了,还需要一个程序把所有内容按预算拼起来。下面是一段能跑通思路的 Python 示例:

import json from pathlib import Path import tiktoken def count_tokens(text: str, enc) -> int: return len(enc.encode(text)) def truncate_to_tokens(text: str, limit: int, enc) -> str: tokens = enc.encode(text) if len(tokens) <= limit: return text return enc.decode(tokens[:limit]) class ContextBuilder: def __init__(self, config_path: str = ".ctx/modes.json", model: str = "gpt-4o"): raw = json.loads(Path(config_path).read_text()) self.window_size = raw["window_size"] self.reserved = raw["reserved_output_tokens"] self.modes = raw["modes"] try: self.enc = tiktoken.encoding_for_model(model) except KeyError: self.enc = tiktoken.get_encoding("cl100k_base") def build(self, mode: str, system_prompt: str, project_sections: list[str], history: list[str], tool_logs: list[str]) -> str: cfg = self.modes[mode] sections = { "system_prompt": system_prompt, "project_info": "\n\n".join(project_sections), "history": "\n\n".join(history[-cfg["history_rounds"] * 2:]), "tool_logs": "\n\n".join(tool_logs[-5:]), } ordered_keys = ["system_prompt", "project_info", "tool_logs", "history"] final_parts = [] used = 0 for key in ordered_keys: limit = max(0, cfg["budget_tokens"] - used) piece = truncate_to_tokens(sections[key], limit, self.enc) final_parts.append(piece) used += count_tokens(piece, self.enc) return "\n\n".join(final_parts)

这段代码的思路很简单:按优先级顺序把四个区块塞进去,前面的区块先吃预算,后面的区块剩多少吃多少。如果project_info特别长,会先被压短;而system_prompt永远在最前面、最优先,不会被压缩到消失。真实工程里还可以加缓存、加并发锁、加摘要替换,但骨架就是这样的:模式不同,等于换了一组预算和扫描参数,拼装逻辑完全复用。

3.3 一定要给输出和模型行为留余地

第一次实现时我犯过一个低级错误:把输入预算顶到了窗口的 95%。结果模型每次生成到一半就断了,或者输出显得畏畏缩缩,好像知道自己的“字数不够了”。后来我把reserved_output_tokens单独列为配置项才想明白:上下文窗口是输入和输出共用的物理空间,模型生成答案也要占 token。

建议是至少预留 10%~30% 给输出。按 32000 窗口来算,reserved_output_tokens我取 2400,偏向保守。如果预判这次任务会产生长输出,比如让模型生成一整个模块代码,我会把预留值临时调高到 5000,同时把模式从 standard 切到 deep。否则,一次长代码生成就可能输送到一半被窗口卡死。

另外,不要小看格式开销。一次请求里还有角色标记、分隔符、系统消息结束符这些隐式 token,它们不在你的业务文本里,但会真实计入统计。预留的空间,能把这类隐形开销一并兜住。

3.4 加日志,把上下文使用变成可分析的数据

模式切了、上下文拼了、请求发出去了,然后呢?如果不记录,你又回到了“靠感觉调参数”的老路。我在每次请求前后都埋点,输出一行 JSON 日志:

ctx --mode standard --log .ctx/request.log

一条日志大概长这样:

2025-06-18 10:21 mode=standard input=5140 output=1820 ctx_used=70% result=success file=src/service/user.py 2025-06-18 10:25 mode=deep input=11800 output=2300 ctx_used=74% result=type_error file=src/service/order.py

字段分别是:发生时间、当前模式、输入 token、输出 token、上下文占用率、任务结果、涉及文件。攒两周数据后,你可以快速看出几个问题:哪种模式下出错的概率更高?哪个文件的扫描成本最离谱?哪类任务总是触达预算导致降级?这些数据反哺配置,模式参数就不必再靠猜。

4. 常见问题与排查实录

4.1 deep 模式反而变笨?多半是无关信息污染

有人问过我:为什么切换到 deep 模式后,模型表现反而更差?我遇到过的最常见原因,不是模型不够强,而是上下文被无关信息污染了。deep 模式扫描范围更大,会把大量老代码、旧规范、废弃接口一并塞进去。模型读多了陈旧风格,自然被带着跑偏。

排查思路很简单:先跑一个 light 模式的同一请求,如果输出反而更稳定,那说明问题不在“信息不够”,而在“噪声太多”。解决办法是在 deep 模式下增加裁剪约束,例如只扫描 import 路径上能到达的文件,忽略测试夹具、生成目录和第三方代码。后来我甚至给扫描器加了一块规则:与目标文件没有依赖关系的文件,默认不进入上下文。

4.2 切换模式后,模型像失忆了

有一段时间,我设置了history_rounds=2后,模型连刚才讨论过的 API 名字都记不住。查日志发现,我的“轮”数取错了对象:直接把最近的 2 条消息当成了 2 轮,实际上那可能只是 AI 的一条回复加你的一条追问,根本不够表达一段完整对话。

正确的做法是,把“轮”定义为“用户消息 + 助手消息”这个完整配对。同时在切换模式时,把更早的历史压缩成摘要,放到历史区块的最前面。比如先写一行<summary>当前任务:修复用户列表接口 500 错误,已确认问题在分页参数类型</summary>,再放最近 3 轮完整对话。这样即使只保留少量历史,模型对任务背景仍有基本盘。deep 模式之所以效果好,也就是因为它把这条摘要拉得更长、更接近原文。

4.3 token 估算和厂商扣费对不上

预估 token 和实际扣费不一致,也是常见问题。刚开始我用 OpenAI 的 cl100k_base 编码估算所有模型,结果在部分模型上偏差最高到 20%。原因是不同模型的词汇切分表不同,直接套用必然不准。更稳妥的做法,是每次请求完成后从 API 返回的 usage 字段里读实际 token 数,把它回写到日志和本地缓存里。用真实数据反推和校正,比任何经验公式都可靠。

如果厂商不直接返回 token 明细,可以用“字符数 × 系数”先粗估,再在多次请求后得到该模型的实际系数。把系数写进配置,而不是硬编码在代码里,后续校准才不麻烦。千万别在“差不多够”的边缘反复试探,超了预算,截断伤害的是整段输出。

4.4 给新手的避坑清单

最后整理几条实操心得,都是我用坏了无数个 session 换来的:

  • 模式数量不要超过 4 个。三四档足够;太多档会让人懒得切,最终又回到“一刀切”。
  • 配置文件必须集中,不要散落在代码各处的 magic number 里。改参数只改 JSON,代码只负责执行。
  • 自动降级只能提示,不能静默。所有上下文变化都要能被日志看到,否则出了问题根本无从定位。
  • 不要迷信长窗口。窗口再大,也装不下整个仓库,更装不下“所有信息都重要”这种幻觉。
  • 每接入一个新模型,先花十分钟跑一组 token 估算对照,把你的模式预算重新标定一遍,不要沿用旧参数。

对我而言,context-mode 最后的形态就是一种对上下文的预算管理:明白哪些内容值得占内存、哪些内容可以舍弃、哪些历史需要保留,这本身就是 AI 辅助开发的核心能力。它不是什么高深算法,但跑顺之后,整个工作流的稳定性会有明显提升。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 8:00:13

重言式判别算法详解:从表达式树构建到剪枝优化实战

简介&#xff1a;这是一份面向计算机科学与技术专业学生的《数据结构与算法》课程设计报告&#xff0c;主题为重言式判别程序的设计与实现&#xff0c;适合正在学习栈、二叉树与表达式求值的本科阶段读者参考。报告覆盖题目要求、问题分析、数据结构选择、概要设计与测试用例等…

作者头像 李华
网站建设 2026/10/29 7:59:21

GEO实战指南:用代码让AI搜索引用你的网站

流量没跌太多&#xff0c;但询盘、下载、注册这类“有效动作”突然少了一大截。这是我从去年初开始频繁听到的抱怨。细问之下&#xff0c;大家的排名好像也还凑合&#xff0c;关键词排名没崩&#xff0c;Google Analytics里的自然搜索会话数也不算难看。直到有人做了个小调研—…

作者头像 李华
网站建设 2026/10/29 7:59:19

Agent工作台革命:从Codex CLI到OpenWorkBuddy的范式迁移

1. 不是模型切换&#xff0c;而是工作流范式的迁移“从 Codex 到 OpenWorkBuddy&#xff1a;我换的不是模型&#xff0c;而是 Agent 工作台”——这句话刚发到内部技术群&#xff0c;就有三位同事立刻追问&#xff1a;“Codex 不就是 GitHub 的那个代码补全模型吗&#xff1f;O…

作者头像 李华
网站建设 2026/10/29 7:56:39

动态规划解不相交的线问题:LCS应用与优化

1. 问题背景与题目解析第一次在力扣上看到1035号题目"不相交的线"时&#xff0c;我承认有点被这个形象化的题目名称吸引了。这道题归类在动态规划专题下&#xff0c;但它的实际应用场景远比看起来要丰富得多。题目描述是这样的&#xff1a;在两条平行线上分别有若干个…

作者头像 李华
网站建设 2026/10/29 7:54:26

工业机器人监控技术演进与智能应用实践

1. 机器人监控技术十年演进全景十年前&#xff0c;工业机器人还主要依靠PLC和简单传感器实现基础监控功能&#xff0c;而今天我们已经进入了多模态感知、边缘智能和数字孪生深度融合的新阶段。这十年间&#xff0c;机器人监控技术经历了三次重大范式转移&#xff1a;2013-2016年…

作者头像 李华
网站建设 2026/10/29 7:53:23

STM32室内气体检测系统:从传感器采样到浓度换算的完整实现

简介&#xff1a;基于STM32F103C8T6的智能室内气体检测系统完整工程&#xff0c;实时监测温湿度与空气质量&#xff0c;具备本地显示、智能调控与云端交互三大核心功能。系统通过MQTT协议将数据上传至HomeAssistant&#xff0c;实现可视化监控与硬件控制&#xff0c;并可接入小…

作者头像 李华