🧊 如果你問我,做 Agent 最重要、最基本,而且從第一版就該盯緊的指標是什麼,我會選 Prompt Cache 命中率。它牽動每一輪的速度和成本,是我心中的頭號指標。每個版本都要跑 Eval 驗證,不能等帳單變貴、回應變慢,才回頭補救。
今天要介紹的是 Prompt Cache。Agent 每次呼叫模型,通常都要帶上規則、工具說明、先前對話和最新結果。即使這次只多了一小段新資訊,前面那一大段內容也會再送一次。Prompt Cache 讓模型重用相同開頭已經算過的中間資料,省下重複計算;這段相同的開頭,叫 prefix(前綴)。
拿一個修 CI 的 Agent 當例子,後面整篇都用它。它先讀 CI log,決定開哪個檔案,讀完再改,改完跑測試,每一步都要呼叫一次模型。假設 Harness 為了讓模型知道現在幾點,在 system prompt 最前面放了一行時間。把第一輪和第二輪送出的 request 開頭上下擺在一起看:
第一輪送出的 request(開頭)
[tools] read_file、edit_file、run_tests……
[system] 現在時間 2026-09-22 10:15:07
[system] 你是修 CI 的助手。規則:先讀 log,再改檔案,改完一定跑測試……(這段約 20,000 token)
[user] CI 在 build 這一步失敗了,log 如下……
第二輪送出的 request(開頭)
[tools] read_file、edit_file、run_tests……
[system] 現在時間 2026-09-22 10:15:32
[system] 你是修 CI 的助手。規則:先讀 log,再改檔案,改完一定跑測試……(這段約 20,000 token)
[user] CI 在 build 這一步失敗了,log 如下……
[assistant] 呼叫 read_file(build.gradle)
[tool] (檔案內容)
兩份只差時間那一行的秒數。前綴比對是從開頭往後逐個 token 對:工具定義排在最前面,兩輪一樣,那一段還對得上;對到「07」和「32」就停了,從那裡之後——那 20,000 token 的規則、後面整段對話——明明一個字都沒改,也全部要重新算。時間戳擺到當輪訊息那一段,或乾脆不放,兩份 request 從頭到規則結束就是同一串,第二輪才能接著用第一輪算過的東西。這篇後面講的每一件事,都是在回答「怎麼讓那一段對得上」。
要理解它,先看模型這一輪收到了什麼。這些用來產生回應的背景資料,就是我們一直在說的 Context。它可以包含使用者問題、系統規則、工具定義、歷史對話,以及工具剛讀回來的檔案或執行結果。文字會切成 token,也就是模型處理文字的單位;一個 token 不一定是一個中文字或英文單字。例如英文 unbelievable 可能被拆成 un、believ、able 三個 token,「測試通過」四個字可能是兩個 token,也可能是四個,看模型的切法。所以算容量時要用 API 回報的 token 數,拿字數估不準。
Context window 是模型在一次生成過程中能容納的內容範圍,大小通常用 token 表示。 以 Claude 的計算方式來說,送入的內容和本輪產生的輸出都要占用這個範圍,因此規劃輸入時,也要替回覆留空間。命中 cache 的輸入同樣計入容量。Claude Context windows

圖:假設輸入和輸出共用 32,000 token 的容量,相同前綴占 20,000、新增輸入占 2,000,再預留 4,000 給輸出,剩餘 6,000;下面一列即使命中 cache,這些區塊的大小也不變。這是容量分配的示意,實際限制要照所用模型確認。
圖裡的輸入合計是 22,000 個 token。就算前面 20,000 個全部命中 cache,也還是占用 20,000 個 token 的空間。改變的是處理這段輸入時,需要重新做多少計算、按什麼費率計價。Prompt Cache 可以省計算和費用,context window 的容量限制還是存在。
使用推理模型時,要把 thinking/reasoning tokens 算進預算,也就是模型思考時使用的 token。以 Claude 為例,這些 token 也占本輪輸出額度、按輸出計費:如果預留 4,000 token,其中用了 3,000 思考,就剩 1,000 給其他輸出。前幾輪的思考內容是否繼續保留,則依模型和設定而定。Claude 的 thinking 容量說明
所以,我會分開問兩個問題:這輪放進去的資料有沒有必要、容不容得下?保留下來的重複內容,有多少計算可以沿用?前者關係到 Context 怎麼安排,後者就是今天最重視的 Prompt Cache。即使命中率很高,也不能一直把用不到的資料塞進去。
這一點有測試撐著。Chroma 在 2025 年的 Context Rot 測試拿 18 個模型(Claude、GPT、Gemini、Qwen 都有),看輸入變長之後表現怎麼變:結論是會隨長度下滑,而且各家下滑的樣子不一樣,沒有一條固定曲線可以套到所有模型身上。其中一項特別好懂——同一個問題,一邊把完整的長對話整份送進去,一邊只送跟問題相關的那幾段,所有測到的模型都是後者答得比較好。所以命中率漂亮歸漂亮,塞進去的東西讓模型找錯地方,省下的那點錢也救不回來。
接下來會先說明為什麼 Agent 特別依賴這項能力,再用圖解拆開模型留下的資料、下一輪怎麼接著用,以及哪些改動會讓前綴對不上。最後看各家模型的價格差異、組裝輸入的原則,以及 Eval 要驗哪些數字。
以修 CI 的 Agent 為例。它讀一次 log、改一次檔案、拿到一次測試結果,都可能再呼叫模型。上一輪的規則、工具說明和對話,又跟著送進下一輪。工作越長,重複處理的內容就越多。
這些內容能不能重用,看的是 Harness 怎麼組裝輸入。開頭那兩份 request 就是一個例子:規則一個字沒改,只因為最前面那行時間,第二輪的前綴從第一行就對不上。工具清單每輪換一種順序輸出,也是同一類問題:

