Function calling 各家看起來都一樣——直到它不一樣。OpenAI 把 tool_calls 當成你要跨串流區塊累加的 delta 送來。Anthropic 把 tool_use 回傳為與 text 區塊平起平坐的內容區塊。Google 把一切包進 candidates、帶 functionCall 物件。DeepSeek 緊跟 OpenAI——直到並行呼叫時開始分道。
你每次換模型,Agent 程式就壞一次。這份指南修好這件事。四家供應商都能跑的程式碼。告訴你哪裡會壞的差異表。還有一個讓你「工具定義寫一次、到處用」的統一包裝器模式。Function calling 是每個AI Agent 建構的地基——掌握工具迴圈,Agent 架構就變成簡單的事。
Function calling 實際上怎麼運作
模式對所有供應商都一樣。一次搞懂它,比背每家供應商的語法重要。
工具迴圈:
- 你定義工具——名稱、描述、參數的 JSON Schema
- 你把使用者訊息+工具定義送給模型
- 模型決定要回文字,還是請求工具呼叫
- 如果是工具呼叫:你的程式解析函式名與引數——執行函式——把結果送回
- 模型處理結果——決定:回文字,還是再呼叫另一個工具
- 重複,直到模型回文字或你撞到最大疊代次數
Function calling vs 結構化輸出。 Function calling:模型決定何時用工具——需要自主性時用(「自己判斷需要什麼資訊並取得它」)。結構化輸出:模型永遠回你的 schema——需要保證格式時用(「永遠回傳帶這些欄位的 JSON 物件」)。
供應商一:OpenAI Function Calling
OpenAI 的function calling 實作最成熟,是其他人跟隨的參考標準。
from openai import OpenAI
import json
client = OpenAI(
base_url="https://api.tokspan.com/v1",
api_key="ts-your-key-here"
)
tools = [{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "Get the current stock price for a ticker symbol. Returns price in USD.",
"parameters": {
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
},
"required": ["symbol"]
}
}
}]
response = client.chat.completions.create(
model="gpt-5.5",
messages=[{"role": "user", "content": "What's Apple's stock price?"}],
tools=tools,
tool_choice="auto"
)
msg = response.choices[0].message
if msg.tool_calls:
for tool_call in msg.tool_calls:
args = json.loads(tool_call.function.arguments)
result = execute_stock_lookup(args["symbol"])
# Send result back
messages = [
{"role": "user", "content": "What's Apple's stock price?"},
msg,
{"role": "tool", "tool_call_id": tool_call.id, "content": str(result)}
]
final = client.chat.completions.create(model="gpt-5.5", messages=messages)
print(final.choices[0].message.content)
OpenAI 特點。 並行工具呼叫:GPT-5.5 能在一個回應裡要求多個工具——檢查 msg.tool_calls 是否有多個項目。串流:tool_calls 以 delta 抵達——跨區塊累積 index、function.name、function.arguments。結構化輸出+function calling:用 strict: true 定義工具參數,保證 schema 相符。
供應商二:Anthropic Tool Use
Claude 的tool use結構上不同——工具以訊息內的內容區塊出現,不是獨立的欄位。
import anthropic
client = anthropic.Anthropic(
base_url="https://api.tokspan.com/anthropic",
api_key="ts-your-key-here"
)
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
tools=[{
"name": "get_stock_price",
"description": "Get the current stock price for a ticker symbol.",
"input_schema": {
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
},
"required": ["symbol"]
}
}],
messages=[{"role": "user", "content": "What's Apple's stock price?"}]
)
for block in response.content:
if block.type == "tool_use":
# Execute the tool Claude requested
result = execute_stock_lookup(block.input["symbol"])
# Build the conversation continuation —the full cycle:
# 1. The assistant message contains ALL content blocks from Claude's response
# 2. The user message contains tool_result blocks matching each tool_use
assistant_msg = {"role": "assistant", "content": response.content}
tool_result_msg = {
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
}]
}
# Send the result back and get Claude's final response
follow_up = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
messages=[
{"role": "user", "content": "What's Apple's stock price?"},
assistant_msg,
tool_result_msg
]
)
# Claude will return a text block with the final answer
for follow_block in follow_up.content:
if follow_block.type == "text":
print(follow_block.text)
與 OpenAI 的關鍵差異。 工具定義用 input_schema 而不是 parameters。工具呼叫是 response.content 內的 tool_use 內容區塊——與 text 區塊平起平坐,不是獨立欄位。工具結果以 tool_result 內容區塊放在使用者訊息裡送出。串流包含部分的 tool_use 區塊——你逐步拿到工具名稱與引數。
供應商三:Google Gemini Function Calling
# Gemini uses a different structure —function declarations with OpenAPI-like schema
tools = [{
"function_declarations": [{
"name": "get_stock_price",
"description": "Get the current stock price for a ticker symbol.",
"parameters": {
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
},
"required": ["symbol"]
}
}]
}]
# Response structure:
# response.candidates[0].content.parts[0].function_call.name
# response.candidates[0].content.parts[0].function_call.args
Gemini 特點。 自動 function calling:Gemini 能在單一 API 請求裡呼叫並執行函式——在工具設定裡設 automatic_function_calling。以 Google 搜尋結果為基礎(grounding):內建的「工具」會用 Google 搜尋結果為回應提供依據,不用你實作搜尋 API。
供應商四:DeepSeek Function Calling
DeepSeek 遵循 OpenAI 格式。同樣的工具定義、同樣的回應結構。實際差異:並行工具呼叫比 GPT-5.5 不可靠——該並行呼叫的工具有時會被依序呼叫。如果你要從 GPT-5.5 換到 DeepSeek,特別測試你的多工具情境。
# Identical to OpenAI code —just change base_url and model
client = OpenAI(
base_url="https://api.tokspan.com/v1",
api_key="ts-your-key-here"
)
# Same tool definitions, same response handling as OpenAI example above
跨供應商差異表
| 功能 | OpenAI | Anthropic | DeepSeek | |
|---|---|---|---|---|
| 工具定義格式 | function.parameters(JSON Schema) | input_schema(JSON Schema) | function_declarations.parameters | 與 OpenAI 相同 |
| 回應位置 | message.tool_calls[] | content[] 區塊 | candidates[].content.parts[] | 與 OpenAI 相同 |
| 並行工具呼叫 | 是,可靠 | 是,可靠 | 是 | 部分支援,較不可靠 |
| 串流工具 | Delta,需累積 | 部分區塊 | 部分 candidates | 與 OpenAI 相同 |
| 工具選擇控制 | tool_choice: "auto"/"required"/"none" | 帶類似選項的 tool_choice | function_calling_config | 與 OpenAI 相同 |
| 每請求最大工具數 | 128 | 未文件化(很大) | 未文件化 | 遵循 OpenAI |
| 切換所需程式碼修改 | — | 100%(不同 SDK) | ~80% | 0%(自 OpenAI) |
常見陷阱
這些是會出貨到生產的 bug。每一個都有一下午就能實作的修法——前提是你知道在使用者之前先去找它。
1. 串流 tool_calls 累積 bug。 串流模式下,tool_calls 跨多個區塊抵達——每個帶著 index、部分的 function.name、部分的 function.arguments 字串。錯誤做法:在帶 finish_reason: "tool_calls" 的最終 delta 區塊到達前,對引數字串呼叫 json.loads()。你每次都會拿到 JSONDecodeError,而且部分狀態被破壞讓重試邏輯更糟。按 index 跨區塊累積引數。只有當串流表示完成時才解析。這是最常見的生產工具呼叫故障模式之一。
2. 供應商特定的 JSON Schema 差異。 OpenAI 在工具參數 schema 支援 $ref、anyOf、巢狀 oneOf。Gemini 默默忽略 $ref 定義——你的工具照樣能跑,但模型永遠看不到被引用的 schema。Anthropic 的伺服器端驗證比 OpenAI 嚴格——在 GPT-5.5 上通過的 schema 在 Claude 上回 400、帶一個晦澀的驗證錯誤。把你的 schema 對每一家供應商在 CI 裡測——不要上線前一天手動測。每家供應商 API 的 CI schema 驗證步驟幾分鐘就能抓出來。
3. 並行工具呼叫 ID 對不上。 模型在一個回應裡回 get_price("AAPL") 和 get_price("GOOGL")。你並行執行兩個。結果亂序到達。你假設位置對應執行順序,結果對回錯的 tool_call_id。模型在 AAPL 的 ID 下收到 GOOGL 的價格,產生一個自信、貌似合理、完全錯誤的答案。在建立結果訊息之前,永遠用 tool_call_id 索引結果。絕不依賴陣列位置。
4. 被當成合法資料的工具錯誤。 你的 get_stock_price 函式的 HTTP 呼叫逾時。你接到例外、回傳字串 "Error: connection timeout"。模型把那個字串當資料讀,回覆:「目前價格是 Error: connection timeout。」用 TOOL_ERROR: <type> —<message> 這種可辨識的前綴格式化工具錯誤。在工具的 description 欄位描述錯誤處理,讓模型知道要重試或告訴你工具失敗了。你不給訊號的話,模型分不出 bug 和異常資料。
統一的 Function Calling 包裝器
包裝器模式:用供應商無關的格式把工具定義一次。呼叫時轉成每家供應商的原生格式。把回應正規化回統一的格式。
class UnifiedToolClient:
"""One tool definition. Any provider. Automatic translation."""
def __init__(self, base_url: str, api_key: str):
self.openai_client = OpenAI(base_url=base_url, api_key=api_key)
def call_with_tools(self, model: str, messages: list, tools: list):
"""Provider-agnostic tool calling. Handles translation internally."""
# Tools defined in OpenAI format —works for OpenAI, DeepSeek, and
# platforms that translate to Anthropic/Google natively
response = self.openai_client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
tool_choice="auto"
)
return self._normalize_response(response)
def _normalize_response(self, response):
"""Return a unified format regardless of which provider served the request."""
msg = response.choices[0].message
return {
"text": msg.content,
"tool_calls": [
{"name": tc.function.name, "arguments": json.loads(tc.function.arguments)}
for tc in (msg.tool_calls or [])
] if msg.tool_calls else []
}
聚合平台的近路。 這個包裝器是 30 行程式。但它只處理講 OpenAI 相容格式的供應商的轉換。Anthropic 原生功能(思考+工具使用結合、串流部分工具結果)和 Google 原生功能(自動 function calling),你需要一家對每家供應商都有原生協定支援的平台——否則你在維護三條獨立的程式路徑。帶多協定支援的平台在基礎設施層處理這件事。你的程式保持供應商無關,同時每家供應商的獨特功能仍然可用。如果你剛開始接觸統一工具呼叫,TokSpan 快速入門指南會在五分鐘內帶你設定第一個多供應商工具請求。
常見問題
哪家供應商的 function calling 最好?
GPT-5.5:最可靠、並行呼叫最好、生態系最強。Claude Opus:推理深度重要的複雜多步驟工具鏈最好。Gemini:自動 function calling 對簡單工具是便利上的勝利。DeepSeek:簡單工具夠好、並行呼叫偶爾不可靠。工具可靠度關鍵時用 GPT-5.5。工具推理深度比原始可靠度重要時用 Claude。
所有供應商能用同一份工具定義嗎?
原生不行。JSON Schema 共用,但包裝器格式不同。用轉換層(30 行 Python)或自動轉換的聚合平台。你的工具定義——名稱、描述、參數 schema——即使包裝器格式不通用,本身是可移植的。
每個請求能定義多少工具?
OpenAI:128。Anthropic:沒文件但很多。Google:沒有硬上限。實際上,超過 10 個工具會讓選擇準確度退化——模型開始搞混名字類似的工具。讓你的作用中工具集保持聚焦。
該自己建包裝器還是用平台?
只用 1–2 家供應商、需要對工具呼叫迴圈做特定控制,就自己建。想自由切換供應商、不想維護四條程式路徑,就用平台。這份指南的包裝器模式實作與維護只要 30 分鐘。平台把它變成零——處理四家供應商轉換與正規化的平台層級 API,見TokSpan 文件。
Function calling 是每個 AI Agent 的地基。但這裡有個產業還沒回答的尷魀問題:為什麼 2026 年了,每家 LLM 供應商的工具定義格式還是有點不一樣?JSON Schema 是共用的。「tool_call」的概念也是共用的。但包裝器格式——input_schema 對 parameters、tool_use 區塊對 tool_calls 陣列——仍然頑固地依供應商。一個標準化機構可以用六個月的工作組修好這件事。到目前為止,沒人召集過。問題是:市場會透過 OpenAI 相容預設強迫標準化,還是原生工具使用功能會變得分歧到讓跨供應商相容性被永久放棄?
試試統一的 function calling——一份工具定義。四家供應商。零包裝器程式碼——趁產業還在搞懂標準化到底來不來。