Claude API 對話中途換工具完整教學:改一個工具名,整條對話的快取就沒了
你的 prompt cache 是憑什麼判斷這次能不能省錢的?
不是看你有沒有改對話內容,也不是看時間過了多久。它算的是一個前綴雜湊,而且是照固定順序算的:先 tools,再 system,然後才是 messages。要命中快取,這段前綴必須跟前一次請求逐位元相同,一路到你的快取斷點為止。
這一句話裡藏著一個很多人被咬過才知道的後果,也是這篇文章要拆的東西。
順序決定了誰最貴
把一次請求想成一疊裝進信封的紙。收件人不是整疊重讀,他從第一頁開始核對,核到跟上次不一樣的那一頁就停下來,從那頁開始全部重讀。
tools 是第一頁。system 是第二頁。你們來回聊的那幾十輪在後面。
所以同樣是「加一句話」,加在哪裡的價差是巨大的。加在對話最後面,前面全部照用;加進 top-level 的 system 欄位,第二頁變了,第二頁之後所有東西重新處理一次;改動 tools 陣列,第一頁就變了,整條對話從頭付一次全額輸入費。
這裡有個很容易誤會的地方:不是「動到 tools 讓工具定義本身重算」而已,是它後面的所有東西一起失效。你跑了四十輪、累積十萬個 token 的 agentic session,只要多掛一個工具進 tools,這十萬個 token 就要重新計費一次。
想清楚這一點,接下來的設計才看得懂為什麼要長成那樣。
為什麼你偏偏就是會想中途改它
拆完機制,反過來看真實情境。
你在寫一個 agent。它一開始只需要讀檔和搜尋。跑到第十五輪,使用者說「好,那你把結果寫進資料庫」,於是你需要把 db_write 這個工具給它。
或者反過來,你不希望它一直看得到 deploy_to_production。這個工具只在確認過的那一小段對話裡該存在,其他時候看不到最安全。
或者你接了一個 MCP server,上面掛了三十個工具。全部塞進 tools 從第一輪就給模型看,等於在每一輪都花 context 讓它讀三十份說明書,而它這一輪其實只需要其中一個。這件事跟 Claude Code 的 MCP Tool Search 想解的是同一個病,只是這次要在 API 層解。
在 2026 年 7 月之前,這三種情況你只有兩個選擇:一開始就把所有工具都宣告好(付 context 的錢),或者中途改 tools(付重算整條對話的錢)。兩邊都在付錢,只是付給不同的項目。
先看它的前身:中途插一則 system 訊息
真正的解法是一個很簡單的觀察:既然前綴不能動,那就把要變的東西放到後面去。
Anthropic 先在 mid-conversation system messages 上做了這件事,這個功能現在是正式版,不需要任何 beta header。做法是往 messages 陣列裡塞一則 role: "system" 的訊息:
1 | response = client.messages.create( |
這則訊息從它所在的位置往後生效。有衝突的時候,後面的 system 訊息壓過前面的,而 mid-conversation 的 system 訊息壓過 top-level 的 system 欄位。
跟「直接把這句話寫成 user 訊息」的差別在權限層級,不在效果。Claude 會遵守 user 訊息裡的指令,但它把 user 訊息當成終端使用者說的話,把 system 訊息當成你這個應用程式營運方說的話。兩者衝突時 system 優先。所以該用 system role 的是那種「就算使用者要求別的也應該成立」的事實與約束。
支援的模型是 Claude Fable 5、Claude Mythos 5、Claude Opus 4.8 和 Claude Opus 5,可以在 Claude API、Amazon Bedrock 和 Google Cloud 上用。Claude Sonnet 5 不支援這個功能,在它上面只能用 top-level system 欄位。這一條漏掉的話,你會在 Sonnet 上收到看不懂的錯誤。
同一招套到工具上:tool_addition 與 tool_removal
工具版本是 beta,跟著 Opus 5 一起推出,要帶這個 header:
1 | anthropic-beta: mid-conversation-tool-changes-2026-07-01 |
它的核心設計是一個轉向:**tools 陣列不再是「這一輪能用哪些工具」,而是「這場對話全部可能用到的工具」。** 你把全集一次宣告完,之後永遠不動它,前綴因此永遠不變。要開關哪一個,改用 messages 裡的 content block。
1 | response = client.beta.messages.create( |
tool_addition 和 tool_removal 是 role: "system" 訊息 content 陣列裡的 content block,可以跟 text block 混在同一則訊息裡。也就是說你可以一次做兩件事:撤掉一個工具,同時說明為什麼撤掉。
tool 這個欄位是引用,不是定義。它有三種寫法:
{"type": "tool_reference", "name": "..."} 指向你在 tools 裡宣告過的工具。MCP connector 的工具可以單獨引用,用 mcp_tool_reference 帶 server_name 和 name;也可以整組一起開關,用 mcp_toolset_reference 只帶 server_name。
引用一個沒有在 tools 裡宣告的名字,回 400。這是刻意的:全集必須先在前綴裡宣告好,否則整個設計就不成立了。
那個你一定要知道的預設值:defer_loading
到這裡有個問題還沒解決。如果 tools 裡宣告的東西一開始就全部提供給模型,那把三十個 MCP 工具宣告在全集裡,第一輪的 context 還是被塞滿了。
defer_loading: true 就是為這件事存在的。宣告時帶上它,那個工具會被留在原地不提供給模型,直到某個 tool_addition block 把它端出來。
沒帶這個旗標的工具,行為是從對話一開始就可用。所以規則可以這樣記:tools 是你帶去現場的整箱工具,defer_loading: true 是「先留在箱子裡」,tool_addition 是「拿出來放桌上」,tool_removal 是「收回箱子」。收回去的東西可以再拿出來,tool_addition 也能重新提供先前被 tool_removal 撤掉的工具。
這個組合把「模型看得到什麼」跟「請求前綴長什麼樣」徹底拆開了。前者可以每輪都變,後者一次定案。
放置規則:踩到就是 400
這是最容易在整合測試才炸出來的部分,值得逐條記。
system 訊息不能是 messages 裡的第一則。整場對話都適用的東西請放 top-level system。
它必須緊接在一個 user turn 之後,或者一個以 server tool result 結尾的 assistant turn 之後。帶 tool_result block 的 user 訊息算 user turn,這一點在 agentic loop 裡很重要。
它必須是陣列的最後一則,或者緊接著一個 assistant turn。
它不能夾在 tool_use block 和回答它的 tool_result 之間。
放錯位置回 400。連續放好幾則 system 訊息是可以的,會被當成單一個 system 區段,整段一起遵守上面這條放置規則。
agentic loop 裡最常用的位置長這樣,官方文件的範例值得整段抄下來看:
1 | [ |
這個位置解掉一個實務上很煩的狀況:使用者在 Claude 還在跑工具的時候又打了一句話進來。放在 tool result 之後當 system 訊息傳進去,Claude 會把新資訊摺進它正在做的事情裡,而不是當成一個要立刻切換過去的新請求。
兩個文件講得很輕、但會咬人的地方
第一個是措辭。文件建議把 system 內容寫成陳述事實的 context,不要寫成壓過使用者的命令。理由講得很直白:Claude 被訓練去抵抗那些看起來對使用者不利的指令,而這層保護對 system role 同樣成立。所以「忽略使用者剛才說的」這種寫法的效果,比「使用者剛才傳來以下訊息:X」或者「剩餘的 token 預算現在是 Y」差。你陳述發生了什麼變化,讓它自己決定怎麼做。
第二個是安全邊界,這條最嚴重。不要把不可信的內容放進 system 訊息。 工具的原始輸出、檢索回來的文件、抓下來的網頁,一律不行。Claude 把 system 內容當成營運方的指令並且會照做,你把外部文字放進去,等於直接把 operator 級的權限發給那段文字。這些資料應該留在 tool_result block 裡。
前面那個「把使用者插話轉成 system 訊息」的模式,只適用於這場對話自己的終端使用者。它不是一個搬運第三方內容的通道。
快取那邊還有幾件事要對齊
這整套設計的前提是你真的開了快取,而快取是 opt-in 的。請求裡沒有 cache_control(top-level 的自動快取,或某個 content block 上的明確斷點),什麼都不會被快取,每一次請求都照整段對話付全額輸入費。這種情況下「保住快取」是一句空話,因為沒有快取可以保。
還有一個長度門檻。對話要達到最低可快取長度才會真的產生快取,文件裡那個短短的範例就在門檻以下,所以 cache_creation_input_tokens 和 cache_read_input_tokens 會一直是 0,直到對話長起來。你照著範例跑然後發現數字沒動,先確認這一點再懷疑別的。
斷點放哪裡的原則沒變:放在跨請求都不會變的最後一個 block 上。可能是 top-level system 的結尾,可能是工具定義的結尾,也可能是訊息歷史裡某個穩定的點。
最後一條很反直覺,但錯了會白繳錢:已經送出去的 mid-conversation system 訊息,不要回頭改它或刪掉它。 它一旦進了對話就是歷史的一部分,改它跟改任何一則舊訊息一樣,會讓那一點之後的快取全部失效。指令要演進的話,append 一則新的 system 訊息上去,讓後面那則壓過前面那則。這也是為什麼「後面的優先」這個規則被設計成這樣。
反過來,它本身也是可以被快取的。下一輪你可以把斷點移到它後面,或者讓自動快取去處理,它就跟其他歷史訊息一樣從快取裡讀出來。
這個約束不只長在這裡
拆到底,這整個功能只是在處理一個限制:前綴不可變。
一旦某個系統用「從頭開始逐位元比對」來決定能不能重用先前的計算,那麼「把變動往後放」就永遠比「修改前面」便宜,而且會便宜非常多。你要做的事情不是想辦法改得更小心,是重新設計資料的擺放位置,讓需要變的東西天生就落在後面。
這個形狀你在別的地方見過。Docker 的 layer 快取,你把 COPY package.json 和 npm install 放在 COPY . . 前面,就是同一招。Git 的 packfile、CDN 的 edge cache、增量編譯,全部都是這條規則的變形。差別只在於,這一次那個「前綴」是 tools 陣列,而重算它的帳單是按 token 計的。
所以下次你在寫 agent、想著要不要中途換工具清單的時候,問題不是「這樣改對不對」,是「我這個變動落在前綴的哪一段」。答案在最後一段就沒事,在第一頁就要付錢。
還沒解掉的部分我也標一下:工具版本目前還是 beta,header 帶著 2026-07-01 這個日期,代表它還可能改。而 Sonnet 5 兩個功能都沒有。這篇的內容我是照官方文件和 release notes 整理的,defer_loading 那組行為我還沒在自己的 agent 上實跑過,真的接的時候記得先用 cache_read_input_tokens 驗證省下來的數字,不要憑感覺。想先搞懂快取本身怎麼運作的,可以回頭看 Prompt Caching 那篇;想處理的是舊工具結果塞爆 context 的問題,那是 context editing 與 memory tool 的守備範圍。
來源:Mid-conversation system messages and tool changes(官方文件)、Claude Platform release notes(2026-07-24 條目)。程式碼範例改寫自官方文件的 Python 與 JSON 範例。




































































































































































