使用 AI 编程工具时,很多人会有一个疑问:明明只输入了一句话,为什么额度消耗这么快?
原因很简单:模型收到的内容远不止那一句话。一次完整调用通常会包含系统指令、工具定义、项目规则、对话历史、工具调用结果,以及本轮新增输入。用户刚输入的那一小段文本,经常只占总输入的很小一部分。
要控制 AI 编程成本,不能只盯着“我说了多少字”,而要理解三件事:
- Token 是怎么计算的;
- 每次 API 调用实际发送了什么;
- 哪些内容可以被缓存,哪些内容每次都要重新付费。
Token 不是字数
Token 是大语言模型处理文本时的基本单位,它不是严格意义上的“字”或“词”。不同模型使用的 tokenizer 不完全一样,但可以用下面的经验值估算。
| 内容类型 | 粗略换算 | 说明 |
|---|---|---|
| 英文 | 1 token ≈ 4 个字符,约等于 0.75 个单词 | hello 通常是 1 个 token,长单词可能被拆分 |
| 中文 | 1 个汉字 ≈ 1~2 个 token | 常见汉字通常较省,生僻字或复杂组合可能更贵 |
| 代码 | 波动较大 | 关键字、变量名、符号、缩进都会消耗 token |
一些常见规模可以这样估算:
| 内容 | 大致 Token 数 |
|---|---|
| 1000 字中文说明 | 1200~1800 tokens |
| 200 行 Python 代码 | 2000~4000 tokens |
| 一份中等规模项目规则文档 | 2000~5000 tokens |
| 一个复杂工具返回结果 | 3000~10000 tokens |
代码的 token 消耗经常比直觉更高,因为模型不只看自然语言,还要处理标点、括号、缩进、路径、变量名和字符串。
一次 API 调用包含哪些内容
在 AI 编程工具里,每发出一次请求,模型接收到的通常不是单条消息,而是一整包上下文。
flowchart TD
A[用户输入一句话] --> B[组装完整上下文]
B --> C[System Prompt<br/>系统指令、工具定义、项目规则、Memory]
B --> D[对话历史<br/>用户消息、AI 回复、工具调用入参和结果]
B --> E[本轮新增输入<br/>当前需求、附加文件、选中文本]
C --> F[发送给模型]
D --> F
E --> F
F --> G[模型输出<br/>文字回复或工具调用指令]
G --> H[输出进入下一轮历史]
一次调用可以拆成四部分:
| 部分 | 是否经常出现 | 典型规模 | 成本特点 |
|---|---|---|---|
| System Prompt | 每轮都有 | 8k~25k tokens | 固定成本,缓存后便宜很多 |
| 对话历史 | 逐轮增长 | 5k~100k+ tokens | 会越来越大,但可缓存 |
| 本轮输入 | 每轮新增 | 0.5k~3k tokens | 通常全价 |
| 模型输出 | 每轮产生 | 1k~8k tokens | 按输出价格计费,通常比输入贵 |
真正的大头通常是前两项。用户输入可能只有几十个 token,但系统指令和历史上下文可能已经有几万 token。
Prompt Cache:为什么旧会话可能比新会话便宜
如果每次调用都从头计算完整上下文,长对话的成本会增长得非常快。
假设:
- System Prompt:15k tokens;
- 每轮新增历史:2k tokens;
- 连续对话 10 轮。
没有缓存时,每一轮都要重新计算全部内容。
Turn 1: 15k + 2k = 17k
Turn 2: 15k + 4k = 19k
Turn 3: 15k + 6k = 21k
...
Turn 10: 15k + 20k = 35k
10 轮输入合计 ≈ 260k tokens
因为每一轮都会带上之前所有历史,总成本接近二次增长。对话越长,重复计算越多。
KV Cache 的核心原理
大语言模型常用 Transformer 架构。模型处理前文时,会为每个 token 计算注意力机制需要的 Key 和 Value 张量。只要前缀内容不变,这些 Key 和 Value 也不会变。
KV Cache 的做法就是:把已经算过的前缀结果缓存起来。下一次请求如果前缀完全匹配,就直接读取缓存,不再按全价重新计算。
flowchart LR
A[稳定前缀<br/>System Prompt + 历史消息] --> B[计算 Key / Value]
B --> C[(KV Cache)]
D[下一轮请求] --> E{前缀是否匹配}
E -- 匹配 --> F[读取缓存]
E -- 不匹配 --> G[重新计算]
F --> H[只计算新增内容]
G --> H
这类缓存也常被称为 Prompt Cache。它不是把模型回复缓存起来,而是把“已经处理过的输入前缀”缓存起来。
写入、命中、全价三种输入价格
以 Claude API 的计费思路为例,输入 token 可以分成三类。
| Token 类型 | 价格倍率 | 触发条件 |
|---|---|---|
| 缓存写入 | 基础输入价 × 1.25 | 某段前缀第一次出现,需要写入缓存 |
| 缓存命中 | 基础输入价 × 0.1 | 后续请求命中相同前缀,直接读取缓存 |
| 全价输入 | 基础输入价 × 1.0 | 本轮新增内容,或无法命中的内容 |
不同模型的具体单价会变化,但“缓存命中显著便宜”这个规律很重要。缓存读取通常只需要基础输入价的十分之一。
| 模型 | 基础输入 | 缓存写入 | 缓存读取 | 输出 |
|---|---|---|---|---|
| Claude Opus 4 | $5 / M | $6.25 / M | $0.50 / M | $25 / M |
| Claude Sonnet 4 | $3 / M | $3.75 / M | $0.30 / M | $15 / M |
| Claude Haiku 4.5 | $1 / M | $1.25 / M | $0.10 / M | $5 / M |
M 表示一百万 tokens。对于 Opus 这类模型,命中缓存后的输入价格甚至可能低于更小模型的基础输入价格。
有缓存后的 10 轮成本
继续使用前面的假设:
- System Prompt:15k tokens;
- 每轮新增:2k tokens;
- 缓存命中价格按 0.1 倍计算;
- 首次写入按 1.25 倍计算。
Turn 1:
System 15k × 1.25 + 新增 2k × 1.0
= 20.75k 等价 tokens
Turn 2:
System 15k × 0.1 + 历史 2k × 0.1 + 新增 2k × 1.0
= 3.7k 等价 tokens
Turn 3:
System 15k × 0.1 + 历史 4k × 0.1 + 新增 2k × 1.0
= 3.9k 等价 tokens
Turn 10:
System 15k × 0.1 + 历史 18k × 0.1 + 新增 2k × 1.0
= 5.3k 等价 tokens
对比结果更直观:
| 场景 | 10 轮输入规模 | 等价成本 | 增长特征 |
|---|---|---|---|
| 无缓存 | 约 260k 全价 tokens | 约 260k | 接近 O(N²) |
| 有缓存 | 仍发送约 260k tokens | 约 62k | 更接近 O(N) |
缓存不会让上下文消失,但会让稳定前缀变得便宜。长会话的优势就在这里:只要前缀稳定,后续每轮主要支付新增部分。
缓存只能从头匹配
Prompt Cache 的关键限制是“前缀匹配”。它不是在任意位置找相同片段,而是从输入开头开始逐段匹配。
一个典型上下文可以看成一条链:
flowchart LR
A[核心系统指令] --> B[工具定义]
B --> C[Rules]
C --> D[Memory]
D --> E[项目文档]
E --> F[历史消息 1]
F --> G[历史消息 2]
G --> H[本轮新增]
如果只是在末尾追加新消息,前面的内容都能命中缓存。
[命中 System] -> [命中 Tools] -> [命中 Rules] -> [命中 Memory] -> [命中历史] -> [新增消息全价]
如果中间某一段变了,变化点之后的内容都不能继续复用。
| 变化位置 | 命中情况 | 成本影响 |
|---|---|---|
| 只追加新消息 | 前缀基本全命中 | 最省,只为新增内容付全价 |
| 修改 Rules | Rules 之前命中,Rules 之后失效 | 后续历史需要重新计算 |
| 修改 Memory | Memory 之前命中,Memory 之后失效 | 项目文档和历史可能重新计算 |
| 切换模型 | 基本无法复用 | 不同模型的 KV 张量不能互通 |
| 新开会话 | 重新冷启动 | System Prompt 和历史都要重新建立 |
因此,频繁修改配置、切换模型、新开窗口,都会破坏缓存收益。
缓存有效期与保活
缓存通常有有效期。以 Anthropic API 的常见机制为例,默认缓存有效期是 5 分钟,也可以通过付费选项延长。部分产品层可能会为订阅用户做额外优化,例如把有效期扩展到 1 小时。
每次命中缓存时,过期计时通常会被刷新。也就是说,只要在过期前继续发送匹配前缀的请求,缓存就能持续存在。
实践中要注意两点:
| 行为 | 结果 |
|---|---|
| 连续工作,中间间隔很短 | 大概率持续命中缓存 |
| 长时间离开后再回来 | 可能缓存过期,下一轮变成冷启动 |
| 用简单消息维持会话活跃 | 可以减少长任务中缓存失效的概率 |
如果正在处理一个持续数小时的大任务,长时间暂停前要意识到:回来后的第一轮可能会比之前更贵、更慢。
配置项如何影响 Token 成本
AI 编程工具通常有多层配置:Memory、Rules、Skills、MCP 工具、项目说明文档等。它们看起来都是“给 AI 的规则”,但加载方式完全不同。
不同加载方式决定了它们的成本差异。
flowchart TB
A[Memory<br/>全量常驻] --> B[Rules<br/>常驻或触发]
B --> C[Skills<br/>加载前轻量,加载后进入历史]
C --> D[MCP 工具<br/>Schema 常驻,结果进入历史]
A1[适合一句话偏好] -.-> A
B1[适合编码规范和场景模板] -.-> B
C1[适合完整工作流] -.-> C
D1[适合浏览器、数据库、部署等外部能力] -.-> D
Memory:简单但全量常驻
Memory 通常用于记录用户偏好或项目约定。它的特点是:所有记忆条目会被拼接后注入 System Prompt,不会根据当前任务自动筛选。
例如:
- Python 缩进使用 4 个空格
- SQL 不等于使用 <>
- 文档统一使用 Markdown
- 部署脚本放在 scripts/ 目录
这些内容单条很短,总量也许只有几百 tokens,但它们每轮都会进入系统上下文。
Memory 的问题不只在 token 数量,还在两个隐性影响:
| 影响 | 说明 |
|---|---|
| 缓存链影响 | 修改任意一条 Memory,都可能让它后面的前缀失效 |
| 注意力噪声 | 与当前任务无关的偏好也会被模型看到,可能干扰判断 |
Memory 适合放一句话能说清的稳定偏好,不适合放复杂规范、长模板或完整流程。
Rules:高频常驻,低频触发
Rules 比 Memory 更适合承载结构化规范。它通常支持不同加载模式。
| 模式 | 加载时机 | Token 影响 | 适合内容 |
|---|---|---|---|
| 常驻 | 每轮都进入 System Prompt | 增加固定系统成本,缓存后变便宜 | 高频编码规范 |
| 触发式 | 根据关键词或语义加载 | 不触发时几乎零成本 | 特定场景模板 |
| 手动 | 用户显式指定时加载 | 完全按需 | 低频流程或特殊约束 |
一个合理的规则配置可能是:
code-style-guide.mdc -> 常驻 -> 每个编码任务都要遵守
api-generation.mdc -> 触发式 -> 只有生成 API 时加载
migration-checklist.mdc -> 触发式 -> 只有数据库迁移时加载
release-process.mdc -> 手动 -> 只有发版任务时加载
常驻 Rules 的数量越多,System Prompt 越重。低频规则如果也常驻,就会变成每轮都要支付的固定税。
Skills:加载前很轻,加载后留在历史里
Skills 通常用于描述一个完整能力包。它可能包含:
SKILL.md:技能说明和执行流程;references/:参考资料;scripts/:可执行脚本。
它的成本模式和 Rules 不一样。
| 阶段 | 上下文状态 | Token 成本 |
|---|---|---|
| 未加载 | 只有简短 description 出现在可用列表中 | 很低,常见几十 tokens |
| 被触发加载 | SKILL.md 全文注入对话 |
一次性 5k~15k tokens |
| 加载之后 | 内容留在对话历史中 | 后续可缓存,但无法从历史中移除 |
Skills 适合完整工作流,而不是单条规范。比如“数仓开发流程”“代码审查流程”“接口压测流程”这类任务,往往需要指令、参考文档和脚本配合,放在 Skills 里更合适。
但不要随意预加载 Skills。一个 10k tokens 的技能文件,一旦进入历史,后续每轮都会带着它,只是缓存命中后成本降低而已。
MCP 工具:定义常驻,结果累积
MCP(Model Context Protocol,模型上下文协议)用于把外部工具接入模型,例如浏览器自动化、数据库查询、文件系统操作、部署平台调用等。
MCP 的成本分成两部分。
| 成本来源 | 说明 |
|---|---|
| 工具定义 | 每个工具的 JSON Schema 常驻在 System Prompt 中,常见 200~500 tokens |
| 调用结果 | 工具返回内容会进入对话历史,后续每轮继续携带 |
工具定义看起来不大,但如果全局启用很多闲置工具,固定成本会明显增加。
| 工具行为 | 典型返回规模 | 后续缓存命中后的等价成本 |
|---|---|---|
| 读取 200 行文件 | 3k~5k tokens | 300~500 tokens / 轮 |
| 搜索代码并返回 10 条结果 | 2k~4k tokens | 200~400 tokens / 轮 |
| 执行长输出命令 | 1k~10k tokens | 100~1000 tokens / 轮 |
复杂任务中,工具调用结果经常比用户输入大得多。一个会话如果调用了 20 次工具,对话历史里可能已经积累了 40k~100k tokens 的结果。
内容应该放在哪一层
配置分层的原则是:越高频、越短、越稳定的内容,越适合常驻;越低频、越长、越场景化的内容,越应该按需加载。
| 内容类型 | 推荐位置 | 原因 |
|---|---|---|
| “变量命名用 snake_case” | Memory | 一句话偏好,全量常驻也不贵 |
| 代码风格规范 | 常驻 Rules | 高频使用,结构化表达更清楚 |
| API 生成模板 | 触发式 Rules | 只有生成接口时才需要 |
| 数据迁移检查清单 | 触发式 Rules | 低频但需要明确步骤 |
| 完整开发流程 | Skills | 需要指令、参考资料和脚本 |
| 浏览器操作、数据库查询 | MCP 工具 | 需要外部系统能力 |
| 大型项目背景说明 | 项目文档 + 引用 | 核心内容常驻,细节用路径引用 |
一个常见错误是把所有东西都塞进 System Prompt。这样做最直接的问题是固定成本变大,而且任何配置变动都会破坏缓存链。
更合理的做法是:
短偏好 -> Memory
高频规范 -> 常驻 Rules
低频模板 -> 触发式 Rules
完整流程 -> Skills
外部能力 -> MCP
大型参考资料 -> 文件引用,不要全文内联
Sub-Agent 不一定省钱,但能隔离上下文
很多 AI 编程工具支持 Sub-Agent。主 Agent 可以派出独立子 Agent 去做代码搜索、审查、分析等任务。
Sub-Agent 的价值不是天然省 token,而是隔离上下文,防止主会话被大量工具结果撑大。
Sub-Agent 为什么不能直接复用主会话缓存
主 Agent 和 Sub-Agent 往往有不同的上下文。
| 差异 | 对缓存的影响 |
|---|---|
| 工具集不同 | 工具 Schema 不同,前缀不匹配 |
| 消息历史不同 | 子 Agent 是独立任务上下文 |
| 模型可能不同 | 不同模型的 KV Cache 不能互通 |
| 系统指令不同 | 缓存前缀从开头就可能不一致 |
一个对比可以说明问题。
主 Agent 第 8 轮:
System 15k × 0.1 + 历史 23k × 0.1 + 新增 2k
= 5.8k 等价 tokens
Sub-Agent 第 1 轮:
System 10k × 1.25 + 传入任务 3k
= 15.5k 等价 tokens
子 Agent 经常是冷启动,因此单次调用可能更贵。
什么时候适合用 Sub-Agent
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 读取少量文件 | 主 Agent 直接处理 | 主会话已有缓存,边际成本低 |
| 大范围代码探索 | Sub-Agent | 避免几十个文件结果进入主历史 |
| 独立代码审查 | Sub-Agent | 审查上下文可以隔离,结论再回传 |
| 可用便宜模型完成的子任务 | Sub-Agent | 子任务用更低成本模型处理 |
| 需要长期共享上下文的任务 | 主 Agent | 避免子 Agent 反复冷启动 |
Sub-Agent 更像“上下文防火墙”。它把脏活、重活、探索性工作放到独立上下文里,主会话只接收整理后的结论。
成本优化策略
Token 成本优化可以分成两类:配置侧和对话侧。
配置侧决定固定系统成本有多大,对话侧决定缓存是否稳定、历史是否膨胀。
配置侧:会话开始前一次性配好
配置类内容位于上下文前缀中,修改它们很容易打断缓存链。因此,最好在会话开始前整理好 Rules、Memory、MCP 工具和项目文档,任务进行中尽量不要频繁改。
Rules 优化
| 策略 | 做法 | 效果 |
|---|---|---|
| 减少常驻规则 | 只把高频规则设为常驻 | 降低每轮系统税 |
| 低频规则触发式加载 | 通过关键词或语义触发 | 不用时不进上下文 |
| 合并同类规则 | 多个碎规则合成一个结构化规则 | 减少元信息开销 |
| 精简规则内容 | 删除重复示例,保留约束和必要例子 | 降低规则体积 |
错误配置和合理配置的差异很明显:
不合理:
5 条规则全部常驻
=> System Prompt 增加 5k~15k tokens
更合理:
1 条高频规则常驻 + 4 条低频规则触发式加载
=> 常驻部分降到 1k~3k tokens
Skills 优化
| 策略 | 做法 | 效果 |
|---|---|---|
| 不预加载 | 让工具按任务判断是否加载 | 避免无效注入 5k~15k tokens |
精简 SKILL.md |
只放核心流程,长资料放 references/ |
降低首次加载成本 |
| 写清 description | 明确触发场景和关键词 | 减少误加载 |
Memory 优化
| 策略 | 做法 | 效果 |
|---|---|---|
| 定期清理 | 删除过时偏好和重复约定 | 减少噪声 |
| 合并相似条目 | 把多条同类规则合成一条 | 降低条目数量 |
| 控制长度 | 每条 1~2 句话 | 保持 Memory 轻量 |
MCP 工具优化
| 策略 | 做法 | 效果 |
|---|---|---|
| 禁用闲置工具 | 不把所有工具全局打开 | 减少 Schema 固定成本 |
| 按项目启用工具 | 每个项目只启用需要的 MCP | 降低 System Prompt 体积 |
| 控制返回内容 | 搜索、日志、命令输出尽量限制范围 | 减少历史膨胀 |
项目文档优化
项目说明文档可以帮助模型理解代码库,但不要把所有细节都内联进去。
更好的写法是保留核心信息,并用路径指向细节:
# 项目约定
- 后端入口在 `src/server/`
- 数据库迁移脚本在 `migrations/`
- API 规范参考 `docs/api-style.md`
- 发版流程参考 `.ai/skills/release/SKILL.md`
这样模型能知道去哪找信息,而不是每一轮都携带所有文档全文。
配置优化的量化效果
假设优化前后如下:
优化前:
- 多条 Rules 全部常驻
- 10 个 MCP 工具全局启用
- 30 条 Memory
- 大型项目文档全文内联
System Prompt ≈ 25k tokens
缓存命中后每轮等价成本 ≈ 25k × 0.1 = 2.5k
优化后:
- 1 条高频 Rule 常驻
- 低频 Rules 触发式加载
- MCP 按项目启用
- Memory 控制在 500 tokens 以内
- 项目文档用引用代替全文
System Prompt ≈ 12k tokens
缓存命中后每轮等价成本 ≈ 12k × 0.1 = 1.2k
每轮节省约 1.3k 等价 tokens。长任务如果进行 100 轮,就能少消耗约 130k 等价 tokens。
对话侧:保护缓存,减少无效轮次
会话进行中,最重要的是保护缓存前缀。很多看似普通的操作,其实会让下一轮变成冷启动。
保护缓存
| 原则 | 做法 | 原因 |
|---|---|---|
| 一个任务尽量用同一个会话完成 | 不频繁开新窗口 | 新会话需要重新建立 System Prompt 和历史 |
| 不频繁切换模型 | 选定模型后持续使用 | 不同模型的缓存不能互通 |
| 不在任务中途改配置 | Rules、Memory、MCP 尽量提前配好 | 配置变化会打断前缀匹配 |
| 长时间任务保持活跃 | 避免缓存过期 | 过期后需要重新计算前缀 |
减少无效 Token
| 原则 | 低效方式 | 更高效方式 |
|---|---|---|
| 一次说清需求 | 分三轮补充字段、约束、输出格式 | 一次给出目标、输入、限制和验收标准 |
| 少贴无关代码 | 直接贴 500 行文件 | 指出文件路径、函数名、错误行和现象 |
| 限制工具输出范围 | 让工具返回完整日志 | 只返回最近 100 行或匹配关键字 |
| 避免重复确认 | 每一步都问“继续吗” | 让 AI 在可控范围内连续完成 |
| 跨会话保存计划 | 只靠聊天历史承接 | 把方案写进 plan.md 或任务文件 |
复杂任务尤其适合把计划沉淀到文件里。这样即使换会话,也不用把完整历史重新解释一遍,只需要让模型读取关键文件。
示例:
# task-plan.md
## 目标
把用户订单导出接口改为异步任务模式。
## 已确认约束
- 保持原接口兼容
- 新增任务状态查询接口
- 导出文件保留 7 天
## 待完成
- 调整数据库表结构
- 增加异步任务 worker
- 补充接口测试
这种文件化上下文比长聊天历史更稳定,也更容易被复用。
常见误区
| 误区 | 实际情况 |
|---|---|
| “我只输入一行字,不应该很贵” | 用户输入通常只是零头,系统指令和历史上下文才是大头 |
| “长对话越来越贵,所以要经常新开窗口” | 稳定长会话能命中缓存,新窗口反而会冷启动 |
| “Sub-Agent 一定省 token” | 子 Agent 常常冷启动,它的优势是隔离上下文 |
| “Memory 很耗 token” | Memory 通常不大,但过多无关条目会增加噪声并影响缓存链 |
| “压缩历史一定省钱” | 压缩可能改变前缀,导致缓存失效;接近上下文上限时再考虑 |
| “Rules 越多越好” | 常驻 Rules 会增加固定系统成本,低频规则应按需加载 |
| “Skills 加载了不用也没关系” | SKILL.md 一旦进入历史,后续会话轮次都会携带 |
| “配置可以边做边改” | 修改 Rules、Memory、项目文档或 MCP 工具可能打断缓存 |
一套实用判断框架
控制 AI 编程工具的 Token 消耗,可以用下面这套流程判断。
flowchart TD
A[准备一个任务] --> B{内容是否每轮都需要?}
B -- 是 --> C{是否很短且稳定?}
C -- 是 --> D[放入 Memory]
C -- 否 --> E[放入常驻 Rules]
B -- 否 --> F{是否是低频规范或模板?}
F -- 是 --> G[放入触发式 Rules]
F -- 否 --> H{是否是完整工作流?}
H -- 是 --> I[做成 Skills]
H -- 否 --> J{是否需要外部能力?}
J -- 是 --> K[启用对应 MCP 工具]
J -- 否 --> L[放在项目文档中按需引用]
再配合四条会话习惯:
- 同一个任务尽量在同一个会话里完成;
- 会话中途不要频繁改配置;
- 不要随意加载大型 Skills;
- 工具调用要控制范围,避免把无关结果塞进历史。
AI 编程的成本不只取决于输入了多少字,更取决于上下文如何组织、缓存是否命中、配置是否轻量、工具结果是否膨胀。把常驻内容压小,把低频内容改成按需加载,把长任务稳定放在同一个会话里,通常就能用同样额度完成更多工作。