圖:CI 修復持續進行 → system 開頭插入秒級時間 → 工具清單換了排序 → 前綴從差異處不再匹配 → cache 用量下降 → 比對 request 和實際 usage。這是示意,實作要照自己的工具和環境調整。
對輸入很長、每輪只產生少量工具參數的 Agent,能不能沿用重複輸入的計算,會影響開始回覆前的等待,也會影響每一步的費用。request 從第一天怎麼組裝,就已經在影響它。
麻煩的是,cache 掉了很安靜。改版時如果沒驗,任務照樣答對,只是同一份輸入開始每輪重算,速度和成本一起退步,常常要等帳單來了才發現。
做 Agent 大半的工夫,花在決定給模型看什麼:哪些情報它需要才做得出判斷,還有掛哪些 tool 給它,讓它自己把缺的那塊補回來。大部分任務都長這樣,模型呼叫 tool 去查它要的東西,一輪一輪把情報堆起來,最後整理成一段話交給使用者。所以 Agent 的輸入天生就比輸出大很多:查回來的全部要再送一次進去,吐出來的常常只有一筆 tool 參數,或最後那段結論。中間只要有一輪讓前綴對不上,要重算的就是那一大坨輸入,代價比省下的那點輸出大得多。
這個判斷也有實際產品的經驗支持。Manus 在 2025 年的工程文章說,如果只能選一個 production Agent 指標,作者會選 KV-cache hit rate。他們當時的 input:output token 比平均約 100:1,很多費用花在反覆讀入 Context。這是 Manus 的工作特性;自己的 Agent 有多依賴 cache,要從實際輸入和輸出用量確認。
要盯這個指標,就得把穩定內容怎麼組裝、什麼改動會影響前綴、每次改版要驗哪些數字,列進開發要求。後面會一直提到的 Eval,就是拿固定的一組任務讓新舊版本各跑一次,比結果和代價;具體驗哪些數字,留到後面那一節。下面先把「重用計算」這件事從模型怎麼處理輸入講起。
回到修 CI 的 Agent。上一輪已經送過專案規則、工具說明和對話歷史,現在工具回報新的測試結果,Harness 準備再次呼叫模型。這一輪大部分輸入都相同,只有最後多了一段內容。
Prompt Cache 要解決的,就是讓相同前段已經做過的計算,下次可以接著用。 先看它省在哪裡,再往下拆開「已經算好的資料」到底是什麼。
前面先用 token 算了 Context 占多少空間,現在往下看:模型要做哪些計算,才能根據這些輸入產生回覆?
一次產生回應,可以先分成兩個階段:prefill 是處理已知的輸入,decode 是接著產生新的 token。 前者建立模型後續會用到的中間數值資料;後者依據已有內容,繼續產生新的回覆。TNG 的工程文章
放回修 CI 的 Agent 走一次。Harness 把規則、工具說明、對話和剛拿到的測試結果送過去,模型先把這一整段輸入從頭讀一遍,替每個位置算出後面要用的中間資料,這段是 prefill。讀完才開始一個 token 一個 token 吐回覆,例如「呼叫 read_file 讀 build.gradle」,這段是 decode。你在畫面上等第一個字出現的那段時間,大部分花在 prefill;字開始一個個冒出來之後,就是 decode 在跑。Prompt Cache 省的是 prefill 裡重複的那一段。
假設這輪有 22,000 個輸入 token:前面 20,000 個跟先前相同,最後 2,000 個是新內容。下圖比較的是這一輪「沒有可用的前綴 cache」和「前面 20,000 個成功命中」的兩種情況。

圖:最上面是相同前綴加上新增內容;未命中時處理全部輸入,命中時讀取前綴已算好的資料,再處理新增內容;兩條路都繼續產生新的回覆。這是計算分工的示意,方塊寬度不代表實際耗時。
沿著圖的中間一列看,未命中時,這輪要處理全部 22,000 個輸入 token,建立後面生成回覆需要的資料。沿著下面一列看,命中時,前面 20,000 個已經有可用的計算結果,就從這個基礎繼續處理新增的 2,000 個。
兩列最後都是「產生新的回覆」。所以,cache 可以讓下一輪更快開始生成,新的 CI 結果也照樣會影響這次的判斷。Modular 的 Prefix caching 說明也是把「沿用前綴」和「處理剩餘輸入」分開看。
這裡講的「處理輸入」是在模型裡做數值計算。把對話文字存在資料庫、或把同一段文字再次送到 API,都還需要確認模型端有沒有可用的計算結果可以沿用。
圖中留下的那份中間資料,叫 KV-cache。接下來只要弄懂兩件事:K、V 在算什麼,以及什麼條件下它們可以沿用。
我想把「中間結果」再拆開一點,先認識常見的 Autoregressive Transformer。Autoregressive 描述的是生成方式:根據已有內容,選出下一個 token,再把它接回內容裡,繼續下一步。例如文字可以逐步接成「測試」→「測試通過」→「測試通過。」;這是在示意內容怎麼變長,實際每步處理的是 token,不一定是一個中文字。Jay Alammar 的 GPT-2 圖解有動畫展示這個循環。
Transformer 則是模型的架構:把 token 轉成數字,經過一層層計算,最後得到下一個 token 的機率。token 進去後,先變成一串數字,叫 vector(向量);最初這份數字表示叫 embedding,後面每一層再根據前一層的結果加工。模型也會把位置資訊加進去,所以同一個字出現在哪裡、前面有哪些內容,算出來的數字都不一樣。
每一層裡最要緊的一步是 attention(注意力機制)。拿 CI log 裡的一句「它失敗了」來看:模型處理到「它」這個位置時,光看這個字不知道指什麼,得回頭看前文。前面講的是那個測試,還是那次部署?attention 做的就是這件事:目前位置回頭對前面每個位置各算一個權重,代表「從你那裡拿多少資訊」,再照權重把那些位置的資訊加進目前位置。前文在講測試,「測試」那幾個位置的權重就高,「它」算出來的數字就往測試靠。
要做這個比對,每個位置要先從這一層收到的數值算出三種向量。接下來拆開的 Q、K、V 就是它們;KV-cache 保留的是其中可以重用的部分。
| 名稱 | 在計算裡做什麼 | 可以先怎麼記 |
|---|---|---|
| Query,簡稱 Q | 目前位置拿去跟各個 K 比對,算出分數 | 這次拿什麼去查 |
| Key,簡稱 K | 各個位置提供給 Q 比對的資料 | 用什麼讓別人找到我 |
| Value,簡稱 V | 按照比對後的權重,被加總進目前位置的結果 | 找到之後取用什麼 |
這些名稱描述數值的用途。K 不是我們手寫的關鍵字,V 也不是一段原文;Q、K、V 都是模型用訓練好的參數算出的向量。The Illustrated Transformer有逐步拆解它們怎麼產生。
套回「它失敗了」:「它」拿自己的 Q,去跟前面「測試」「部署」各自的 K 比,比出來分數高的位置就多拿一點;拿的東西是那些位置的 V。算法是這樣:先用目前位置的 Q 跟可見位置的 K 做 dot product(內積),也就是把對應的數字相乘後加總,得到每個位置一個分數。分數經過縮放,再交給 softmax(把一組分數壓成總和為 1 的比例),變成權重。最後用這些權重,對各位置的 V 做加權加總。Attention Is All You Need §3.2

