Function CallingTool UseLLM APIAI Agents

LLM API横断のFunction Callingとツール使用:クロスプロバイダーガイド

約1分

Function callingは、どのプロバイダーでも一見同じに見えます——違いが現れるまでは。OpenAIはtool_callsを、ストリーミングチャンクを跨いで蓄積するデルタとして送ります。Anthropicはtool_usetextブロックと対等なコンテンツブロックとして返します。GoogleはすべてをcandidatesfunctionCallオブジェクトとして包みます。DeepSeekはOpenAIに忠実に従います——並列呼び出しで違ってくるまでは。

モデルを切り替えるたびに、エージェントコードが壊れます。このガイドはそれを修正します。4プロバイダーすべての動作コード。どこで何が壊れるかを示す差異表。ツール定義を一度書いてどこでも使える統合ラッパーパターン。Function callingはすべてのAIエージェント構築の基盤です——ツールループをマスターすれば、エージェントアーキテクチャは簡単になります。

Function callingの実際の仕組み

パターンは全プロバイダーで同じです。一度理解することが、各プロバイダーの構文を暗記するより重要です。

ツールループ:

  1. ツールを定義——名前、説明、パラメータのJSON Schema
  2. ユーザーメッセージ+ツール定義をモデルに送る
  3. モデルがテキストで応答するか、ツール呼び出しを要求するか決定
  4. ツール呼び出しなら:コードが関数名と引数をパース——関数を実行——結果を送り返す
  5. モデルが結果を処理——テキストで応答するか、別のツールを呼ぶか決定
  6. モデルがテキストで応答するか、最大反復回数に達するまで繰り返す

Function calling vs 構造化出力。 Function calling:モデルがいつツールを使うか決定。「必要な情報を判断して取得せよ」。構造化出力:モデルが常にあなたのスキーマを返す。「常にこれらのフィールドを持つJSONオブジェクトを返せ」——形式保証が必要なときに。

プロバイダー1: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は1つの応答で複数ツールを要求できます——msg.tool_callsに複数アイテムがあるか確認。ストリーミング:tool_callsはデルタとして到着——indexfunction.namefunction.argumentsをチャンク間で蓄積。構造化出力+function calling:strict: trueでツールパラメータを定義し、スキーマ準拠を保証。

プロバイダー2: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との主な違い。 ツール定義はparametersではなくinput_schemaを使用。ツール呼び出しはresponse.content内のtool_useコンテンツブロック——textブロックと対等で、別フィールドではありません。ツール結果はユーザーメッセージ内のtool_resultコンテンツブロックとして送信。ストリーミングには部分的なtool_useブロックが含まれます——ツール名と引数を段階的に取得。

プロバイダー3: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を設定。検索グラウンディング:組み込みの「ツール」が、検索APIを実装せずにGoogle検索結果で応答をグラウンディング。

プロバイダー4: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.parametersOpenAIと同じ
レスポンスの場所message.tool_calls[]content[] ブロックcandidates[].content.parts[]OpenAIと同じ
並列ツール呼び出しはい、信頼性ありはい、信頼性ありはい部分的、信頼性は低め
ストリーミングツールデルタ、蓄積が必要部分ブロック部分candidatesOpenAIと同じ
ツール選択制御tool_choice: "auto"/"required"/"none"同様のオプションを持つtool_choicefunction_calling_configOpenAIと同じ
リクエストあたりの最大ツール数128非公開(大きい)非公開OpenAIに従う
切り替え時のコード変更100%(別SDK)~80%0%(OpenAIから)

よくある落とし穴

これらは本番に出荷されるバグです。どれも午後ひとつで実装できる修正があります——ただし、ユーザーより先に探すことを知っていれば。

1. ストリーミングtool_callsの蓄積バグ。 ストリーミングモードでは、tool_callsが複数のチャンクに分かれて到着します——それぞれがindex、部分的なfunction.name、部分的なfunction.arguments文字列を持ちます。ミス:finish_reason: "tool_calls"を持つ最終デルタチャンクが到着する前に、引数文字列にjson.loads()を呼ぶ。毎回JSONDecodeErrorになり、部分状態が破損するためリトライロジックが悪化させます。indexごとにチャンクを跨いで引数を蓄積。ストリームが完了を示したときだけパース。これは最も一般的な本番ツール呼び出し障害モードの1つです。

