搜索文章

输入关键词开始搜索

AI Coding的Token到底花在哪里

AI#AI#Agent

AI Coding的Token到底花在哪里

最近在使用 Codex、Claude Code、Kimi Code 这些工具时,我一直有一个疑问:

如果一个模型支持 1M 上下文,但我实际只用了 50K Token,它会不会比 256K 上下文的模型更贵?

一开始我下意识觉得,上下文窗口越大,消耗应该越高。后来仔细拆了一下才发现,这里混淆了三个东西:

  • 最大上下文容量
  • 当前请求的实际上下文长度
  • 多轮任务累计消耗的 Token

真正影响成本的,不是模型标注的最大上下文窗口,而是每次请求到底带了多少内容,以及这些内容被重复带了多少次。

上下文窗口不是实际消耗

上下文窗口可以理解成 AI 的工作台。

256K 和 1M 的区别,只是工作台最多能摆多少材料。1M 上下文并不意味着每次请求都会自动消耗 1M Token。

假设两个模型的定价、缓存策略和其他配置完全相同:

  • 1M 模型实际输入 50K
  • 256K 模型实际输入也是 50K

那么理论上的 Token 消耗基本相同。

所以,上下文窗口更像油箱容量,不是汽车油耗。大油箱不会让车在同一段路上自动消耗更多油。

不过现实中还要额外检查一个条件:不同上下文规格可能不是同一个价格。有些服务会给超长上下文设置不同单价,或者在输入超过某个长度后使用另一档计费方式。

因此,判断成本不能只看 Token 数量,还要看具体模型的价格规则。

每次请求都带了什么

很多人对 Token 消耗的理解是:

我问一句,AI 回一句,只计算这两句话。

实际的 Agent 工具通常还会带上很多内容,例如:

  • 系统提示词
  • AGENTS.md、CLAUDE.md 等项目规则
  • 工具定义
  • 当前任务说明
  • 之前的对话
  • 读取过的代码和文档
  • Shell 命令输出
  • 测试日志
  • Git diff
  • 压缩后的历史摘要

用户新输入的那句话,可能只有十几个 Token,但这次请求的完整输入已经有几十万 Token。

例如到了某一轮,我只输入了一句“继续修复”,但当前请求可能包含:

系统与工具说明       10K
项目规则与任务说明    10K
历史对话             60K
代码和文档           50K
Shell 与测试输出      20K
本轮问题              10

这次请求的输入不是十几个 Token,而是大约 150K。

当然,实际工具不一定会原样重发所有历史。有些内容可能被缓存,有些可能已经被压缩,也有些只是保存在本地、需要时才读取。但从使用者角度,应该关注的是最终送进模型的实际上下文,而不是自己刚刚输入了几个字。

真正烧 Token 的是多轮累计

长对话最容易忽略的问题,是历史上下文会在后续请求中反复出现。

假设一个任务开始时有 20K 基础上下文,之后每轮新增 10K 内容:

第 1 轮:20K
第 2 轮:30K
第 3 轮:40K
第 4 轮:50K
第 5 轮:60K

第五轮的上下文只有 60K,但前五轮累计输入已经是:

20K + 30K + 40K + 50K + 60K = 200K

多轮上下文累计与压缩

这也是为什么一个会话用得越久,Token 消耗可能越来越快。

如果每轮都增加差不多数量的历史,而且没有缓存、压缩或清理,那么累计输入会接近二次增长。任务轮数翻倍,总消耗不一定只是翻倍。

之前我比较关注“这次任务占用了多少上下文”,后来才发现,更应该关注的是:

这段上下文被模型重复读取了多少轮。

缓存可以降低价格,但不会让大上下文消失

Prompt Cache 会对稳定、重复的上下文提供更低的计费价格。

例如系统提示词、工具定义和没有发生变化的项目说明,如果连续请求的前缀相同,就可能命中缓存。

这当然有用,但不能简单理解成“缓存命中后就免费了”。

具体服务的计费方式不同,通常可能包括:

  • 普通输入 Token
  • 缓存写入 Token
  • 缓存读取 Token
  • 输出 Token
  • 部分产品单独统计的推理 Token

缓存读取一般比普通输入便宜,但不一定是零成本。

而且,高缓存命中率不一定代表上下文管理得很好。它也可能意味着,每次请求都带着一个非常大的固定前缀。

就像一辆车的单位油耗很低,但每天开一千公里,总油耗仍然很高。

因此应该同时观察:

  • 当前上下文长度
  • 普通输入量
  • 缓存读取量
  • 输出量
  • 整个任务累计消耗
  • 任务最终完成质量

只看缓存命中率,很容易得出错误结论。

小上下文为什么经常显得更省

小上下文模型的隐藏作用,是迫使 Harness 更早处理历史。

当会话接近上下文上限时,Codex、Claude Code 之类的工具可能会进行自动压缩,把大量历史整理成一份摘要:

原始历史
├── 讨论过的方案
├── 读取过的文件
├── 多次工具调用
├── 已完成的修改
├── 测试失败记录
└── 中间推理过程

压缩后
├── 当前目标
├── 已完成内容
├── 关键决策
├── 当前代码状态
└── 下一步任务

原来 200K 的历史,可能被压缩成十几 K。之后的请求只需要携带摘要和当前工作内容,单轮输入自然会下降。