圖:上半部用目前位置的 Q 比對兩個 K,得到假設的 0.8/0.2 權重;下半部把權重乘到對應的 V,再加總成 [1.6, 0.8],交給後續模型計算。這是單組 attention 的數值示意,實際向量通常大得多。
把圖對回那句話:目前位置是「它」,圖上的位置 1 當作前面的「測試」、位置 2 當作「部署」。Q 跟兩個 K 比出來的權重 0.8 和 0.2,意思是這次從「測試」那邊拿八成、從「部署」拿兩成。沿著圖算一次:兩個 V 分別是 [2, 0]、[0, 4],乘上假設的權重 0.8 和 0.2,得到 [1.6, 0] 和 [0, 0.8]。最後把對應位置相加,就得到 [1.6, 0.8]。
也就是 0.8 × [2, 0] + 0.2 × [0, 4] = [1.6, 0.8]。這裡的 0.8 是這一次取用 V 的權重,不是答案有 80% 的機率正確;向量裡的數字也沒有直接指定「這格代表測試,那格代表部署」。
這份結果還要經過後續模型層,最後才得到下一個 token 的機率。Q 和 K 決定這次怎麼取用資料,V 提供被取用的數值內容。 真實模型會用多組這樣的計算,也就是多個 attention head,分別處理資訊後再合併。
如果想搭配圖解讀,我會從這幾份開始:
處理完「它」,輪到下一個位置「失敗」。它要算的是自己的 Q,拿這個新 Q 去跟前面「測試」「部署」「它」的 K 比對,再取用那些位置的 V。「它」當初的 Q 已經完成當時的查詢,這一步用不到;用得到的是前面每個位置的 K 和 V。
所以標準的 KV-cache 保存先前各位置、各模型層算好的 K、V。新位置算出自己的 Q、K、V,使用「舊的 K、V 加上自己的 K、V」完成 attention,再把新的 K、V 留給下一步。Hugging Face 的 How caching works
對照剛才的圖,下一步換了一個新的 Q,就重新算權重;前面已算好而且可沿用的 K、V,可以直接拿來用。Sebastian Raschka 的 KV-cache 圖解也是逐步對照哪些向量需要新算、哪些可以保留,後面還附了可讀的實作。
這也回答一個容易混淆的地方:命中 cache 之後,舊內容還在參與計算。新 Q 還是要讀取舊 K、V、算出這一次的權重;省下的是重建舊資料的工作。長 Context 即使能重用,也還是有讀取和 attention 的成本。保存這些 K、V 不會更新模型訓練好的參數;保存的是這次輸入算出的資料。
先看下面三列:第一次計算、只在最後追加、改動中間內容。A、B、C、D 是連續的輸入片段;藍色表示可沿用的前綴,橘色表示需要計算的內容。