2. プロバイダー固有のJSON Schema差異。 OpenAIはツールパラメータスキーマで$refanyOf、ネストoneOfをサポート。Geminiは$ref定義を黙って無視——ツールは動きますが、モデルは参照されたスキーマを決して見ません。AnthropicはOpenAIより厳格なサーバーサイド検証を実行——GPT-5.5で通るスキーマがClaudeで不明瞭な検証エラーの400を返す。スキーマをすべてのプロバイダーに対してCIでテスト——ローンチ前日に手動ではなく。各プロバイダーのAPIでのCIスキーマ検証ステップが、これを数分で捕捉します。

3. 並列ツール呼び出しID不一致。 モデルが1つの応答で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フィールドでエラー処理を説明し、モデルがリトライするか失敗を伝えることを知るように。シグナルを与えなければ、モデルはバグと異常データを区別できません。

統合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)には、各プロバイダーのネイティブプロトコルをサポートするプラットフォームが必要です——さもないと3つの別コードパスを維持することになります。マルチプロトコルサポートを持つプラットフォームはこれをインフラレベルで処理します。コードはプロバイダー非依存のままで、各プロバイダーの独自機能は利用可能なまま。統合ツール呼び出しを始めたばかりなら、TokSpanクイックスタートガイドが5分以内に最初のマルチプロバイダーツールリクエストの設定を案内します。

FAQ

どのプロバイダーのfunction callingが最良ですか?

GPT-5.5:最も信頼性が高く、並列呼び出しが最良、エコシステム最強。Claude Opus:推論深度が重要な複雑な多段階ツールチェーンに最良。Gemini:自動function callingは単純ツールの利便性で勝ち。DeepSeek:単純ツールに十分、並列呼び出しで時々信頼性が低い。ツール信頼性が重要ならGPT-5.5。ツール推論深度が生の信頼性より重要ならClaude。

全プロバイダーで同じツール定義を使えますか?

ネイティブにはできません。JSON Schemaは共有されますが、ラッパー形式は異なります。変換レイヤー(30行のPython)か、自動変換する集約プラットフォームを使いましょう。ツール定義——名前、説明、パラメータスキーマ——はラッパー形式が移植可能でなくても、ツール定義自体は移植可能です。

リクエストあたりいくつのツールを定義できますか?

OpenAI:128。Anthropic:文書化されていませんが大きい。Google:ハード制限なし。実際には、10ツール超で選択精度が劣化——モデルが似た名前のツールを混同し始めます。アクティブなツールセットを絞りましょう。

自分でラッパーを構築すべきですか、それともプラットフォームを使うべきですか?

1〜2プロバイダーを使い、ツール呼び出しループを特定制御したいなら構築。プロバイダーを自由に切り替えたい、4つのコードパスを維持したくないならプラットフォーム。このガイドのラッパーパターンは実装・維持に30分。プラットフォームならゼロ——4プロバイダーすべての変換と正規化を処理するプラットフォームレベルAPIはTokSpanドキュメントをご覧ください。

Function callingはすべてのAIエージェントの基盤です。しかし業界が答えていない厄介な問いがあります:なぜ2026年になっても、各LLMプロバイダーがツール定義にわずかに異なる形式を持っているのか? JSON Schemaは共有されています。「tool_call」の概念も共有。それでもラッパー形式——input_schemaparameterstool_useブロック対tool_calls配列——は頑固にプロバイダー固有のまま。標準化団体が6か月のワーキンググループでこれを解決できるはず。今のところ、誰も招集していません。問い:市場はOpenAI互換デフォルトを通じて標準化を強制するのか、それともネイティブツール使用機能が高度に差別化され、クロスプロバイダー互換性が永久に放棄されるのか?

統合function callingを試す——1つのツール定義。4つのプロバイダー。ゼロラッパーコード——業界が標準化が本当に来るのか考える間も。