<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Prompt Caching on Chico's Tech Blog</title><link>https://realtime-ai.chat/tags/prompt-caching/</link><description>Recent content in Prompt Caching on Chico's Tech Blog</description><image><title>Chico's Tech Blog</title><url>https://github.com/chicogong.png</url><link>https://github.com/chicogong.png</link></image><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 05 May 2026 11:00:00 +0800</lastBuildDate><atom:link href="https://realtime-ai.chat/tags/prompt-caching/index.xml" rel="self" type="application/rss+xml"/><item><title>Prompt Caching 实战:把推理成本和延迟砍下来</title><link>https://realtime-ai.chat/posts/prompt-caching/</link><pubDate>Tue, 05 May 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/prompt-caching/</guid><description>同一段 system prompt 反复 prefill 是在烧钱。这篇讲清 prompt caching 怎么命中、缓存断点放哪、Anthropic/OpenAI/Gemini/DeepSeek 各家计费与 TTL 差异,以及对延迟的影响。</description><content:encoded><![CDATA[<p>先说一个很多团队没算过的账。</p>
<p>假设你的 Agent 有一段 4000 token 的 system prompt:角色设定、工具说明、几个 few-shot 例子,雷打不动。用户每轮真正输入的,可能就 30 个字。一天 10 万次请求,这 4000 token × 10 万,就是 4 亿个 token 反复进入模型做同一件事——把固定前缀重新算一遍。</p>
<p>这部分计算,90% 是白烧的。因为前缀一模一样,模型每次算出来的中间结果(KV cache)也一模一样。<strong>Prompt caching 就是把这份中间结果存下来,下次直接复用。</strong> 它不改你的代码逻辑,不动模型质量,却能把输入侧成本砍掉一大半,顺带把首 token 延迟压下去。</p>
<p>2026 年,它依然是被严重低估的省钱手段。不是因为难,恰恰是因为太简单——简单到大家以为&quot;开了就行&quot;,结果断点放错位置,缓存全程没命中,白付一笔写入费还不自知。</p>
<h2 id="它到底缓存了什么">它到底缓存了什么</h2>
<p>要用对,先得知道模型推理分两个阶段。</p>
<p><strong>Prefill(预填充)</strong>:把你的整段 prompt 一次性喂进模型,逐 token 算出每一层的 KV(key/value)向量。这一步是并行的、算力密集的,prompt 越长越慢。</p>
<p><strong>Decode(解码)</strong>:基于 prefill 的结果,一个一个吐出回答 token。</p>
<p>Prompt caching 缓存的,就是 prefill 阶段算出来的那份 KV。注意:它缓存的是<strong>前缀</strong>,不是&quot;整个 prompt&quot;。模型从第一个 token 开始,一段一段比对——只要某个位置往前的内容和缓存里的完全一致,这段就能复用;一旦遇到第一个不一样的 token,从那里往后全部得重算。</p>
<pre class="mermaid">flowchart LR
  A["请求 prompt"] --> B{"逐 token 比对前缀"}
  B -->|"前缀命中"| C["复用 KV<br/>(便宜 + 快)"]
  B -->|"遇到第一个差异"| D["从这里往后重新 prefill"]
  C --> D
  D --> E["Decode 出 token"]
</pre><p>这张图就是 prompt caching 的全部精髓。所有的&quot;怎么用对&quot;,归结成一句话:<strong>让不变的东西待在前面,让变化的东西待在后面。</strong></p>
<h2 id="为什么前缀的顺序决定一切">为什么前缀的顺序决定一切</h2>
<p>各家请求体的拼接顺序是固定的:<strong>tools(工具定义)→ system(系统提示)→ messages(对话历史)</strong>。模型按这个顺序拼成一条长 prefix,再从头比对。</p>
<p>这意味着排在越前面的内容,越&quot;值钱&quot;——它一旦变化,后面所有东西的缓存全部作废。所以一个合格的可缓存 prompt,结构应该长这样,从稳定到易变排列:</p>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>放什么</th>
          <th>变化频率</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>最前</td>
          <td>工具定义、函数 schema</td>
          <td>几乎不变</td>
      </tr>
      <tr>
          <td>靠前</td>
          <td>system prompt、角色设定、few-shot 例子</td>
          <td>发版才变</td>
      </tr>
      <tr>
          <td>中间</td>
          <td>知识库片段、长文档、检索结果</td>
          <td>按会话变</td>
      </tr>
      <tr>
          <td>最后</td>
          <td>当前用户输入、本轮变量</td>
          <td>每次都变</td>
      </tr>
  </tbody>
