博客

實踐中的 Prompt Caching:什麼會被複用、什麼會破壞快取,以及如何驗證

快取不是撥一下開關;它是字首複用,只有渲染結果匹配的字首才會命中。下面講實際被複用的是哪些內容、那些靜默破壞快取的寫法,以及如何在你自己的用量資料裡驗證命中。

實踐中的 Prompt Caching:什麼會被複用、什麼會破壞快取,以及如何驗證

Prompt caching 本身並不減少任何東西。當一次請求渲染後的字首與服務商已經處理過的某個字首匹配時,它會複用已有工作;不匹配時,它靜默失敗。所以實踐歸結為三個問題:什麼算作字首、什麼會破壞匹配、以及你如何驗證命中真的發生了。本指南用你在生產中最可能遇到的兩種機制回答這三個問題,並把機制扣在各家服務商自己的文件上,而不是傳說上。用得好,它也是少數能在不改變產品行為的前提下降低 LLM API 成本的槓桿之一,但它是你第二個要修的東西,永遠不是第一個。

Prompt caching:兩種機制,一條規則

OpenAI 是自動快取的。按照該服務商的 prompt caching 指南,快取會複用你請求渲染後的字首,其中可以包含文本、圖片、文件和受支援的音訊,而複用要求整個渲染字首完全匹配。字首不只是你的第一條訊息:它由隱藏的 system 內容、工具定義與 schema、developer 指令以及對話歷史拼裝而成,大致按這個順序。如果內容或相關設定在某個斷點之前發生變化,該變化之後的一切都無法與已有條目匹配。存在一個最小可快取長度,它隨模型而異,所以當前閾值屬於官方頁面,而不屬於你的記憶。

Anthropic 則靠顯式標記工作。它的 prompt caching 文件描述了兩種模式:自動快取——一個頂層cache_control欄位把斷點放在最後一個可快取塊上,並隨對話增長向前移動;以及在你想要細粒度控制時,在個別內容塊上顯式設定 cache_control 斷點。有兩點機制在實踐中很重要。寫入只發生在你的斷點處,而雜湊是累積的,所以改動斷點處或之前的任何塊,都會讓下一次請求產生不同的雜湊。讀取則從你的斷點向後看,檢查更早的位置是否存在先前請求已經寫入的條目。這種不對稱正是關鍵:一次請求每個斷點最多寫入一個快取條目,之後每個匹配該雜湊的請求都複用它,而標記之後的一切都會被重新處理。把標記放對,讀取自然跟上。

管道不同,規則同一條:請求中穩定的部分必須放在前面,所有易變的東西必須放在它們之後。快取生命週期選項和可快取長度閾值按服務商分別記錄,並且會隨時間變化,所以請到各家頁面上讀當前值,而不是從任何地方照抄,包括不要照抄本文。如果你通過 String AI 這樣的相容平臺消費模型,平臺的文件就是約定,說明哪些行為在其端點上得以保留。

Prompt 快取不生效?那些靜默的破壞者

快取未命中不會拋錯。它只是把一切重新處理一遍,而你唯一的線索是用量資料裡的某個欄位。下面是破壞字首最常見的幾種寫法,每一種都配了修法。

字首裡帶時間戳、UUID 或隨機種子。任何每次請求都變的東西,都會毒化它之後的每一個 token。症狀:prompt 看起來一模一樣,命中率卻接近零。修法:把易變的值移到斷點之後,或者乾脆去掉。

使用者輸入被拼在穩定指令前面。把使用者的問題拼在 system 規則之前,意味著從第一個易變 token 起字首就不同了。症狀:system prompt 看起來被快取了,其實沒有。修法:凍結模板的前部,把動態內容追加到最後。

每次請求都改工具定義。OpenAI 的指導很直接:改動工具名稱、描述、schema 或順序都會改變字首,而工具位於整摞內容的靠前位置。症狀:快取未命中開始於某人“整理”工具列表的那一天。修法:把工具塊作為一個整體做版本管理,別隨手重排它。

多模態順序不穩定。圖片和文件也屬於渲染後的字首,所以同樣的素材以不同順序附加就是不同的字首。症狀:未命中與某個不保留附件順序的 UI 相關聯。修法:傳送前確定性地排序附件。

