API 開發

定義 tool schema 與接住 tool call:實作細節全走一遍

一份 tool schema 有三個部分:name、description 與 input_schema,Claude 靠它判斷何時該用工具。呼叫 API 時把 schema 放進 tools 參數,Claude 決定用工具時會回傳含 text 與 tool use 區塊的多區塊訊息;你執行函式後,用 tool result 區塊包好結果,以 user 訊息送回,id 必須與請求一致。
定義 tool schema 與接住 tool call:實作細節全走一遍:文章重點卡

定義 tool schema 與接住 tool call:實作細節全走一遍

上一篇我們把工具函式寫好了,但 Claude 還不知道它的存在。這一篇處理兩件事:用 JSON schema 把工具介紹給 Claude,以及接住它發出的 tool call,把結果正確送回去。

這是 tool use 實作裡最容易踩坑的一段:訊息從單純的文字變成多區塊結構,id 要對得上,歷史要留完整。我們一步一步來。

你將學到什麼

schema 的三個組成

name、description、input_schema,各自扮演什麼角色。

讓 Claude 代寫 schema

貼上函式與官方文件,schema 草稿直接生出來。

讀懂多區塊訊息

text 區塊給人看,tool use 區塊給你的程式看。

tool result 的細節

id 對應、內容序列化、錯誤旗標,一個都不能錯。

Tool schema:Claude 要讀的說明書

JSON Schema 不是 AI 圈的發明,它是行之有年的資料驗證規格;AI 社群採用它,純粹因為拿來描述函式參數又方便又成熟。你可以把整份工具規格想成給 Claude 讀的說明書:它讀了,才知道世界上有這個工具、什麼時候該用、參數要怎麼填。一份完整的工具規格有三個部分:

  • name:清楚好懂的工具名稱,例如 get_weather
  • description:這個工具做什麼、什麼時候該用、會回傳什麼。
  • input_schema:描述函式參數的 JSON schema 本體。

description 是整份 schema 的靈魂

Claude 是靠 description 判斷「這個工具現在該不該用」的,所以它值得你多花幾句話:用 3 到 4 句說明工具做什麼、什麼情境該用、回傳什麼樣的資料,每個參數也各自給詳細描述。描述寫得含糊,Claude 就會在該用的時候不用、不該用的時候亂用。

我自己的經驗是,把 description 當成寫給一位新同事的交接文件來寫,效果最好:他沒看過你的程式碼,只能靠這幾句話決定要不要用這個工具、參數該填什麼。能讓人看懂的描述,Claude 通常也判斷得準。

偷吃步:請 Claude 幫你寫 schema

schema 不必從零手寫。把工具函式的程式碼貼給 Claude,附上 Anthropic 官方的 tool use 文件當脈絡,請它「為這個函式寫一份符合最佳實務的 tool calling JSON schema」,就能拿到一份格式正確的草稿。命名慣例上,建議 schema 變數跟著函式走:函式叫 get_current_datetime,schema 就叫 get_current_datetime_schema,一眼對得上。

get_current_datetime_schema = {
    "name": "get_current_datetime",
    "description": "Returns the current date and time formatted according to the specified format",
    "input_schema": {
        "type": "object",
        "properties": {
            "date_format": {
                "type": "string",
                "description": "A string specifying the format of the returned datetime. Uses Python's strftime format codes.",
                "default": "%Y-%m-%d %H:%M:%S"
            }
        },
        "required": []
    }
}

想要更穩,可以從 anthropic.types 匯入 ToolParam 型別把 schema 包起來,讓型別檢查在開發階段就幫你擋掉格式錯誤。對功能來說不是必要,但 schema 拼錯欄位名這種錯誤,寧可在編輯器裡被抓到,也不要等到執行時才發現。

帶著工具呼叫 API:迎接多區塊訊息

messages = []
messages.append({
    "role": "user",
    "content": "What is the exact time, formatted as HH:MM:SS?"
})

response = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    tools=[get_current_datetime_schema],
)

在 API 呼叫加上 tools 參數後,回應的形狀就變了。Claude 決定用工具時,回傳的 assistant 訊息不再是單一文字,而是 content 清單裡裝著多個區塊:一個 text 區塊(給人看的說明,例如「我來幫你查現在的時間」),加上一個 tool use 區塊(給你的程式看的指令)。