</table>
<p>最常见的翻车,是把&quot;变化的东西塞进了前面&quot;。比如有人喜欢在 system prompt 顶部写一句 <code>当前时间:2026-05-05 11:23:07</code>。看着无害,实际是灾难——这个时间戳每秒都不一样,等于把整条 prefix 的第一个字就改了,<strong>后面 4000 token 的缓存全程一次都命中不了</strong>。同类的坑还有:user ID、请求 UUID、A/B 实验分组标记、随机打乱的 few-shot 顺序。</p>
<p>如果你确实需要给模型当前时间,把它放到对话消息的<strong>最后</strong>,跟用户输入待在一起。前面那一大坨稳定前缀,该缓存照样缓存。</p>
<h2 id="缓存断点放哪自动-vs-手动">缓存断点放哪:自动 vs 手动</h2>
<p>这里是各家最大的分歧,也是最容易用错的地方。</p>
<p><strong>自动派(OpenAI、Google 隐式缓存、DeepSeek)</strong>:你什么都不用做。系统自动识别请求之间的公共前缀,命中了就给你折扣。OpenAI 对超过 1024 token 的 prompt 自动启用;DeepSeek 是后端自动复用磁盘上的前缀缓存;Gemini 2.5 及以后的模型默认开启隐式缓存。</p>
<p>自动派的好处是零成本接入,坏处是<strong>没有保证</strong>。命中是&quot;尽力而为&quot;的——Google 自己也写明,隐式缓存只在系统判定命中时才给折扣,你无法强制。</p>
<p><strong>手动派(Anthropic,以及 Gemini 的显式缓存)</strong>:你得自己在 prompt 里打一个 <code>cache_control</code> 标记,告诉模型&quot;缓存到这里为止&quot;。这个标记叫<strong>缓存断点(cache breakpoint)</strong>。Anthropic 一个请求最多打 4 个断点。</p>
<p>手动派麻烦一点,但换来确定性:你明确知道哪一段被缓存了。</p>
<p>手动派最经典的错误,是<strong>把断点打在了会变的块上</strong>。比如这样的结构——一大段静态知识库,后面跟一个&quot;包含时间戳 + 用户输入&quot;的块,然后断点打在最后这个块上。结果时间戳每次都变,这个块的 hash 每次都不同,缓存永远写入、永远读不到。</p>
<p>正确做法:<strong>断点打在「最后一个跨请求不变」的块的末尾</strong>,而不是打在变化的块上。把静态前缀和动态后缀切开,断点卡在它们的交界处。</p>
<pre class="mermaid">flowchart TB
  subgraph 错误["错误:断点在变化块上"]
    A1["静态知识库 8000 token"] --> A2["时间戳 + 用户输入 ⟵ 断点"]
  end
  subgraph 正确["正确:断点在静态前缀末尾"]
    B1["静态知识库 8000 token ⟵ 断点"] --> B2["时间戳 + 用户输入"]
  end
