Function CallingTool UseLLM APIAI Agents

跨 LLM API 的 Function Calling 與工具使用:跨供應商指南

閱讀 1 分鐘

Function calling 各家看起來都一樣——直到它不一樣。OpenAI 把 tool_calls 當成你要跨串流區塊累加的 delta 送來。Anthropic 把 tool_use 回傳為與 text 區塊平起平坐的內容區塊。Google 把一切包進 candidates、帶 functionCall 物件。DeepSeek 緊跟 OpenAI——直到並行呼叫時開始分道。

你每次換模型,Agent 程式就壞一次。這份指南修好這件事。四家供應商都能跑的程式碼。告訴你哪裡會壞的差異表。還有一個讓你「工具定義寫一次、到處用」的統一包裝器模式。Function calling 是每個AI Agent 建構的地基——掌握工具迴圈,Agent 架構就變成簡單的事。

Function calling 實際上怎麼運作

模式對所有供應商都一樣。一次搞懂它,比背每家供應商的語法重要。

工具迴圈:

  1. 你定義工具——名稱、描述、參數的 JSON Schema
  2. 你把使用者訊息+工具定義送給模型
  3. 模型決定要回文字,還是請求工具呼叫
  4. 如果是工具呼叫:你的程式解析函式名與引數——執行函式——把結果送回
  5. 模型處理結果——決定:回文字,還是再呼叫另一個工具
  6. 重複,直到模型回文字或你撞到最大疊代次數

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 抵達——跨區塊累積 indexfunction.namefunction.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

跨供應商差異表

功能OpenAIAnthropicGoogleDeepSeek
工具定義格式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_choicefunction_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 支援 $refanyOf、巢狀 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_schemaparameterstool_use 區塊對 tool_calls 陣列——仍然頑固地依供應商。一個標準化機構可以用六個月的工作組修好這件事。到目前為止,沒人召集過。問題是:市場會透過 OpenAI 相容預設強迫標準化,還是原生工具使用功能會變得分歧到讓跨供應商相容性被永久放棄?

試試統一的 function calling——一份工具定義。四家供應商。零包裝器程式碼——趁產業還在搞懂標準化到底來不來。