圖:第一次計算 A、B、C 再保存 K、V;只追加 D 時,前面三段有機會沿用;把 B 改成 B′ 後,A 還可匹配,B′ 和後面的 C、D 要重新處理。這是前綴重用的示意,實際命中量還要看服務的區塊、長度和保存條件。
原因是 causal attention(因果注意力)的方向限制:一個位置只能參考自己和前面的位置,看不到後面。這個限制透過 causal mask 實作,也就是在算權重時,把後面不能看的位置遮掉。Transformer 論文 §3.2.3
把原本的輸入分成 A、B、C 三段。處理 A 時看不到後面的 B、C;處理 B 時可以參考 A;處理 C 時可以參考 A、B。現在把 D 追加到最後,A、B、C 的前文和位置都沒變,原本的計算就有重用的基礎。
反過來,如果把 B 改成 B′,C 雖然文字沒改,前文卻變了。經過 attention 和後續模型層之後,C 的表示和更深層的 K、V 都可能跟著改變,所以不能直接接回原本 C 的 cache。A 位在差異之前,還有機會沿用。
例如「它失敗了」,前面說的是測試還是部署,意思就不同。模型各層處理的也包含這種前文影響;不能把每個詞的 K、V 當成一張只按字面查找、永遠不變的對照表。
Prefill 可以平行處理多個輸入位置,這和 causal mask 並不矛盾。輸入 token 一開始都已知,模型可以在同一層同時計算多個位置,再用 mask 限制每個位置能看誰。「同時計算」和「可以看見後文」是兩個不同條件。
回到修 CI 的 Agent,可以把前面的原理接成一次完整的流程:
第 3 步才接到我們追蹤的跨 request Prompt Cache。保存哪些前綴、保存多久、如何匹配和計費,由服務決定;API 回報的 cached input 用量也不是模型內部所有 KV 重用次數的總和。vLLM 的跨 request 重用說明
在一般前綴匹配裡,服務會找出從開頭連續相同、可以沿用的部分。用修 CI 的 request 對照:
| 下一輪怎麼改 | 哪段前綴有機會沿用 | 原因 |
|---|---|---|
| 保留規則、工具和歷史,只在最後追加測試結果 | 原本相同的前段 | 新內容接在後面 |
| 在歷史中間插入一段新摘要 | 插入位置之前的相同前段 | 後面的前文變了 |
| 在開頭插入每秒不同的時間 | 通常只剩變動處之前很短的一段 | 原本的大段穩定內容被推到差異之後 |
這張表描述匹配方向,實際命中量還要看 API。以 vLLM 的實作文件為例,它把 cache 分成區塊,每塊的識別包含自己的 token 和前面區塊的識別,而且只重用完整區塊。這也說明:改一個字可能影響後面很長一段;前面相同的部分則不必全部作廢。
因此,「意思一樣」和「能重用相同計算」是兩回事。 例如把規則裡的「改完一定跑測試」改寫成「每次修改後都要執行測試」,對人是同一條規則,對前綴匹配就是從這個位置起換成另一串 token,後面全部要重算。工具清單從 read_file、edit_file、run_tests 換成 run_tests、read_file、edit_file,也是一樣:工具一個沒少,前綴已經對不上了。
回到第一張圖的 22,000 個輸入 token:20,000 個前綴命中,省下那段的重複計算;新增的 2,000 個 token、這輪的回應生成和後續執行測試,都還需要處理。
不能把它解讀成「整輪會快 22,000 ÷ 2,000,也就是 11 倍」。cache 最直接省下的是重複輸入的 prefill,整體加速還取決於其他時間占多少。 TTFT(Time to First Token)是送出 request 到收到第一個 token 的時間,也包含網路、排隊等等待,所以後面的 Eval 會同時記錄 TTFT 和整個任務耗時。vLLM 的效能界線、TNG 的推論階段說明
費用同樣要分開算:命中的輸入依 cache 讀取費率計價;新增輸入、cache 寫入、輸出,以及有些服務的保存費,照各自規則計算。後面的價格表和完整算例,會把這些費用一起放回來。
同一份規範可以搭配新的問題、新的 CI 結果,讓模型產生新的判斷。Prompt Cache 不會直接把上一輪「測試通過」的答案貼回來,也不會替 Agent 查詢最新測試狀態。應用程式照樣要送入正確的當輪資料。
服務也沒有承諾永久保留這些計算。超過保存時間或不符合重用條件,就可能要重新處理。因此,保存聊天紀錄、讓 Agent 記住工作進度,以及讓推論 cache 命中,是三件需要分別確認的事。Claude cache 的生命週期
想繼續往底層讀,可以看 Prompt Cache: Modular Attention Reuse for Low-Latency Inference。這篇研究用明確定義的 prompt modules 重用 attention states,讓人更容易理解 cache 存的是「可重用的計算」;它有自己的結構和位置處理方式,不能直接推論所有商用 API 都能把任意相同段落拆開來 cache。
理解這些之後,「穩定內容放前面、歷史盡量追加、動態內容放後面」就有了原因:讓下一輪保留更多可以沿用的計算。這也是我把它當第一個要盯的指標的理由。
會。開頭那兩份 request 已經看過了:時間戳落在前綴裡,從那個位置之後就對不上;差異之前相同的部分還是可能重用。時間戳放在 system prompt 最前面,等於把後面整段穩定內容都推到差異之後,是最糟的位置。
工具清單是同一回事,只是差異藏得比較深。Serialization 是把程式裡的資料結構轉成要送出的文字或 bytes(位元組),中文常叫序列化。工具清單如果每輪從沒有固定順序的 map 重新產生,同一組工具就可能用不同順序輸出,人看起來一樣,送出去的 token 已經是另一串。工具名稱沒變,排序、描述或 schema(工具參數的格式說明)的表示方式變了,也會縮短能沿用的 prefix。
OpenAI 的文件 說明,cache 比對的是供應商實際處理的 rendered prefix,除了看得到的文字,還可能包含工具定義、developer messages、歷史和供應商加入的內容。它建議穩定的指令和共用資料放前面,時間戳和使用者特定內容放後面,歷史則盡量追加。
Anthropic 在手動設定 cache 邊界時,前綴依 tools、system、messages 的順序組成,到指定的 cache_control block 為止。前面改了,後面的匹配也會受影響。
兩家的 API 和條件要分別看。不能假設換模型、供應商或 region,還會用同一份 cache,保存時間和路由條件也得依目前使用的服務確認。
那要怎麼查出是哪裡變了?最直接的做法是把兩輪實際送出的 request 存下來,從頭往後比到第一個不同的字,看它落在 system、工具清單還是對話裡。後面「request 要留下哪些資訊」那節會講怎麼用內容指紋做這件事,不用每次都存整份 prompt。至於當下時間該放哪,OpenAI 那句已經給了方向:跟最新的 CI 結果一樣,放在當輪的訊息裡,前面那段穩定內容就不會被它推到差異之後。「放回這個 Agent」那節會把這件事接到實際的 API 設定上。
Agent 每走一步,都要準備下一輪 Context,裡面可能有共用規則、工具說明、對話、working memory(目前工作摘要)、最新結果和檔案狀態。
有些資料同一版程式裡很少變,有些隨歷史增加,有些每輪都不同。我會先把它們分開整理:
這是整理資料的方式,最後送進 API 怎麼排,還是照供應商的規則。動態工作資料可以後置,需要放在 system 或 developer 的規則則保留原本的角色,不能為了 cache 改成一般 user 內容。也不用為了 prefix 不變,就把用不到的工具全部留下來,它們還占 Context,還可能讓選擇變難。
我會在 review 時把下面這張表拿出來,對照實際送出的 request。OpenAI 的工具和多輪對話範例也特別要求工具定義、順序保持一致,後續訊息接在原有對話後面。
| 要維持的原則 | 在這個修 CI 的 Agent 裡怎麼做 | 改版時檢查什麼 |
|---|---|---|
| 固定指令放在動態資料前面 | 共用規則保留;最新 CI 狀態放在後面的當輪訊息 | 時間戳、request ID 有沒有跑到共用規則前面 |
| 同一份資料產生相同表示 | 工具排序、描述、schema 和模板格式固定 | SDK 或 schema 產生器升級後,輸出有沒有改變 |
| 已送過的歷史盡量保留 | 新的工具結果照協定追加 | 有沒有為了整理畫面,重排或重寫模型已讀過的前段 |
| 把會變的摘要放在合適位置 | 目前工作摘要更新時,不插回共用前綴開頭 | 摘要更新後,原本可重用多少內容還留得住 |
| 模型和 cache 設定一起管理 | 模型、API、保存時間和路由設定有版本 | 備援切換換了供應商或模型,有沒有被算成原本那組的退步 |
| 以實際 usage 驗證 | 每輪留下讀取、寫入和一般輸入用量 | 前綴看起來沒變時,命中量是否真的維持 |
「工具固定」也要分清楚。固定一組常用工具時,不要每輪任意增刪它們;工具很多時,可以用供應商支援的按需載入。Anthropic 的 Tool Search 文章說明,延後載入的工具先不占初始 Context,需要時再加入,原本的 system 和常用工具還可重用。這種設計跟每輪重寫前面的工具清單不同,也接得上明天要談的按需發現。
我會先固定小而常用的工具集,再用 Eval 比較按需載入能不能減少總費用和選錯工具的次數。不能為了 cache,把幾百個用不到的工具永遠塞在前面。
Prompt Cache 重用的是輸入計算,每次回答還是重新產生。每輪生成需要的 Context,可以由 Harness 送入,也可以由服務端接回已保存的對話。以 OpenAI Responses 為例,previous_response_id 是用來接續上一筆回應的識別碼,讓客戶端不用每次重傳完整歷史。OpenAI 的對話狀態說明
例如上一輪已提供專案規則和 CI log,下一輪可以傳入前一筆回應的識別碼,再加上新的測試結果。傳輸的資料變少,模型使用的 Context 還包含接回來的歷史;這些內容照樣要計算輸入用量,是否命中 cache 也要另外確認。傳了多少資料、Context 有多大、重用了多少計算,要分開看。
Gemini 的 explicit caching,是由開發者先建立 cache 物件,再讓後續 request 引用;implicit caching 則由服務自動嘗試重用前綴。依目前官方文件,手動建立 cache 物件適用於 generateContent API,這組文件已標為 Legacy(舊版介面);Interactions API 目前只支援 implicit caching。實作前先確認自己使用哪一組 API。generateContent caching、Interactions caching
例如我們採用 Claude Messages API 的手動 cache 邊界:先固定工具定義和共用指令,在可重用內容的結尾設定 cache_control;模型、最低長度和保存時間必須符合該服務條件。接著把「CI 還在跑」或「CI 已失敗」放在當輪的訊息裡,不要每輪把它插回共用指令最前面。
符合條件的第一輪建立 cache;第二輪送出相同前段,加上新的 CI 結果。如果前綴相符且 cache 還可用,usage 會顯示讀取了多少 cache。沒有命中時,依正常請求處理,再查長度、保存時間、路由和內容差異。設了邊界之後,要看 usage 確認這輪實際寫入或讀取多少。
OpenAI Prompt Caching 的現行文件同時區分 implicit caching 和支援模型的 explicit caching;使用哪一種,要對照模型和 API。收到回應後,再從 usage(這次呼叫的用量資料)核對 cached tokens。Claude 的 cache_control 欄位不能直接套到另一家的 API。Claude Prompt Caching、OpenAI Prompt Caching
這也回答了原本的時間戳問題:需要「目前時間」就照實更新、放在合適的動態區段;只有「Run 開始時間」這種本來就固定的事實,才能整次工作共用。
ProjectDiscovery 的 Neo 案例 用他們自己的組裝示意說明:working memory、相關 skills 和 runtime Context 插在固定內容之間,讓後續內容難以重用。這張示意描述 Neo 怎麼整理資料,不代表 Anthropic API 的前綴順序;實際比對還依前面提到的 tools、system、messages 規則。
他們把動態資料移到後面、固定工具排序,也一起調整模板變數、Run 內的時間和路由。文章報告的每週 cache rate,從 2026-02-09 的 7.6%,到 02-16 的 73.7%,後來 03-16 達到 84.3%。
標題的「省 59%」也要看比較基準:他們把實際帳單跟「相同 token 全部按標準輸入費率計價」比較。這些是 Neo 當時使用 Anthropic 的結果,沒有換模型,但同時改了好幾個地方,不能說只搬一段文字就一定省 59%,也不能直接套到其他 Agent。
Manus 的工程文章提出固定 prefix、歷史盡量追加,和 deterministic serialization,也就是同一份資料每次都用相同排序和格式產生。秒級時間戳放在 system prompt 最前面,就是它列出的失效例子。
它也提醒,直接增刪前面的工具定義會影響後續 cache。Manus 當時用自己的方式限制模型能選哪些動作;使用外部 API 時,得確認那個 API 支援什麼,不能假設都能直接控制模型選 token 的機制。
沿著這個做法,我會先確認:Harness 能不能看見和控制 request 最後怎麼組出來。framework 如果把這一段全部藏起來,連工具順序是否變了都不知道,就很難查 cache 為什麼 miss。
下面比較直接呼叫各家 API 的文字輸入,單位都是美元/每百萬 token(MTok)。採一般即時付費費率,不混入 Batch、加速方案或訂閱月費。OpenAI 採官方表的 Short context 費率;Gemini 3.1 Pro Preview 採輸入不超過 200k token 的費率。
「一般輸入」是沒有 cache 讀取或寫入的價格;一次 cache miss 如果觸發建立 cache,就要看「寫入」那欄,不能一律按一般輸入算。價格核對日期是 2026-09-22。OpenAI 定價、Claude 定價、Gemini 定價
| 模型 | 一般輸入 | 命中後讀取 | 讀取比一般輸入便宜 | cache 寫入 | 輸出 |
|---|---|---|---|---|---|
| GPT-6 Astra | $10.00 | $1.00 | 90%(1/10) | $12.50 | $50.00 |
| GPT-5.6 Sol | $4.00 | $0.40 | 90%(1/10) | $5.00 | $20.00 |
| GPT-5.6 Terra | $2.00 | $0.20 | 90%(1/10) | $2.50 | $12.00 |
| GPT-5.6 Luna | $0.20 | $0.02 | 90%(1/10) | $0.25 | $1.20 |
| Claude Fable 5.1 | $10.00 | $0.25 | 97.5%(1/40) | $12.50/$20.00 | $50.00 |
| Claude Opus 5 | $5.00 | $0.50 | 90%(1/10) | $6.25/$10.00 | $25.00 |
| Claude Sonnet 5 | $2.00 | $0.20 | 90%(1/10) | $2.50/$4.00 | $10.00 |
| Claude Sonnet 4.6 | $3.00 | $0.30 | 90%(1/10) | $3.75/$6.00 | $15.00 |
| Claude Haiku 4.5 | $1.00 | $0.10 | 90%(1/10) | $1.25/$2.00 | $5.00 |
| Gemini 3.8 Flash | $0.75 | $0.075 | 90%(1/10) | 另看保存費 | $3.75 |
| Gemini 3.1 Pro Preview | $2.00 | $0.20 | 90%(1/10) | 另看保存費 | $12.00 |
| Gemini 3.1 Flash-Lite | $0.25 | $0.025 | 90%(1/10) | 另看保存費 | $1.50 |
Claude 寫入欄的兩個數字,分別是保存 5 分鐘和 1 小時的價格。表內 GPT-5.6/GPT-6 模型也有 cache 寫入費,不能套用早期 OpenAI 模型「沒有額外寫入費」的印象。GPT-5.6 Sol 現價屬官方至少維持到 2026-11-21 的優惠;Gemini 3.8 Flash 這組價格適用到 2026-12-31。長 Context 和不同處理方案要另外查對應費率。
Gemini 的 explicit caching 會另外按保存的 token 數和時間計費。表內三款的保存費,依序是每百萬 token 每小時 $0.50、$4.50、$1.00;Gemini 3.8 Flash 的 $0.50 同樣適用到 2026 年底。這筆是明確建立 cache 物件的保存費,不要加到只使用 implicit caching 的 request。Google 的 generateContent cache 文件
假設這次工作呼叫模型 20 輪,每輪都有相同的 20,000 token 前綴,另外各有 2,000 token 輸入和 500 token 輸出。為了把帳算清楚,我們只在那段固定前綴設 cache 邊界,其他輸入都按一般費率計算;前綴寫入一次,後面 19 輪都在保存時間內完整命中。
用 Claude Sonnet 4.6、5 分鐘 cache 寫入價格計算:
| 費用項目 | 完全不用 cache | 前綴寫入一次、命中 19 次 |
|---|---|---|
| 固定前綴 | 20 × 20,000 ÷ 1,000,000 × $3 = $1.200 | 寫入 $0.075,加上 19 次讀取 $0.114,共 $0.189 |
| 其他輸入 | 20 × 2,000 ÷ 1,000,000 × $3 = $0.120 | $0.120 |
| 輸出 | 20 × 500 ÷ 1,000,000 × $15 = $0.150 | $0.150 |
| 模型 API 合計 | $1.470 | $0.459 |
同樣的 token 數,這組假設下模型費用少了約 68.8%。 第一筆 cache 寫入比一般輸入貴,後續重用才把費用省回來。如果每輪都把前綴改掉、又重新寫入 cache,這筆工作反而會花 $1.770,比完全不用 cache 還貴約 20.4%。
這是照牌價計算的算例,沒有實際呼叫 API。它也只算模型費用,外部 CI、VM 和其他工具費用要另外加。正式工作的歷史會增長、前綴可能部分命中,所以最後還是用每輪 usage 結帳。
速度也有公開數字可參考。Anthropic 發布 Prompt Caching 時報告,長 prompt 的延遲最多降低 85%。那是當時模型和測試條件的結果;放到這次修 CI 的流程,我會量 TTFT 和整個任務耗時,分清楚省下的是模型讀取輸入的等待,還是工作本身也更快完成。
Day 5 說明訊息收下和送達模型是兩件事。今天再往 request 組裝看:已保存的紀錄和當輪 Context 可以不同。Append-only 指只追加新內容、不回頭重寫舊內容;拿它維持前綴時,也不要求每輪塞進全部歷史。想讓 prefix 穩定,可以避免為了畫面排列,反覆改寫已送出的前段內容;歷史太長,還可以摘要或按需讀取,只是原始事件和摘要要分開。
工具結果晚到,也不能為了 cache 就隨便放到訊息最後。事件可以在自己的 log 追加,UI 顯示在原呼叫旁,但送給模型的 call ID、訊息相鄰和順序,要符合供應商的協定。
另外,cache miss 可能根本跟 prefix 沒關。Anthropic 文件 提到,低於模型的最小長度,可能不建立 cache 也不報錯;多個 request 一起送出時,也要等第一個回應開始,才有可使用的 cache。第一批都是 cold cache,也就是還沒有可重用的 cache,就不能期待每筆都命中。
保存時間也要看怎麼計算。以 Claude 的 5 分鐘 cache 為例,有效期間內再次命中會刷新 TTL,也就是保存時間。假設 10:00 建立,10:04 又成功命中,保存時間就從這次命中重新計算 5 分鐘。因此,整段工作可以超過 5 分鐘;要看的是相鄰兩次重用之間隔了多久。Claude cache 的保存和刷新規則
cache 寫入和較長的 TTL 還有成本,所以要看實際 read/write 用量和省下的費用,再決定值不值得預熱。不要為了拉高命中率,硬塞一堆無關文字。
更新資料也不能為了 cache 停掉。現在時間、使用者新要求、檔案版本和最新結果會影響判斷,就要放進 request;可以固定的是原本就不會變的資訊,例如 Run 開始時間,不能把過期狀態當成穩定資料。
Eval 就是拿一組固定任務,讓新舊版本在可比較的條件下執行,再看結果和代價。今天要把 Prompt Cache Rate,也就是 cache 命中率,放進這套評估。
每個準備上線的 Agent 版本,都要跑包含 Prompt Cache 的 regression eval。 Regression eval 是在確認改版後,原本做得到的事有沒有退步。改 prompt、工具 schema、SDK、Memory 組裝、模型或路由,都可能影響 cache;「這次只改了一行」不能當成略過的理由。
Anthropic 的 Agent Eval 文章把 capability eval(能力評估)和 regression eval 分開,後者用來持續抓退步。把 cache 列成每版必測項目,是我在這裡提出的工程要求;檢查值和放行門檻要由自己的工作負載決定。
我會把下面兩個數字分開記,主要用第二個觀察輸入重用:
| 指標 | 算法 | 能回答什麼 |
|---|---|---|
| 有命中的 request 比例 | cache read 大於 0 的 request 數 ÷ 全部 request 數 | 有多少次呼叫用到至少一部分 cache |
| 輸入 token 命中率 | 全部 cache read token 加總 ÷ 全部輸入 token 加總 | 所有輸入 token 裡,有多少比例由 cache 讀取 |
假設十次 request 都有 100,000 input token,但每次只命中 1,000 token。第一個指標是 100%,第二個只有 1%。只看「每次都有 hit」,會漏掉剩下 99% 的輸入費用。
這個分母也包含新資料。例如兩輪都完整重用 20,000 token 的固定前綴,新增輸入從 2,000 變成 5,000,命中率就會從約 90.9% 降到 80%。前綴沒有被改壞,只是新資料占比增加了。所以看到命中率下降,我會一起查 cache read 的數量和輸入內容,再判斷原因。
統計多次呼叫時,先加總 token 再相除,不能直接平均每個 request 的百分比,否則 1,000 token 的小請求和 100,000 token 的大請求會有相同權重。也要分模型、任務類型和版本來看。
各家的欄位意思不同,我會先轉成「總輸入、cache read、cache write、一般輸入」再計算:
| API | 總輸入怎麼取得 | 命中量怎麼取得 |
|---|---|---|
| OpenAI Responses | usage.input_tokens,已包含讀取和寫入 |
usage.input_tokens_details.cached_tokens;表內新模型另有 cache_write_tokens |
| Claude Messages | input_tokens + cache_creation_input_tokens + cache_read_input_tokens |
cache_read_input_tokens;5 分鐘和 1 小時寫入費要分開計 |
| Gemini generateContent | usageMetadata.promptTokenCount |
usageMetadata.cachedContentTokenCount,是總輸入的一部分 |
上表是這幾個 API 的對照。Gemini Interactions 的命中量查看 usage.total_cached_tokens,不能直接套 generateContent 的欄位名稱;如果有換 API,負責整理用量的 adapter 也要跟著調整。adapter 就是把各家回傳格式轉成自己統一紀錄的那一層。OpenAI 用量說明、Claude 用量說明、Gemini UsageMetadata、Gemini Interactions caching
第一層先固定輸入,檢查 request 組裝。把同一份規則、工具和對話交給新舊版本,確認內容、順序和動態值的位置。這一步在本機就能跑,可以抓到工具排序突然改掉,卻無法證明供應商一定會命中。
第二層真的呼叫使用中的模型 API。挑有代表性的 CI 修復工作,固定 repository 起始版本、測試條件和模型設定,再量每輪 usage 和時間。API 測試要包含兩種跑法:重播固定的多輪輸入和工具結果,隔離組裝差異;讓 Agent 在重設過的環境自己完成任務,觀察步數、選工具和最後成果有沒有變化。
我會至少保留下面這些情況。Cold cache 指還沒有可用 cache;warm cache 指前綴已建立、後續請求有機會沿用。這裡看的是 cache 的狀態,跟 Agent process 剛啟動多久要分開。
| 測試情況 | 要觀察什麼 |
|---|---|
| 首次執行 | cache 建立費、第一輪等待;確認 usage,不能只因換了 session 就宣稱是冷 cache |
| 保存時間內連續多輪 | 前綴不變時讀取量能否維持,追加工具結果後費用怎麼增加 |
| CI 等待後才繼續 | 照實際等待間隔量,區分 cache 還在和已過期的情況 |
| 更新當輪時間、檔案或摘要 | 模型拿到新資訊,同時檢查前面的穩定內容有沒有被改動 |
| 載入工具、整理長對話 | 看按需載入或 compaction 前後的命中量和後續總成本;compaction 是把長對話整理成較短 Context |
| 接近正式環境的同時執行量 | 多個任務一起跑時,命中、等待和費用有沒有跟單一任務差很多 |
測新舊版本時,先訂好暖機方式和執行順序,避免第一個版本負責付建立費,第二個版本剛好讀到它留下的 cache。供應商有支援的隔離方式就使用;無法隔離時,記錄實際冷暖狀態,交錯跑多次。不要為了做實驗,每次都在 prompt 最前面塞隨機字串,再拿那個命中率代表正式環境。
每版報告至少放:輸入 token 命中率、cache read/write 用量、模型費用、任務成功率、TTFT 和整個任務耗時。時間除了中位數,也看 p95,也就是把資料從快排到慢後,95% 的測量落在它以下的位置。樣本少的時候一起列出樣本數,不把一次卡住當成穩定的 p95。
還要算「每個成功任務攤到的費用」:整批任務費用除以成功數,失敗和 retry 花掉的錢也算進去。成功數是 0,就直接標成沒有成功結果。
我會先用現行版本重跑幾次,量出正常波動,再訂退步門檻。例如同類 warm cache 工作,token 命中率下降超過 5 個百分點,或每個成功任務費用增加超過 10%,就擋下自動發布,查明原因。這兩個數字只是起始範例,要按自己的基線調整;不能拿「大家都該有 90% 命中率」當通用標準。
任務成功率和等待時間也要各自有門檻,不能靠命中率提高就抵銷做錯或變慢。需要更新規則、移除過期資料或改善正確性時,可以接受有原因的 cache 下降,但要留下比較結果。測了,至少知道這一版付出了什麼代價。
GitHub 的 Agentic Workflows 文章提供了可落地的觀測方式:在 API proxy,也就是轉送模型 request 的中介服務,統一留下每次呼叫的輸入、輸出、cache read/write、模型、供應商和時間。自己的 Harness 規模小時,在模型呼叫入口記同樣的資料就夠了。上線後也要繼續看,因為實際等待和流量不會跟測試完全一樣。
舊文件不更新,prefix 一樣可以很穩定。我們要維持的是任務做對的前提下,能重用的輸入持續被重用。 cache 沒有確認資料正確,也沒有把命中的 token 從 Context window 裡移除。