</pre><p>还有一个多轮对话特有的坑:对话越滚越长,你的断点可能被挤到&quot;上一次写入位置&quot;20 多个块之外,超出回溯窗口,于是又一次踩空。多轮场景里,务必随着对话增长<strong>滚动更新断点位置</strong>,让它始终贴着最新的稳定边界。</p>
<h2 id="四家的计费和-ttl差得不小">四家的计费和 TTL,差得不小</h2>
<p>省多少、贵多少、能存多久——各家规则不一样,接之前一定要看清。</p>
<table>
  <thead>
      <tr>
          <th>厂商</th>
          <th>缓存写入</th>
          <th>缓存读取</th>
          <th>TTL</th>
          <th>模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Anthropic</td>
          <td>1.25×(5 分钟)/ 2.0×(1 小时)输入价</td>
          <td>0.1× 输入价</td>
          <td>5 分钟默认,可选 1 小时</td>
          <td>手动断点</td>
      </tr>
      <tr>
          <td>OpenAI</td>
          <td>不额外收费</td>
          <td>视模型 0.1×~0.5× 输入价</td>
          <td>几分钟,空闲淘汰</td>
          <td>自动</td>
      </tr>
      <tr>
          <td>Google Gemini</td>
          <td>隐式无写入费;显式按标准输入价计</td>
          <td>约 0.1× 输入价(2.5+ 省 90%)</td>
          <td>隐式自动;显式按 TTL 计存储费</td>
          <td>隐式自动 / 显式手动</td>
      </tr>
      <tr>
          <td>DeepSeek</td>
          <td>不额外收费</td>
          <td>约 0.1× 输入价(cache hit 价)</td>
          <td>后端管理,存储免费</td>
          <td>自动(磁盘)</td>
      </tr>
  </tbody>
