Claude API 有提示快取(prompt caching)的功能,讓每次都相同的文字內容不必重新處理。做法是在請求裡加上 cache_control,第一次送出時,系統會把那段內容寫進快取,之後的請求如果一樣,就直接從快取讀,所花費的時間和費用都會比較少。
標上 cache_control 的區塊,官方文件稱為斷點(breakpoint)。快取只會在斷點寫入一筆資料,內容是整段前綴的雜湊。雜湊是累積的,所以斷點本身或它之前任何一個區塊有改動,下一次出來的值就會是另一個雜湊。不過讀取的時候,系統會先拿斷點位置的雜湊去找,沒找到就往回退一步,換位置的雜湊再找一次,這樣一步一步找。
規則放到實際的請求裡,有些要注意的地方。文件裡有個例子,請求前面五個區塊是固定的系統內容,每次送出去都完全相同;第六個區塊放的是這次的時間戳記,以及使用者剛打的訊息。如果把 cache_control 標在最後面,也就是第六個區塊上,第一次請求確實會寫進快取,只是寫進去的雜湊內容,是從第一個區塊一路到第六個區塊,也就是說,時間戳記也算在裡面。
但是到了第二次請求,時間戳記換了,第六個區塊的雜湊也當然會不同,因為時間不一樣了,系統找不到對應的紀錄,於是像剛剛說的開始往回找,第五、第四,一路到第一個區塊。但因為這幾個位置上一次都沒有寫過任何東西,因為寫入只發生在斷點,所以一直找到最前面,也不會讀到雜湊。前面五個區塊明明完全沒變,但是往回找只是查之前的請求已經寫過的紀錄,不會因為內容沒變就另外補存一筆。結果導致每一次請求都會再寫進一筆新的,到了下一次請求,又因為時間戳記不同而對不上。
如果把斷點往前移到第五個區塊,也就是最後一個每次都相同的區塊,第一次請求寫入的就會在這裡,而不是包含會改變的時間。到了第二次請求時,我們可以在第五個區塊上對應到雜湊,前面五個區塊就從快取讀取出來,會變的第六個區塊照一般的輸入處理,也就不會影響快取。
如果用的是自動快取,也就是把 cache_control 放在請求的最外層、讓系統自己決定斷點,在這個例子裡也會碰到相同的問題。自動快取會把斷點放在最後一個可以快取的區塊,而這裡的最後一個區塊剛好就是每次都會變的那一個,所以建議還是改用明確的斷點。
那放錯位置的影響是什麼呢?請求照樣會成功,回答也跟沒有開快取時一樣,但差別會出現在你的花費上,快取能有效地降低花費。5 分鐘快取的寫入價格是一般輸入的 1.25 倍,讀取則只要一般輸入的一點點。斷點放錯地方,每一次反而要多付一點快取的價格,讀取卻一次也沒發生過,實際比較下來比完全不開快取還多付一些。那關於寫入和讀取的價格差別怎麼影響整體的設計,可以參考之前寫的相關文章:Token 成本的真相:分級,但別分太細。
因為快取不會有錯誤訊息,要確認有沒有讀到,就要看回應裡的 usage。cache_creation_input_tokens 是這次寫進快取的 token,cache_read_input_tokens 是從快取讀出來的 token。第一次請求本來就只會有寫入,所以主要看第二次:同樣的開頭內容再送一次(兩次之間要在快取的有效時間內,預設是 5 分鐘),cache_read_input_tokens 應該會大於 0。如果第二次還是只有寫入、讀取是 0,就可以回頭看看斷點是不是標在每次都會變的區塊上,或是其他可能錯誤。另外如果沒有被快取,這種情況同樣不會回傳錯誤,原因可能是內容沒有達到該模型的最低長度。
最後說明,這篇是照 Anthropic 目前的 prompt caching 文件和價格頁整理的。各個模型的最低長度、快取設計以及讀取價格也不太一樣,而且會隨新模型更新,實際的數字以文件上的表格為準,這裡還是提供一個小概念和想法讓大家參考。

