乐于分享
好东西不私藏

AI工具这么成熟,还需要关注prompt cache吗

AI工具这么成熟,还需要关注prompt cache吗

SDD(Spec-Driven Development,规范驱动开发)也火了一段时间了,你可能听过但是不一定用过,核心是:先写一份详细的需求文档,让 AI 按文档实现。

但很多人没注意到一个问题:

SDD 的核心是把大段 spec 塞进 prompt,而这恰恰是缓存命中率的杀手。


● ● ●

先搞清楚 prompt cache 在做什么

根据 Anthropic 官方文档,机制很简单:

API 服务商把你请求的前半段存下来,下次前半段没变就直接用缓存。

为什么前半段能缓存?

大模型推理分两步:先把输入的 token 全部转成 KV Cache(键值缓存),再基于这个缓存逐个生成输出。

KV Cache 是 Transformer 架构的产物。每一层注意力计算时,输入的每个 token 都会算出一对 Key 和 Value 向量存起来。后续生成新 token 时,要用这些 Key/Value 和当前 token 做注意力计算。

这个前置的 KV Cache 计算是重复且耗时的——如果两次请求的前缀一模一样,算出来的 KV Cache 也一模一样。

所以 API 服务商把前缀的 KV Cache 存下来,下次遇到相同前缀直接复用,跳过整个前置计算阶段:

请求 1:[系统提示词 + 工具列表 + spec] + 用户消息A

         ↑ 这部分算出的 KV Cache 存入缓存

请求 2:[系统提示词 + 工具列表 + spec] + 用户消息B

         ↑ 前缀没变 → 直接取缓存,不重算

         ↑ 只算新增部分的 KV Cache

缓存命中时省掉的不只是钱——而是整个前缀的前向计算。 这就是为什么读取只要 0.1× 的价格:服务商几乎不用算力,只花了一次内存读取。

命中和不命中,速度差多少?

拿两组真实模型算一下。假设你的 system prompt + 工具列表 + spec 共 20000 token,每轮用户消息 2000 token。

GLM-5.2(智谱,推理速度约 100 token/s)

场景
前缀处理
耗时
总延迟
不命中(冷启动)
20000 token 全量计算 KV Cache
~200 秒前缀 + 推理
极慢
命中缓存
0 token 前缀计算(直接读取)
~0 秒前缀 + 推理
快 10 倍以上

GPT-5.6(OpenAI,推理速度约 120 token/s)

场景
前缀处理
耗时
总延迟
不命中(冷启动)
20000 token 全量计算 KV Cache
~167 秒前缀 + 推理
极慢
命中缓存
0 token 前缀计算(直接读取)
~0 秒前缀 + 推理
快 10 倍以上

这里的关键不是输出速度,而是 TTFT(Time To First Token,首字延迟)。 不命中时,模型要先算完 20000 个 token 的 KV Cache 才能吐出第一个字。命中时,这 20000 个 token 的计算直接跳过。

实际感受:不命中时你可能等十几秒才看到回复开始,命中时几乎是秒回。20 轮对话累计下来,光等待前缀重算的时间就能差几分钟。

所以 prompt cache 的价值有两层:省钱是表层,省时间是底层。 对于交互式 Agent,用户等待体验比 token 费用更致命。

缓存边界(cache breakpoint)把 prompt 一分为二:

静态前缀(可缓存)
动态后缀(不缓存)
① 系统提示词
④ 本轮用户消息
② 工具列表
⑤ 最新工具结果
③ CLAUDE.md / spec
⑥ 新的对话历史
每次都一样 → 缓存命中每次都变 → 全价计费

定价模型(官方文档):

操作
费用
正常输入
缓存写入
1.25×(第一次贵 25%)
缓存读取
0.1×(后续省 90%)
TTL
5 分钟,每次命中自动续期

只要同一前缀被请求超过两次,缓存就开始省钱。


● ● ●

SDD 为什么容易破坏缓存

SDD 的典型做法是把 spec 文档注入到 prompt 里。问题出在 spec 放在哪:

❌ SDD 常见错误做法

system = [

    "你是一个资深工程师...",      ← 这部分固定

    "项目技术栈:Flask + ...",    ← 这部分固定

    spec_document,               ← 问题在这!

]

messages = [用户消息...]          ← 这部分变化

看起来 spec_document 也固定的,应该能缓存。但实际问题有两个:

问题一:spec 经常修改

SDD 的核心是"spec 先行"。意味着你在开发过程中会反复修改 spec——加一条需求、改一个接口、调一个字段。每次改 spec,整个缓存前缀全部失效。