</table>
<p>几个要点单独拎出来说。</p>
<p><strong>Anthropic 是唯一对&quot;写入&quot;收钱的。</strong> 写一次缓存比正常输入贵 25%(5 分钟档)。这意味着如果你的 prompt 写进去之后根本没被复用就过期了,你是<strong>净亏</strong>的——多付了 25%,一分钱折扣没拿到。所以 Anthropic 的缓存只对&quot;高频复用同一前缀&quot;的场景划算。读取确实便宜,只要 1/10 输入价。</p>
<p><strong>TTL 是个隐形雷区。</strong> Anthropic 默认 TTL 在 2026 年初从 1 小时悄悄变回了 5 分钟,不少团队因此缓存创建成本涨了 20%~30% 还没察觉。5 分钟意味着:如果你的请求间隔超过 5 分钟,缓存早凉了,每次都是冷启动重新写入。好消息是 TTL 的时钟会在每次命中时<strong>重置</strong>——只要请求够密,缓存能一直续命。需要长间隔复用的,Anthropic 可以花 2 倍写入价买 1 小时 TTL。</p>
<p><strong>OpenAI 和 DeepSeek 对开发者最省心</strong>:不收写入费,自动命中,几乎是&quot;白送的折扣&quot;。DeepSeek 2026 年 4 月把 cache hit 价格再砍到发布价的 1/10,V4-Flash 上缓存命中把输入成本从 $0.14 压到 $0.0028 每百万 token——98% 的降幅。</p>
<p><strong>省钱幅度的体感</strong>:输入侧能省 50%~90%。具体看你的 prompt 里&quot;固定前缀占比&quot;有多高——前缀越长、变量越短,省得越狠。一个 8000 token 知识库 + 50 token 提问的 RAG 应用,几乎是为 prompt caching 量身定做的。</p>
<h2 id="别忘了它还能压延迟">别忘了它还能压延迟</h2>
<p>省钱是它最出名的好处,但对实时类应用,<strong>降延迟才是关键收益</strong>。</p>
<p>命中缓存时,prefill 这一步被整段跳过。前面说过,prefill 是算力密集的,prompt 越长越慢。跳过它,首 token 延迟(TTFT)的下降立竿见影——DeepSeek 给过一个数据:128K 的长 prompt 高度命中缓存时,首 token 延迟从 13 秒压到 500 毫秒。</p>
<p>这对语音 Agent、实时对话这种&quot;首 token 延迟就是及格线&quot;的场景,意义比省钱大得多。一个挂着长 system prompt 和工具定义的语音助手,把这部分缓存住,等于每一轮对话都省掉了几千 token 的 prefill 时间。如果你正在为 TTFT 抠毫秒,prompt caching 应该排在优化清单的前列。</p>
<p>不过有个前提:<strong>省下来的延迟,得真的有缓存可命中</strong>。冷启动那一次(第一次写入)不但不快,Anthropic 那边还更慢更贵。所以 prompt caching 优化的是&quot;稳态延迟&quot;,不是&quot;首次延迟&quot;。</p>
<h2 id="一份排查清单为什么我没命中">一份排查清单:为什么我没命中</h2>
<p>如果你接了 prompt caching,但账单没怎么降,大概率是踩了下面某一条。按顺序自查:</p>
<ol>
<li>
<p><strong>前缀里有变量。</strong> 时间戳、UUID、user ID、随机数——但凡有一个混进了 system prompt 或工具定义,整条缓存作废。把它们全部赶到 messages 末尾。</p>
</li>
<li>
<p><strong>断点打错位置(手动派)。</strong> 断点要打在&quot;最后一个不变块&quot;的末尾,不是打在变化块上。切开静态与动态的交界。</p>
</li>
<li>
<p><strong>请求间隔超了 TTL。</strong> Anthropic 默认才 5 分钟。低频请求(比如定时任务、长间隔轮询)很可能每次都冷启动。要么提高请求密度,要么买长 TTL。</p>
</li>
<li>
<p><strong>prompt 太短没够门槛。</strong> OpenAI 要超过 1024 token 才会自动缓存。短 prompt 本来也省不了多少,不用纠结。</p>
</li>
<li>
<p><strong>工具定义或 system prompt 偷偷变了。</strong> 多人协作时,有人调了一下工具描述、改了个标点,排在最前面的 tools 段一变,后面全塌。把可缓存前缀<strong>当成发布制品来管理</strong>,别让它随手改。</p>
</li>
<li>
<p><strong>few-shot 例子顺序不固定。</strong> 有些代码每次随机打乱 few-shot 顺序&quot;增加多样性&quot;——这会让前缀每次都不同。要缓存,就固定顺序。</p>
</li>
</ol>
<h2 id="落地建议">落地建议</h2>
<p>不用一上来就上复杂方案。三步走:</p>
<p><strong>第一步,把 prompt 重新排版。</strong> 不管你用哪家,先按&quot;工具 → system → 知识库 → 用户输入&quot;从稳到变重排一遍,把所有变量揪到最后。光这一步,自动派(OpenAI / DeepSeek / Gemini)就能开始命中了,一行代码没动。</p>
<p><strong>第二步,手动派打好断点。</strong> 用 Anthropic,就在静态前缀末尾打 <code>cache_control</code>;多轮对话记得滚动更新断点。</p>
<p><strong>第三步,盯住命中率。</strong> 各家 API 响应里都会返回 cache 相关字段(命中 token 数、写入 token 数)。把&quot;缓存读取 token / 总输入 token&quot;做成一个监控指标。它要是长期偏低,回到上面那份清单逐条查。</p>
<p>最后提醒一句取舍:prompt caching 不是&quot;开了就一定赚&quot;。对 Anthropic 这种收写入费的厂商,低频、前缀短、变量多的场景,反而可能亏。先搞清楚自己的流量形态——<strong>高频复用同一份长前缀,才是它的主场</strong>。判断对了,这是你能拿到的、性价比最高的一次优化:不掉质量,不改逻辑,省一半成本,还顺手降了延迟。</p>
<hr>
<p>参考资料:</p>
<ul>
<li><a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching">Prompt caching - Claude API Docs</a></li>
<li><a href="https://github.com/anthropics/claude-code/issues/46829">Cache TTL silently regressed from 1h to 5m · Issue #46829</a></li>
<li><a href="https://openai.com/index/api-prompt-caching/">Prompt Caching in the API | OpenAI</a></li>
<li><a href="https://developers.openai.com/api/docs/guides/prompt-caching">Prompt caching | OpenAI API</a></li>
<li><a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/context-cache/context-cache-overview">Context caching overview | Google Cloud</a></li>
<li><a href="https://developers.googleblog.com/gemini-2-5-models-now-support-implicit-caching/">Gemini 2.5 Models now support implicit caching - Google Developers Blog</a></li>
<li><a href="https://api-docs.deepseek.com/news/news0802">DeepSeek API introduces Context Caching on Disk</a></li>
</ul>
]]></content:encoded></item></channel></rss>