每輪都會變的上下文塊。滾動摘要、執行中的筆記,或者每次呼叫都被重寫的“當前狀態”大塊內容,會破壞它所觸及的字首,即使周圍的對話沒變。症狀:多輪會話在第一輪之後就再也不命中。修法:如果它必須變,就放在斷點之後,只快取真正持久的東西。

斷點放在了變數翻湧的錯誤一側。Anthropic 的文件精確描述了這個失敗模式:斷點落在一個每次請求都變的塊上,你就每次都在付一次全新寫入的錢,永遠讀不到命中。症狀:有快取寫入,沒有快取讀取。修法:把斷點移到在多次請求之間保持穩定的最後一個塊上。

一個習慣囊括上面六種修法:把字首當作一個前部凍結的模板,並給這個前部一個你能在自己日誌裡看到的版本號。在模板旁邊留一段短說明,寫清哪些部分可能變化、哪些被凍結、斷點落在哪裡,這樣下一位工程師不必靠盯著命中率崩塌來重新發現同樣的規則。

在你自己的用量資料裡驗證快取命中

會讀快取 token 的用量,是“相信”和“知道”之間的差別。驗證迴路很短,值得按這個順序跑一遍。

  1. 讀那些欄位。在 OpenAI 的響應裡,快取輸入出現在 usage 物件的 token 明細中,字首首次被儲存時還會帶有快取寫入的記賬。Anthropic 把快取寫入和快取讀取作為兩種不同事件報告,這很方便,因為讀取計數持續增長正是你想要的簽名。
  2. 把同一個字首跑兩次。相同字首、兩次呼叫,比較兩個 usage 物件。第二次呼叫就是命中必須出現的地方;如果兩者看起來一樣,那字首其實並不相同。如果欄位在變卻從不累積,先回頭檢查上一節說的斷點位置,再去改別的東西。
  3. 做一個最小 A/B。一個變體保持字首不動,另一個變體插入單個變數,然後比較。在接上程式碼之前,先在終端裡跑一次小呼叫,這就是快速上手裡的同一種紀律,因為測機制時,最小復現勝過完整應用。
  4. 記錄一個指紋。對你傳送的字首做雜湊,並把雜湊記在快取欄位旁邊。命中率一變,兩個指紋的 diff 就告訴你什麼變了,這勝過回去做程式碼考古。
  5. 定期審計。定期對照你的用量記錄確認讀取真的在發生,而不是相信一個幾個月前設好、再沒碰過的看板。還要記住,用量欄位和響應形狀在不同端點上有差異,而這正是相容性清單存在的意義:把這種差異釘死。

如果你在 OpenAI 上,還有一個額外的工具:cache key 可以把快取記賬按使用者或工作流分開,讓驗證和按客戶解釋都更乾淨。

什麼時候快取不值得做

快取獎勵的是字首結構重複的負載,說實話,不是每種負載都符合條件。

  • 字首確實是每次請求都唯一的。長尾的個性化 prompt,斷點之前的一切都各不相同,沒有任何可複用之處,再怎麼調快取也改變不了。
  • 每個產物只調用一次。沒有後續一次生成,就沒有第二個請求來命中快取。
  • prompt 很短。低於服務商的最小可快取長度,就根本沒有合格的快取條目,而閾值隨模型而異,所以去查官方頁面,不要憑假設。
  • 你還沒測過命中率。給一個你從沒量過的負載加快取層,就是團隊增加複雜度然後稱之為最佳化的方式。先測量:如果你的穩定字首在生產日誌裡其實並不穩定,那該修的是字首紀律,不是一個快取專案。
  • 你的客戶端在跟你作對。快取取決於客戶端實際發出的內容;那些在請求之間注入 session 識別符號、重排附件或重寫指令的工具,會破壞字首,無論你的模板有多好。先修客戶端,否則快取會成為一個永久的謎。

接下來做什麼

  • 在調優之前先看清花費。 用量與成本歸屬是本文所假定的測量層。
  • 給故障分診。當快取的表現怪異、看起來像報錯時,錯誤碼指南會把層次分開,包括那些永遠不值得重試的。
  • 字首在哪裡被塑造。客戶端和工具配置是這一切的上游,Cursor 端點演練就是那一層的具體例子。

先修字首,用你自己的用量資料驗證,然後才談快取省下了什麼。