API 開發

用 tool use 拿到穩定的結構化輸出:別再拜託模型「只回 JSON」

想從 Claude 拿穩定的結構化輸出,可以把你要的資料結構定義成一個工具的 input_schema,再請它呼叫這個工具。Claude 為了發出合法的工具請求,會照著 schema 的欄位與型別填值;預設模式下 API 還會在串流時逐組驗證頂層鍵值對,送到你手上的參數已經過了一層把關。
用 tool use 拿到穩定的結構化輸出:別再拜託模型「只回 JSON」:文章重點卡

用 tool use 拿到穩定的結構化輸出:別再拜託模型「只回 JSON」

很多人第一次想從 Claude 拿 JSON,走的是「在 prompt 裡拜託它只回 JSON」這條路。運氣好的時候可行,運氣不好它會在前後多聊兩句,或欄位跟你要的差一點,解析就炸了。

其實 tool use 有個常被忽略的用法:不是為了讓 Claude 拿資料,而是為了讓它交資料。把你想要的輸出結構定義成一個工具的 input_schema,Claude 填參數的過程,就是在產生一份符合結構的 JSON。

你將學到什麼

自由文字為什麼容易碎

開場白、圍欄、欄位漂移,每一種都要多寫防禦程式。

把結構定義成工具

input_schema 就是你的輸出規格書,Claude 照著填。

API 端的 JSON 驗證

串流時逐組驗證頂層鍵值對,通過才送到你手上。

fine-grained 的取捨

用驗證換速度之前,先想清楚要不要。

為什麼「請用 JSON 回答」常常不夠穩

用純文字指令要求 JSON,輸出畢竟還是自由文字:模型可能加一句開場白、在後面補說明、用 markdown 圍欄把內容包起來,甚至欄位名稱跟你要的有出入。每一種變化,你都得多寫一段解析與防禦程式。隨手玩玩無妨,要進正式環境就嫌脆弱。

更麻煩的是,這種失敗是機率性的:十次有九次好好的,第十次歪掉,而你的下游程式已經假設它永遠是合法 JSON。與其把穩定性押在模型今天的心情上,不如換一條天生就有規格的路。

換個思路:把想要的結構做成一個工具

tool use 的機制是:你用 input_schema 描述工具的參數,Claude 依照 schema 產生參數去呼叫。把這件事反過來用:定義一個工具,讓它的參數結構就是你想要的輸出結構,然後請 Claude 呼叫它。Claude 為了發出合法的工具請求,會照著 schema 的欄位與型別填值;你再從 tool use 區塊把參數字典整包取走,這就是你的結構化輸出。

換句話說,schema 從「描述輸入」變成了「規定輸出」,同一套機制,換個方向用。

老方法:在 prompt 裡懇求「請只回傳 JSON,拜託」自由文字:開場白+JSON+註解欄位可能漂移,格式可能歪掉解析容易失敗新思路:用 input_schema 當模具工具的 input_schema 定義輸出結構Claude 照 schema 填參數欄位與型別有規格可依整包取走,直接使用
input_schema 像一個模具:Claude 填進去的參數,天生就是你要的形狀

實際範例:save_article

來看一個實際例子:save_article 工具,用來把一篇文章的整理結果存下來。它的參數包含文章摘要 abstract,以及一個裝著字數 word_count 與評語 reviewmeta 物件。Claude 呼叫這個工具時,產生的參數天生就是這個形狀:

{
  "abstract": "This paper presents a novel...",
  "meta": {
    "word_count": 847,
    "review": "This paper introduces QuanNet..."
  }
}

你不需要再從散文裡把摘要和字數撈出來,也不用擔心模型今天心情好多送你三段說明。要摘要就有 abstract 欄位,要字數就有 word_count 欄位,拿了就能用。

同樣的做法可以套到各種場景:從履歷抽出姓名與工作經歷、把客服對話整理成分類與摘要、從文章抓出評分與標籤,任何「我要的是一份固定形狀的資料」的需求,都適合走這條路。

API 還會幫你把關:內建的 JSON 驗證