因此,小上下文窗口确实可能间接控制成本——不是窗口本身更便宜,而是它更早触发了上下文清理。

这有点像给程序设置内存限制。内存无限时,开发者容易把什么都留在缓存里;内存有限时,系统会更早做分页、淘汰和持久化。

自动压缩也有成本

不过,不能因此得出“小窗口一定更省”的结论。

自动压缩至少有两个代价。

第一个是压缩本身也要消耗 Token。模型需要读取旧上下文,再生成新的摘要。只是如果后面还有很多轮工作,这次成本通常可以被后续减少的重复输入抵消。

第二个是信息损失。

摘要不可能保留全部细节。一次失败的尝试、某个边界条件、测试日志中的一行错误,当前看起来可能不重要,但后面排查问题时可能又需要。

如果摘要丢失了这些信息,Agent 就要重新读取文件、运行命令或者重复排查。压缩太频繁,反而可能增加总成本,还会影响任务质量。

所以比较好的策略是:在上下文中的低价值内容开始明显增加时,做一次 checkpoint。

大上下文什么时候反而更合适

有些任务确实需要较大的工作集,例如:

  • 跨多个模块的架构迁移
  • 全仓库安全或质量审查
  • 需要关联大量调用链的复杂 Debug
  • 大量文档之间的交叉分析
  • 不能随意丢失前面证据的长程任务

如果任务本身需要同时比较 500K 内容,那么强行塞进 256K 窗口,可能需要多次拆分、总结和重新读取。

这种情况下,使用较大的上下文一次完成全局分析,累计成本可能反而更低,结果也更可靠。

所以不能简单地把模型分成“大窗口浪费、小窗口节省”。应该先判断当前任务真正需要多大的工作集。

我现在更倾向于这个原则:按任务实际需要选窗口,选能舒服容纳当前任务的最小上下文,别默认最大或最小。

如何管理 AI Coding 的上下文

整理完这些关系之后,我准备从下面几个方面调整自己的使用方式。

一个会话只保留一个主要目标

不要在同一个会话里先设计架构,再实现几个无关功能,接着写文档,最后又做全仓库 Review。

这些任务虽然属于同一个项目,但需要的材料不同。全部堆在一个会话里,之前读取的文件和工具输出会不断污染后面的上下文。

会话应该按照任务边界切换,同一个项目里不同任务也要分开。

把长期状态写入文件

聊天记录不应该成为项目状态的唯一载体。

需要跨会话保留的内容,可以放进:

docs/architecture.md
docs/decision-log.md
task.md
progress.md
AGENTS.md

其中分别记录:

  • 稳定的架构说明
  • 已确认的关键决策
  • 当前任务边界
  • 已完成内容和下一步
  • 长期工作规则

新会话只读取与当前任务有关的文件,不需要继承完整聊天历史。

控制工具输出

AI Coding 中很大一部分上下文并不是代码,而是日志。

例如:

  • 一次输出几千行的测试
  • 整个目录的递归列表
  • 没有限制范围的代码搜索
  • 重复读取相同文件
  • 把完整构建日志留在会话里

很多时候真正有用的只有错误附近几十行。

因此,命令应该尽量限制范围,先搜索定位,再读取相关片段。测试输出也应该优先保留失败摘要和关键堆栈。

少读无关内容,通常比后面再压缩更有效。

在任务边界做 checkpoint

当一个阶段结束时,可以把当前状态整理为:

## 任务目标

## 已完成

## 修改的文件

## 关键决策

## 验证结果

## 未解决问题

## 下一步

如果后续任务和当前上下文已经明显不同,就从这份 checkpoint 开一个新会话。

这比等到窗口快满时被动压缩更可控,因为此时我还知道哪些信息最重要。

给上下文设置预算

可以给自己设置一个简单的 Context Budget:

  • 低于 50%:正常执行
  • 50%~70%:减少大范围读取,清理无关输出
  • 70%~85%:生成 checkpoint,判断当前阶段能否尽快结束
  • 超过 85%:优先压缩或切换会话,并为模型输出和工具结果预留空间

这个比例不是固定标准,需要根据工具的压缩方式和任务类型调整。它的目的只是让我不要等到上下文完全塞满后才处理。

最后

现在回头看,1M 和 256K 并不是问题的重点。

如果实际输入、价格和缓存策略相同,1M 模型只使用 50K 上下文,并不会因为剩余的 950K 容量自动产生费用。

真正容易烧掉额度的是:

  • 会话持续太久
  • 历史内容被重复携带
  • 工具读取和日志输出没有控制
  • 缓存降低了单价,却掩盖了巨大的重复输入
  • 压缩发生得太晚,或者压缩后又重复读取
  • 不同任务共享了同一个不断膨胀的上下文

小窗口的价值,是帮助我更早处理这些问题;大窗口的价值,是允许真正需要全局信息的任务减少拆分和信息损失。

后续选择模型时,我不会只比较上下文窗口大小,而会先问三个问题:

  1. 这个任务真正需要同时看到多少材料?
  2. 这些材料会在后续请求中重复多少轮?
  3. 哪些状态应该继续留在上下文,哪些应该写入文件?

上下文是 AI 的工作内存,项目的永久状态应该写进文件。像管理程序内存一样管理它,可能比单纯购买更多 Token 更重要。