tool use 區塊裡有四樣東西:追蹤用的 id、要呼叫的函式名稱、參數字典,以及型別標記 tool_use

assistant 訊息text 區塊「我來幫你查現在的時間」tool use 區塊id:abc123name:get_current_datetimeinput:參數字典user 訊息tool result 區塊tool_use_id:abc123content:工具輸出字串is_error:Falseid 必須一致
tool result 的 tool_use_id 必須和 tool use 區塊的 id 完全一致

有一件事絕對不能忘:Claude 不會自己記得對話歷史,歷史是你在維護的。把這則多區塊訊息塞回歷史時,必須原封不動保留整個 content 結構,text 區塊和 tool use 區塊一個都不能少,後續的呼叫才有完整脈絡。

messages.append({
    "role": "assistant",
    "content": response.content
})

如果你之前寫過 add_user_messageadd_assistant_message 這類輔助函式,這裡也要跟著升級:舊版大概假設內容永遠是一段純文字,現在它們得能接住多區塊的 content 結構,字串、區塊清單、完整訊息物件都要能塞進歷史。這一步不起眼,卻是後面多輪工具對話能不能順利跑起來的地基。

執行工具,把結果送回去

接下來換你動手:從 tool use 區塊取出參數字典。因為函式收的是關鍵字引數而不是字典,用 Python 的解包語法餵進去執行,再把結果用 tool result 區塊包好,放進一則 user 訊息送回。注意角色的安排:工具結果是以 user 的身分回給 Claude 的,因為對它來說,這是「外界告訴它的新資訊」,而不是它自己說過的話。

args = response.content[1].input
result = get_current_datetime(**args)

messages.append({
    "role": "user",
    "content": [{
        "type": "tool_result",
        "tool_use_id": response.content[1].id,
        "content": "15:04:22",
        "is_error": False
    }]
})
  • tool_use_id:必須和對應的 tool use 區塊的 id 完全一致。
  • content:工具的輸出,序列化成字串。
  • is_error:執行出錯時設為 True,讓 Claude 知道發生了什麼。

幾個一定會踩到的細節

  • Claude 可能一次要求多個工具呼叫:例如一句話問兩題算術,就會回兩個 tool use 區塊。每個呼叫都有獨立 id,回傳結果時逐一對上,就算結果順序亂了也不怕。
  • 送後續請求時,tools 參數還是要帶。就算你不預期它再呼叫工具,Claude 也需要 schema 才能理解歷史裡的工具往返。
  • 此時完整的歷史應該是三段:原始 user 訊息、帶 tool use 區塊的 assistant 訊息、帶 tool result 區塊的 user 訊息。

把這些做對,送出後續請求,Claude 就會回傳最後一則訊息,把工具結果自然地織進回答裡:使用者問幾點,它回的不再是「我查不到」,而是帶著剛拿到的時間好好回答。到這裡,一次完整的 tool use 往返已經走通;下一篇我們讓它連續呼叫多個工具,處理更複雜的請求。

參考出處本文取材自 Anthropic 官方 Claude Academy 免費課程「Building with the Claude API」,由酒Ann 消化後以自己的視角重新編寫。想看英文原版課程,可到 Claude Academy 修習。

延伸學習

把這篇文章分享給需要的人FacebookLINEThreadsX

常見問答

input_schema 一定要手寫嗎?
不用。把工具函式的程式碼貼給 Claude,附上官方 tool use 文件當脈絡,請它產生符合最佳實務的 schema 草稿,再自己檢查微調即可。
為什麼要原封不動保留多區塊的 content?
因為 Claude 不會自己記得對話歷史。若你只存了文字、丟掉 tool use 區塊,後續呼叫就少了工具往返的脈絡,Claude 會無法正確接上先前的請求。
tool_use_id 對不上會怎樣?
Claude 靠 id 把結果對回請求,一次多個工具呼叫時尤其重要。id 不一致,它就無法確定哪個結果屬於哪個請求,回答自然不可靠,所以務必逐一對應。
已經拿到工具結果了,後續請求為什麼還要帶 tools?
因為對話歷史裡有工具往返的區塊,Claude 需要 schema 才能理解這些內容。即使你不預期它再呼叫工具,tools 參數仍然要帶著。