圖:固定模型和 API 設定 → 照角色規則組裝 request → 固定工具排序和穩定內容 → 記下組裝版本、內容指紋 → 讀回 cache usage 和 TTFT → 同類任務一起比較品質和成本。這是示意,實作要照自己的工具和環境調整。
沿用時間戳和工具排序的例子,不必記錄每份完整 prompt 才能比較。可以替可見內容算出 fingerprint(內容指紋):內容相同就得到相同識別,用來找出哪一塊變了;這個指紋不等於供應商的 cache key。
| 資料 | 這個例子要記什麼 | 誰使用 |
|---|---|---|
| 組裝版本和內容指紋 | 固定指令、工具排序及 schema 的版本 | Harness 在送出前記錄,部署後比較差異 |
| 動態值和位置 | 時間戳放在哪個區塊,歷史從哪裡追加 | 維護 request 的人定位第一個可見差異 |
| 模型和 cache 條件 | 模型、路由、cache 設定及送出時間 | 排查長度、到期或路由造成的 miss |
| 回傳用量和任務識別 | cache read/write、TTFT、對應的 Run | 把 cache 變化和成本、任務結果對起來 |
| 誰呼叫 | 何時呼叫 | 拿到什麼 |
|---|---|---|
| Harness 的模型呼叫處 | 每輪送出前和收到回應後 | 同一筆 request 的組裝紀錄、時間和 cache 用量 |
這段是我放在既有模型呼叫 wrapper(統一處理呼叫的入口)的 pseudocode,也就是接近程式碼的流程表示。adapter.build 負責照所用 API 的角色和排序規則組 request;record 寫受控的診斷紀錄,不能把機密 prompt 原文送到公開 log。
| 欄位 | 這個情境的值或用途 | 誰讀寫 |
|---|---|---|
| profile_version | 模型、API 和組裝規則版本 | 模型 adapter 讀取 |
| prefix_fingerprint | 可見穩定內容的指紋 | 調查者比較兩次 request |
| usage/TTFT | 回傳用量/等到第一個 token 的時間 | 監控比較同類任務 |
def call_model(adapter, fixed, history, current, record):
request, prefix_fingerprint = adapter.build(fixed, history, current)
response, ttft = adapter.send_and_measure(request)
record(adapter.version, prefix_fingerprint, response.usage, ttft)
return response
| 誰呼叫 | 什麼時候 | 結果 |
|---|---|---|
| 模型呼叫 wrapper | 每次送出 request 時 | 保存版本、指紋和實際用量;指紋相同不保證命中 cache |
假設你已經做到 Day 7:重開對話後,能另外查回檔案、本機命令和那筆 CI 的狀態。今天就在既有模型呼叫入口加上用量紀錄,再選一組 CI 修復任務,留下第一版基線。
第一步,把實際費用看得見。 每輪保存 request 組裝版本、模型、cache read/write、一般輸入、輸出和 TTFT,最後對上 Run 的結果。usage 缺失就記成缺失,不能當作 0 次命中或 0 元。
第二步,把穩定前綴保護起來。 固定工具排序和共用規則的格式,動態狀態放後面。部署前比對同一份資料組出的 request,SDK 或 schema 產生器改版也要查。需要更新的規則和檔案照實更新。
第三步,第一版就先量這幾個數字。 同時看 cold cache 和 warm cache、多輪執行的 token 命中率、實際費用、時間和成功率。退步超過事先訂好的範圍,就先找第一個內容差異,再查保存時間、長度和路由。正確性改善如果需要多花錢,也把代價明確記下來。
單一模型、固定工具集,先用一份 request 紀錄和小型 regression test 就夠,不必先做完整監控平台。工作太短、前綴低於門檻或根本不重用,也可能沒有值得啟用的 cache;評估結果寫清楚即可,每版的檢查照樣保留。
用 framework 的人,先確認它有沒有保留原始 usage、實際送出的工具順序和模型設定。自己部署模型則查看推論服務的 prefix caching 設定;vLLM 的 Automatic Prefix Caching就是一個實作參考。
Day 9–10 會處理哪些資料值得送、怎麼確定已送達;Day 11 談摘要改變歷史時的取捨,Day 26 再把成本放回整個 Run 的預算。
回到開頭那筆 CI。只加了「目前時間」,為什麼可能變慢、變貴?因為它被插到原本可重用的前綴裡,後面的相同內容也要重新處理。把當輪時間放到合適的後段,接著用 usage 看能不能把原本的重用量找回來。
再問一個原理題:cache 命中之後,模型會直接回傳上一輪的答案嗎?不會。它沿用的是相同前段的中間計算,接著處理新資料、產生新的回應。新增的 CI 結果和實際工具執行都不能省。
再看圖裡的 A、B、C、D:如果只追加 D,為什麼 A、B、C 可以沿用?因為它們原本就看不到後面的 D。把 B 改成 B′,C 明明沒改,為什麼還要重算?因為 C 的前文變了,後續模型層算出的資料也可能改變。
最後試著說明 Q、K、V:新位置用自己的 Q,跟舊位置的 K 比對,算出這一次的權重,再加權取用 V。能說清楚這個過程,就知道 cache 省下了重建舊 K、V 的工作,也知道舊資料還在參與新一輪的計算。
再用開頭的容量圖確認一次:22,000 個輸入 token 裡,20,000 個命中 cache,輸入只占 2,000 個 token 嗎?還是占 22,000。重用計算可以省費用,容量還是要把命中的部分算進去。
最後一題:新版本每次 request 都有 hit,可以直接上線嗎?還不夠。要看命中了多少輸入 token、建立 cache 花多少錢、任務是否做對,以及同一批工作比上一版快還是慢。
Prompt Cache 省的是重複輸入的計算和費用,而它很容易被一次不起眼的改版弄壞,所以每一版都要拿數字盯著。
明天接著問:就算重複內容能用 cache,也有必要把所有資料都送進每一輪嗎?我們會看如何先給資料入口,再依目前的問題搜尋和讀取。
previous_response_id 接續歷史,以及少傳資料為什麼不等於少算輸入 token。