轮次
spec 版本
缓存状态
第 1 轮
spec v1
缓存写入(贵 25%)
第 2 轮
spec v1
缓存命中(省 90%)✓
第 3 轮
改了 spec → v2
缓存失效!重新写入
第 4 轮
spec v2
缓存命中 ✓
第 5 轮
又改了
又失效...

问题二:spec 太大

有人测试过 Claude Code 的基础开销是 20000-30000 token(来源)。如果你的 spec 再加 5000 token,每次缓存写入的成本就更高。


● ● ●

正确做法:把 spec 拆成静态和动态

关键思路:不要把整个 spec 当成一个不可分割的块。把它拆开:

✅ 优化后的 SDD + 缓存策略

system = [

    身份声明,                    ← 永远不变 → 深层缓存

    工具列表,                    ← 很少变 → 深层缓存

    全局架构规范,                 ← 很少变 → 深层缓存

    ─── cache_control 断点 ───

    当前迭代的需求文档,           ← 会变 → 浅层缓存

]

messages = [具体任务...]          ← 每次变 → 不缓存

Anthropic API 支持最多 4 个 cache_control 断点(文档)。利用多断点把 prompt 分成多层:

system=[# 第一层:几乎永远不变(深度缓存){"type":"text","text":identity_and_tools,"cache_control":{"type":"ephemeral"}},# 断点 1# 第二层:版本迭代时才变(浅层缓存){"type":"text","text":architecture_rules,"cache_control":{"type":"ephemeral"}},# 断点 2# 第三层:每次开发会改(更浅缓存){"type":"text","text":current_sprint_spec,"cache_control":{"type":"ephemeral"}},# 断点 3]messages=[...]# 不缓存

改 spec 时只有第三层之后的缓存失效,前两层仍然命中。


● ● ●

算一笔账

假设你的 SDD 项目:

  • 身份 + 工具 + 架构规范:15000 token(几乎不变)
  • 当前迭代的 spec:5000 token(每 2-3 天改一次)
  • 每轮用户消息 + 工具结果:平均 3000 token

不做缓存分层(整个 spec 一起缓存):

每次改 spec,全部 20000 token 重新写入。20 轮对话改了 5 次:

项目
计算
费用
5 次写入
20000 × 1.25 × 5
125,000
15 次命中
20000 × 0.1 × 15
30,000
消息部分
3000 × 20
60,000
总计215,000 token

做了缓存分层(三层断点):

改 spec 时只有第三层失效(5000 token),前两层仍然命中:

项目
计算
费用
第一层写入(1次)
15000 × 1.25 × 1
18,750
第一层命中(19次)
15000 × 0.1 × 19
28,500
spec 写入(5次)
5000 × 1.25 × 5
31,250
spec 命中(15次)
5000 × 0.1 × 15
7,500
消息部分
3000 × 20
60,000
总计146,000 token(省 32%)

spec 越大、改动越频繁,分层缓存的收益越高。


● ● ●

上下文压缩也和缓存有关

Claude Code 在每轮调模型前会做上下文压缩。它的设计思路值得借鉴:压缩只处理对话历史(messages),不会动 system prompt 前缀——所以缓存依然命中,只是 messages 部分更省。

这给了 SDD 一个启示:spec 内容如果太大,不要全部留在对话历史里。让 AI 读完后压缩成摘要,保留关键约束就行。

压缩的策略是从轻到重分四步:

步骤
做什么
成本
① 大结果落盘
工具输出超阈值就存到文件,上下文里只留预览
0 API 调用
② 超长结果裁剪
早期的大结果只保留头尾
0 API 调用
③ 清理冗余
去掉格式噪声和零碎内容
0 API 调用
④ 历史摘要
全部不够才压成摘要
1 API 调用

前三步是纯文本操作,最后一步才花一次 API 调用。不是等报错后才救火,而是每次调用前都体检。


● ● ●

SDD + 缓存的实操建议

1. spec 分层放置

层级
内容
缓存策略
全局架构
不变
缓存最深层
迭代需求
偶尔变
缓存浅层
具体任务
每次变
不缓存,放 messages

2. 不要在 spec 里写会变的东西

  • ❌ "当前时间是 2024-01-15,使用 React 18.2.0"
  • ✅ "使用 React 18,具体版本看 package.json"

3. spec 改了之后主动刷新缓存

Anthropic API 支持 max_tokens: 0 做一次空写入(文档):发一个不生成输出的请求,只为了把新 spec 写入缓存。后续请求就能命中了。

4. CLAUDE.md 保持精简

真实案例(来源):有用户把 1358 行规则精简到 807 行(减 41%),方法就是把流程性规则从 CLAUDE.md 移到按需加载的 Skills。

CLAUDE.md 就是你的全局 spec——它越大,每次缓存写入越贵。


● ● ●

到底需不需要 SDD?

很多人会说:"我提示词写得差不多,需求就能完成,为什么要费劲写 spec?"

这个问题要分两个角度回答:精准开发质量和缓存成本。

从精准开发的角度:看你项目的复杂度

先说结论:SDD 不是越多越好,也不是完全不需要——取决于你的项目复杂度和你对结果的要求。

不需要 SDD 的场景

提示词写得差不多确实能完成需求——当任务是单次、简单、边界清晰的时候。

"帮我用 Flask 写一个用户登录接口,密码用 bcrypt,

返回 JWT,参照 auth/middleware.py 的风格"

→ 这种任务不需要 spec,模型直接就能写好。

模型已经足够聪明,简单的 CRUD 不需要写文档。 你给它上下文和约束,它就知道怎么做。Claude Code 自己用 515 段动态拼装而不是一份大 spec 就证明了这一点——它只在需要时才加载详细规则。

需要 SDD 的场景

当任务涉及多模块协作、跨迭代维护、多人开发时,不写 spec 会出问题:

场景:一个电商后台,30 个 API,5 张核心表,

涉及权限、支付、库存、订单状态机。

不写 spec 的后果:

- 第 1 轮模型帮你写了用户模块

- 第 2 轮你让它加支付模块

- 第 3 轮改库存逻辑时,它忘了第 1 轮的表结构

  → 因为上下文被压缩了,早期信息丢了

- 结果:接口冲突、命名不一致、重复代码

SDD 的真正价值不是"让 AI 写得更好",而是"让 AI 在多轮迭代中保持一致"。 spec 是项目的记忆载体——模型上下文会丢,但 spec 文件不会。

判断标准

你的情况
要不要 SDD
原因
单次任务,写完即走
不需要
模型够聪明,直接给上下文就行
3-5 轮能完成的小项目
不需要
上下文窗口够大,信息不会丢
超过 10 轮的中大型项目
需要
上下文会被压缩,spec 保证一致性
多人协作、跨迭代维护
必须
spec 是团队共识,不是 prompt
探索性开发(需求不确定)
不要用
spec 太早写死会浪费时间,边做边定

一句话:SDD 适合"需求明确但实现复杂"的项目。需求不明确时写 spec 等于浪费——你改 spec 的时间比写代码还多。

从 prompt cache 的角度:看你调 API 的方式

SDD 对缓存的影响,取决于你的调用模式:

用 Claude Code / Cursor(不直接调 API)

  • 不用管缓存——工具层帮你做了
  • 但 CLAUDE.md(你的全局 spec)别写太大
  • 1358 行精简到 807 行,省 41% 的开销

工具用户不需要管 cache,但需要管 spec 的大小。

调 API 做简单应用(短对话)

  • 对话不超过 5 轮 → 缓存收益很小
  • 写入贵 25%,至少要命中 2 次才回本
  • 5 轮里改了 2 次 spec → 缓存基本没省到

短对话场景,SDD 对缓存的影响可以忽略。 不要为了省缓存而过度设计 spec 分层。

调 API 做长周期 Agent(20+ 轮对话)

这是 SDD + 缓存真正重要的场景。

方案
改 spec 时的开销
20 轮总费用
不做 spec 分层
全部重新写入
215,000 token
做了三层断点
只有第三层失效
146,000 token(省 32%)

spec 越大、改动越频繁,分层缓存收益越高,极端情况下差距 3-5 倍。

长对话场景,SDD 的缓存管理直接决定项目能不能跑下去。 不做分层,token 费用可能让你被迫中断开发。

两个角度的结合:决策矩阵

简单项目
中型项目
大型/长期项目
精准开发
不需要 spec
建议写轻量 spec
必须写完整 spec
缓存成本
不用管
建议分层
必须分层
结论
直接写提示词
写 spec + 简单分层
完整 SDD + 多断点缓存

大部分人觉得"提示词写得差不多就行",是因为他们的项目还在"简单"那一列。 项目变复杂了,不写 spec 的后果不是"质量差一点",而是"模型在多轮迭代中逐渐失控"——上下文被压缩、早期决策被遗忘、命名和结构越来越不一致。

SDD 不是为了让模型更聪明,而是为了在项目变复杂时保持一致性。Cache 不是为了省钱,而是为了在长对话中保持可持续。


实际业务中 SDD 怎么落地?

很多人理解了缓存原理,但到了实际项目还是不知道:spec 放哪?用 Skill 沉淀还是单独维护 docs?CLAUDE.md 到底怎么加载?项目级 spec 每次都要手动告知文件位置吗?

CLAUDE.md 的加载机制

先回答最基础的:CLAUDE.md 到底是怎么被加载进 prompt 的?

Claude Code 有两级 CLAUDE.md,启动时自动加载:

层级
位置
加载时机
能否关闭
全局
~/.claude/CLAUDE.md
每次启动自动加载
不能
项目级
项目根目录 /CLAUDE.md
进入项目目录自动加载
不能

两级 CLAUDE.md 都是自动加载的,不需要你每次手动告知。 全局管你的个人偏好(比如"所有函数必须有类型标注"),项目级管项目约定(比如"技术栈是 Flask + PostgreSQL")。两者会拼接在一起注入 system prompt。

关键问题:项目级 spec 文档需要每次手动告知位置吗?

不需要——如果你在 CLAUDE.md 里写好了指引。

CLAUDE.md(示例)

技术栈:Flask + SQLAlchemy + PostgreSQL

全局规则:函数必须有类型标注

## Spec 文档索引

- API 设计:docs/api-spec.md

- 数据模型:docs/data-model.md

- 认证模块:docs/module-auth.md

- 支付模块:docs/module-payment.md

开发新功能时,先读 docs/ 下对应的 spec 文件。

不要猜架构,一切以 spec 文档为准。

这段话只占 200 token,但模型每次都知道去哪找详细 spec。 你不需要每次开发都打"请先读 docs/api-spec.md"——CLAUDE.md 里的指引已经告诉它了。

工作流变成:

你说:"帮我加一个优惠券功能"

模型自动:读 CLAUDE.md → 发现 docs/ 有 spec 索引

         → 读 docs/api-spec.md 了解 API 规范

         → 读 docs/data-model.md 了解表结构

         → 按 spec 实现

你只需要说一句话,模型自己会去找 spec。 这就是 SDD 落地的关键:不是把 spec 灌进 prompt,而是在 CLAUDE.md 里建好索引,让模型按需读取。

Spec 沉淀的三层载体

Claude Code 有三层 spec 载体,各管不同的事:

载体
放什么
加载方式
缓存影响
CLAUDE.md
全局架构、技术栈、spec 索引
每轮自动加载
越大越贵
docs/ 目录
详细 spec 文档(API 设计、数据模型)
模型按需读取
用到才加载
Skills
流程规则(提交规范、部署流程)
触发时才加载
不触发不加载

具体操作:

项目结构

├── CLAUDE.md          ← 全局架构 + spec 索引(精简)

├── docs/

│   ├── api-spec.md    ← API 设计文档

│   ├── data-model.md  ← 数据模型定义

│   └── module-auth.md ← 认证模块详细 spec

└── .claude/skills/

    ├── git-workflow   ← 提交规范(触发时才加载)

    └── deploy-check   ← 部署检查清单(触发时才加载)

Skill vs docs 怎么选?

你要放的东西
放哪
原因
API 设计文档、数据模型
docs/
是参考资料,模型读文件就行
提交规范、部署流程、测试模板
Skill
有明确的触发时机,不触发时不该占 token
编码约定、技术栈信息
CLAUDE.md
每轮都要知道,但必须精简
临时需求、当前迭代任务
messages
每次都变,不缓存

判断标准很简单:是不是每轮都用?是 → CLAUDE.md;有明确触发时机 → Skill;是参考文档 → docs/。

调 API 做产品的场景:spec 分层注入

如果你直接调 Anthropic API 做 Agent 产品,没有 CLAUDE.md 和 Skill,那分层靠 cache_control 断点实现:

第一层(深度缓存):身份 + 工具定义 + 全局规范

                    → 几乎不变,放最前面

第二层(浅层缓存):当前版本架构 spec

                    → 版本迭代才变

第三层(不缓存):  当前迭代的具体需求

                    → 每次开发都可能改

messages = [本轮对话]  → 完全不缓存

不管哪种工具,核心原则一致:不变放前面,少变放中间,多变放后面。


● ● ●

小结

问题
答案
SDD 会影响缓存吗?
会,spec 越大越频繁改,缓存失效越严重
怎么解决?
spec 分层放置,利用多断点缓存
缓存分层能省多少?
spec 频繁修改场景省 30-50%
最关键的原则?
不变的放前面,会变的放后面,中间设断点

SDD 的价值在于"先想清楚再写代码"。但想清楚的内容也要分层管理——不然你省下的思考时间全花在 token 费用上了。


参考资源:

  • Anthropic prompt caching 官方文档:https://platform.claude.com/docs/en/build-with-claude/prompt-caching
  • Claude Code token 优化实战:https://boringbot.substack.com/p/how-to-save-millions-in-claude-tokens
  • Prompt caching 实用指南:https://medium.com/@mcraddock/unlocking-efficiency-a-practical-guide-to-claude-prompt-caching-3185805c0eef