13. Prompt Caching 怎么影响 Prompt 设计?为了高命中率应该怎么写 prompt?
整理 Prompt Caching 命中机制对 prompt 模板设计的影响。
简单回答
Prompt Caching 命中的硬性条件是 从开头到某个截断点字符完全一致——任何细微变化(多一个空格、字段顺序换、值动了一个字符)都让前缀缓存失效。所以为了高命中率,prompt 设计要遵循 把不变的放最前、变化的放最后 这个原则。具体做法包括:固定的 System Prompt 永远在最前面、共享的 Few-shot 示例和工具定义紧跟其后、可能变化的 RAG 文档放中间但要按"是否常用"分级、用户的 query 永远放最后。多租户场景下租户 ID 等信息要在 cache 边界后才出现,否则每个租户都是独立缓存命中率为零。
详细解释
Prompt Caching 的硬性约束
主流 Provider 的 Prompt Caching(OpenAI、Anthropic、DeepSeek、Google)在机制细节上略有不同,但有共同的硬性约束:
前缀完全一致:从 prompt 开头到 cache 边界点,每个 token 必须完全相同。多一个空格、改一个字符、换一个字段顺序都让命中失败。
最小长度:通常要求至少 1024 token(OpenAI)或 1024 token(Anthropic 默认),更短的内容不缓存。
TTL:Anthropic 默认 5 分钟(可申请延长到 1 小时),OpenAI 默认 5-10 分钟,DeepSeek 几小时。超时后缓存被清除,下次还是 cold。
计费:
- OpenAI:缓存读取的 input token 单价是正常的 50%
- Anthropic:缓存读取 10%、缓存写入 125%(首次写入比正常贵 25%)
- DeepSeek:缓存读取约 10%、缓存写入和正常 input 价格一样
这些规则决定了一个事实:只要 prompt 模板设计不当,缓存命中率为零,省钱的潜力就完全消失。
设计原则:从前到后,从静到动
最重要的原则是把 prompt 内容按"变化频率"从前到后排列:
cache 命中的边界会落在第一次出现"变化内容"的位置——前面的部分都被缓存复用。
容易踩的具体陷阱
用户名/ID 拼在 System Prompt 开头
错误示范:
每个用户的 user_id 不同,整个 system prompt 从开头就不同,缓存命中率为零。
正确做法:
把 user_id 移到用户消息里。System Prompt 部分被缓存共享。
时间戳放在前面
错误示范:
current_time 每次调用都不同,缓存永远不命中。
正确做法是把时间放到 user message 里,或者只在必要时才注入。
字段顺序不稳定
JSON 字段或 markdown section 的顺序如果不固定(比如有时候 metadata 在前有时候在后),同一份内容的 token 序列就不同,缓存失效。要规范化字段顺序——用代码强制按固定顺序组装 prompt。
不必要的随机性
prompt 里如果有"随机选 3 个示例"或者"随机一句问候"这种逻辑,每次缓存都不命中。要么固定示例选择(基于 query 哈希),要么把动态部分挪到 cache 边界后。
多语言混用
如果一个应用既有中文用户又有英文用户,System Prompt 是中英双语的还是分别的?双语会让 prompt 变长(缓存写入成本上升),分别两套则不同语言用户独立缓存(共享变差)。需要根据流量比例权衡。
多租户场景的特殊处理
SaaS 多租户场景下,每个租户可能有定制化的 System Prompt(不同行业规则、不同品牌语调)。这种情况下缓存设计要分层:
Layer 1:完全通用部分(所有租户共享)—— 安全规则、基础对话规范 Layer 2:租户级共享(同一租户的所有用户共享)—— 租户的 System Prompt、行业规则 Layer 3:会话级(同一会话的多轮共享)—— 会话历史
物理上的实现是把 prompt 按这三层拼接:
这样租户 X 的所有用户共享 Layer 1 + Layer 2 的缓存,租户 Y 共享 Layer 1(如果 Layer 1 真的完全一样)+ 自己的 Layer 2。
如果 Layer 1 用得好,跨租户也能共享相当大一段缓存——这是大型 SaaS 应用 prompt 缓存命中率高的关键。
RAG 场景的设计
RAG 把检索到的文档拼进 prompt,这部分天然多变(每个 query 检索结果不同),看起来无法缓存。但有些设计能提升命中:
热门文档前置:如果检索到的文档里有些是高频出现的(FAQ、政策文档),可以把这部分单独前置。结构是:
热门部分能缓存,非热门部分每次重新算。
检索结果排序稳定化:如果检索结果按"相关性 + 文档 ID"排序(而不是只按相关性),同一个 query 多次检索的 prompt 会一致,能命中。
Document caching 模式:把文档作为单独的"system content"用 Anthropic 的 cache_control 标记,配合 Anthropic 的多块缓存能力。
工具调用场景
Tool Calling 应用里工具定义经常占 prompt 几百到几千 token,是缓存的好目标。
工具定义应该:
- 规范化字段顺序(参数 schema、描述按固定顺序)
- 规范化空白字符(pretty-print 还是 minify 要统一)
- 统一不变直到工具集真正改动
很多 SDK 自动序列化工具定义,可能产生不稳定的 JSON 字符串顺序——要么用 sorted JSON、要么手动控制。否则同一组工具不同次序列化产生不同 prompt,缓存失效。
如何监控命中率
工程上要监控的关键指标:
Cache Hit Rate:缓存命中的 input token 占总 input token 的比例。健康的应用应该 > 60%,重度共享场景能到 90%+。
Per-template Hit Rate:按 prompt 模板分类的命中率。如果某个模板命中率突然降低,可能是模板被改动破坏了缓存。
Cache Amortization:每次缓存写入被复用的次数。Anthropic 缓存写入贵 25%,至少要复用 4 次才回本。低于这个数字开缓存反而亏本。
TTL 命中分布:命中是发生在缓存写入后多久?如果大多数命中在 30 秒内,5 分钟 TTL 就够;如果跨小时命中多,要考虑申请更长 TTL。
监控这些指标后能反向优化 prompt 模板——发现命中率低的部分检查是不是有不必要的变动内容。
跨 Provider 的差异
不同 Provider 的 Prompt Caching 在使用上有微妙差异:
OpenAI:自动缓存(不需要显式标记),按内容哈希。前缀完全一致就命中。
Anthropic:需要显式 cache_control 标记缓存边界。可以多个 cache breakpoint。控制更精细。
DeepSeek:自动缓存,行为接近 OpenAI。
Google Gemini:cache 是显式 API(createCachedContent),缓存粒度大块(百万 token 级),TTL 可调。
迁移时要对应 Provider 的 API。Anthropic 的 cache_control 比 OpenAI 显式但更灵活。
面试时可以这样答
Prompt Caching 命中的硬性条件是从开头到 cache 边界字符完全一致——多一个空格、改一个字符都让前缀缓存失效。所以 prompt 模板设计的核心原则是把不变的放最前、变化的放最后。
具体顺序是 System Prompt → Few-shot 示例 → 工具定义 → 共享文档 → 会话历史 → 当前用户 query。前面越稳定后面越变化。
几个常见陷阱。用户 ID/时间戳拼在 System Prompt 开头会让每个用户每次调用都是冷缓存,应该挪到 user message 里。JSON 字段顺序不稳定会破坏命中,要规范化序列化。"随机选示例"这种逻辑要么固定(按 query 哈希)要么挪到 cache 边界后。
多租户场景要分层设计。Layer 1 完全通用部分(所有租户共享)→ Layer 2 租户级共享 → Layer 3 会话级。这样跨租户能共享 Layer 1,租户内能共享 Layer 2。SaaS 应用的命中率优化关键就是 Layer 1 设计得好。
RAG 场景下热门文档前置能让常被检索到的内容部分进入缓存。检索结果排序要稳定(按相关性 + 文档 ID 而不是只按相关性)。
Tool Calling 里工具定义占 prompt 大头是缓存好目标,但 SDK 自动序列化可能产生不稳定的 JSON 顺序,要 sorted JSON 或手动控制。
监控指标包括 Cache Hit Rate(健康应用 > 60%)、Per-template Hit Rate(突然下降说明模板被改动)、Cache Amortization(Anthropic 缓存写入贵 25% 至少要复用 4 次回本)、TTL 命中分布(决定是否要申请长 TTL)。
Provider 差异上 OpenAI 自动缓存、Anthropic 显式 cache_control 更灵活、Gemini 是显式大块 cache、DeepSeek 接近 OpenAI。迁移要对应 API。
常见追问
- Anthropic 的多个 cache breakpoint 怎么用?什么场景下值得设多个?
- 缓存内容里包含了用户的 PII,怎么处理 GDPR 删除请求?
- Prompt 模板更新(比如 System Prompt 改了一句)时怎么平滑过渡?让旧缓存自然过期还是主动刷新?