更棒的是,這條路不是全靠模型自律。先補一個背景:開啟串流時,工具參數的生成會以 InputJsonEvent 事件送達,每個事件帶兩樣東西:partial_json 是這一小段新生成的 JSON 片段,snapshot 是到目前為止累積出來的完整內容,方便你隨時拿到當下的全貌。

for chunk in stream:
    if chunk.type == "input_json":
        # Process the partial JSON chunk
        print(chunk.partial_json)
        # Or use the complete snapshot so far
        current_args = chunk.snapshot

重點來了:Anthropic API 不會把 Claude 生成的每個片段立刻丟給你,而是先緩衝起來做驗證。以上面的結構為例,流程是這樣:

  1. abstract 的值完整生成。
  2. 對照你的 schema 驗證這組鍵值對。
  3. 把 abstract 相關的緩衝片段一次送出。
  4. 再對 meta 物件重複同樣流程。

這解釋了為什麼開了串流還是會看到「停頓一下、噴出一段」的節奏:片段被扣住,直到湊成一組完整且合法的頂層鍵值對。對想拿穩定結構化輸出的人來說,這個看似惱人的行為其實是保障:送到你手上的參數,已經過了一層驗證

不過要留意,驗證層遇到有問題的值時,可能把它包成字串處理,型別不一定符合你原本的預期,關鍵欄位仍值得在自己這端多驗一次。

需要更即時?fine-grained tool calling 的交換條件

如果你的介面需要即時顯示生成進度,可以在呼叫時開啟 fine_grained=True。它做的事只有一件:關掉 API 端的 JSON 驗證。片段一生成就送到你手上,不再有緩衝延遲,但代價是 Claude 偶爾會生出不合法的 JSON,例如把數字欄位寫成 undefined,你的程式必須自己接得住:

try:
    parsed_args = json.loads(chunk.snapshot)
except json.JSONDecodeError:
    # Handle invalid JSON appropriately
    print("Received invalid JSON, continuing...")

開了 fine-grained 之後,你可能在串流很早的階段就先拿到 word_count 這種小欄位,不用等整個 meta 物件生成完畢,介面的即時感確實好很多。一句話總結取捨:預設模式用延遲換驗證,fine-grained 用驗證換速度。多數應用用預設就好;真的需要逐字進度時,記得把錯誤處理寫紮實。

實務建議

  • 結構化輸出需求明確時,優先考慮 tool use,而不是在 prompt 裡懇求格式。
  • schema 的欄位描述寫清楚,Claude 填值的品質會跟著提升。
  • 關鍵欄位在自己這端再驗一次型別與範圍,防線不嫌多。
  • 除非使用者體驗真的需要逐字串流,否則留著預設的驗證機制。
參考出處本文取材自 Anthropic 官方 Claude Academy 免費課程「Building with the Claude API」,由酒Ann 消化後以自己的視角重新編寫。想看英文原版課程,可到 Claude Academy 修習。
把這篇文章分享給需要的人FacebookLINEThreadsX

常見問答

用 tool use 拿結構化輸出,和在 prompt 裡要求 JSON 差在哪?
prompt 要求得到的仍是自由文字,模型可能加開場白或改欄位,解析防不勝防。tool use 則讓模型照著 input_schema 填參數,欄位與型別有規格可依,預設模式下 API 還會再驗證一層。
這個工具需要真的執行什麼嗎?
不一定。這種用法裡工具常常只是接收器:你要的其實是 Claude 填進參數的那份結構化資料,從 tool use 區塊把參數字典整包取走,目的就達成了。
串流時輸出會停頓一下再一次噴出,正常嗎?
正常。預設模式下 API 會緩衝片段,等一組頂層鍵值對完整並通過 schema 驗證,才把該組的片段一次送出,所以節奏是一段一段的。
什麼時候該開 fine-grained tool calling?
當你需要即時顯示生成進度、想盡快處理部分結果,或緩衝延遲明顯影響體驗時。代價是 API 端的 JSON 驗證被關掉,你的程式必須自己接得住不合法的 JSON。