コンテンツにスキップ

Tool Calling(関数呼び出し)

Tool Calling(関数呼び出し、Function Calling とも呼ばれる)により、モデルはテキスト生成だけでなく、必要に応じて構造化された「関数呼び出しリクエスト」を返すことができます。あなたのプログラムが実際のロジック(天気照会、データベースクエリ、注文)を実行し、結果をモデルに返すと、モデルはそれに基づいて最終的な応答を生成します。RouteAPI は /v1/chat/completions(OpenAI 形式)と /v1/messages(Claude 形式)の両方で Tool Calling をサポートしています。

Tool Calling は複数回のやり取りです:

  1. リクエストで「ツール」のセットを宣言します(各ツールには名前、説明、パラメータ schema があります)。
  2. モデルはツールが必要かどうかを判断し、必要な場合は直接回答する代わりに、パラメータ付きの呼び出しリクエストを返します。
  3. あなたのコードがパラメータを解析し、実際のロジックを実行して、結果を取得します。
  4. 結果をモデルに返すと、モデルはユーザー向けの自然言語応答を生成します。

モデル自体はコードを実行しません。「どのツールを呼び出すか」と「どのパラメータを渡すか」を決定するだけです。実際の実行は常にあなた側で行われます。つまり、セキュリティ境界(権限チェック、パラメータフィルタリング)はあなたが管理します。

シーン説明
リアルタイムデータ照会天気、為替レート、株価、在庫など、モデルのトレーニングデータにない情報
内部システム統合データベース照会、内部 API 呼び出し、注文ステータス読み取り
アクションの実行注文、メール送信、チケット作成など、副作用を伴う操作
構造化抽出モデルに固定 schema に従った出力を強制する、構造化出力の手段に相当
Agent のオーケストレーションAgent フレームワークが Tool Calling を使用して多段階タスクを駆動

Tool Calling 機能は選択したモデルによって異なります。主流のモデル(OpenAI GPT シリーズ、Claude シリーズ、Gemini シリーズなど)のほとんどがサポートしていますが、最大ツール数や並列呼び出しのサポートなどの詳細は異なります。モデルリスト API は Tool Calling 能力を示すフィールドを返さないため、モデルプロバイダーのドキュメントを確認するか、対象モデルに tools を含むリクエストを実際に1回送信して検証してください。詳細は Models を参照してください。

/v1/chat/completions では、ツールはトップレベルの tools 配列で宣言され、各ツールは type: "function" のネストされたオブジェクトです。

{
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された都市の現在の天気を照会します。都市名は中国語のフルネームを使用します。",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "都市名、例:北京" },
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度単位、デフォルトは摂氏"
}
},
"required": ["city"]
}
}
}
]
}
フィールドタイプ必須説明
typestringはい"function" に固定
function.namestringはいツール名、文字、数字、アンダースコア、ハイフンのみ使用可能
function.descriptionstring推奨ツールの用途説明、モデルはこれに基づいて呼び出しを決定
function.parametersobjectいいえパラメータ定義、標準 JSON Schema

description の品質は、モデルがツールを正しく選択するかどうかを直接決定します。用途、各パラメータの意味、値の範囲、境界条件を明確に記述することは、サンプリングパラメータを調整するよりも効果的です。

parameters は標準 JSON Schema を使用してパラメータ構造を記述します:

  • type: 通常は "object"。
  • properties: 各パラメータのタイプ、説明、列挙値。
  • required: 必須パラメータ名のリスト。
  • enum: 値の範囲を制限し、モデルが誤った値を渡す確率を大幅に削減。

パラメータのないツールでも空の schema を提供する必要があります: "parameters": { "type": "object", "properties": {} }。

/v1/messages では、ツール定義はフラット構造で、外側の type と function ラッパーはなく、パラメータフィールド名は input_schema です。

{
"tools": [
{
"name": "get_weather",
"description": "指定された都市の現在の天気を照会します。都市名は中国語のフルネームを使用します。",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "都市名、例:北京" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["city"]
}
}
]
}

input_schema の内部構造は OpenAI の parameters と完全に同じで、どちらも標準 JSON Schema です。違いは外側のラッパーのみです。

比較項目OpenAI(/v1/chat/completions)Claude(/v1/messages)
外側のラッパー{ "type": "function", "function": {...} }直接フラット構造、ラッパーなし
ツール名フィールドfunction.namename
説明フィールドfunction.descriptiondescription
パラメータフィールドfunction.parametersinput_schema
パラメータ schema標準 JSON Schema標準 JSON Schema(同一)
モデルの戻り値message.tool_calls 配列content 内の tool_use ブロック
結果を返すロール独立した role: "tool" メッセージuser メッセージ内の tool_result ブロック
結果関連フィールドtool_call_idtool_use_id

Claude プロトコルの完全な詳細(is_error、ストリーミング input_json_delta など)については、Claude Messages プロトコルを参照してください。このページの以降のサンプルは、デフォルトで OpenAI 形式を使用します。

tool_choice はモデルがツールを選択する方法を制御します。

値動作
"auto"モデルがツールを呼び出すかどうか、どれを呼び出すかを自分で決定。tools がある場合のデフォルト値
"none"すべてのツールの呼び出しを禁止し、モデルはテキストのみを出力
"required"少なくとも1つのツールを呼び出す必要があるが、どれを呼び出すかはモデルが選択
{ "type": "function", "function": { "name": "get_weather" } }指定されたツールの呼び出しを強制
{ "tool_choice": "auto" }

最も一般的。「ユーザーの質問が時にはツールを必要とし、時には直接回答する」という汎用シーンに適しています。

{ "tool_choice": "none" }

定義を保持したままツールを一時的に無効化。「最初にモデルに要約させ、ツールを再度呼び出さない」という締めくくり段階でよく使用されます。

{ "tool_choice": "required" }

モデルにツールパスを強制。「構造化結果を生成する必要がある」構造化抽出のようなシーンに適しています。

{
"tool_choice": {
"type": "function",
"function": { "name": "get_weather" }
}
}

Claude 形式の対応する書き方は { "type": "tool", "name": "get_weather" }、required は { "type": "any" } に対応し、none は { "type": "none" } に対応します。

完全な Tool Calling には少なくとも2回のリクエストが含まれます。

第1ラウンド:モデルが tool_calls を返す

Section titled “第1ラウンド:モデルが tool_calls を返す”

tools を含むリクエストを送信:

Terminal window
curl https://api.routeapi.ai/v1/chat/completions \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"messages": [
{ "role": "user", "content": "北京は今どんな天気ですか?" }
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された都市の現在の天気を照会します。",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}
]
}'

モデルがツールが必要と判断した場合、finish_reason は tool_calls、message.content は null、tool_calls に呼び出しリクエストが含まれます:

{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}

注意: arguments は JSON 文字列であり、オブジェクトではありません。自分で JSON.parse / json.loads で解析する必要があります。モデルが時々無効な JSON を生成することがあるため、解析は try/catch に入れる必要があります。

function.name で実際のロジックにディスパッチし、解析されたパラメータで実行:

import json
args = json.loads(tool_call["function"]["arguments"])
result = get_weather(**args) # "北京、晴れ、23度"

第1ラウンドの assistant メッセージ(tool_calls を含む)をそのまま messages に追加し、次に role: "tool" メッセージを追加して結果を運びます。tool_call_id は第1ラウンドの id と完全に一致する必要があります:

{
"model": "gpt-5.5",
"messages": [
{ "role": "user", "content": "北京は今どんな天気ですか?" },
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" }
}
]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "北京、晴れ、気温23度、湿度45%。"
}
],
"tools": []
}

第2ラウンドのリクエストは自然言語の結果を返し、finish_reason は stop に戻ります:

{
"choices": [
{
"message": {
"role": "assistant",
"content": "北京は現在晴れで、気温は23度、湿度は45%です。快適です。"
},
"finish_reason": "stop"
}
]
}

連続した複数のツール呼び出し

Section titled “連続した複数のツール呼び出し”

モデルがタスクを完了するために複数回のツール呼び出しが必要な場合があります:最初に注文番号を照会し、次に注文番号を使用して物流を照会します。各ラウンドは「モデルが tool_calls を返す → 実行 → 結果を返す」ループに従い、finish_reason が stop に戻るまで続きます。本番コードはループとして記述し、無限ループを防ぐために最大ラウンド数の上限を設定する必要があります:

MAX_TURNS = 5
for _ in range(MAX_TURNS):
resp = call_model(messages)
msg = resp["choices"][0]["message"]
messages.append(msg)
if not msg.get("tool_calls"):
break # モデルが最終応答を提供
for tc in msg["tool_calls"]:
result = dispatch(tc) # 実行して文字列を返す
messages.append({
"role": "tool",
"tool_call_id": tc["id"],
"content": result,
})

1ラウンドでモデルが複数の独立したツール(北京と上海の天気を同時に照会するなど)を同時にリクエストする場合があり、tool_calls は複数要素の配列になります。各 tool_call に対応する role: "tool" メッセージを追加する必要があり、tool_call_id を1対1で揃える必要があります。1つでも欠けると次のラウンドでエラーになります。

並列を無効化し、モデルに一度に1つのツールのみを呼び出すように強制したい場合は、OpenAI 形式で "parallel_tool_calls": false を追加し、Claude 形式では tool_choice に "disable_parallel_tool_use": true を追加します。

stream: true を設定すると、Tool Calling パラメータはチャンクごとの増分で返されます。

各 SSE chunk の delta.tool_calls には index(どのツール呼び出しかを識別)が含まれ、function.arguments はパラメータ JSON の断片です:

data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_abc123","type":"function","function":{"name":"get_weather","arguments":""}}]}}]}
data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]}}]}
data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"北京\"}"}}]}}]}
data: {"choices":[{"finish_reason":"tool_calls"}]}
data: [DONE]

index でグループ化して累積: id と name は通常最初の断片にのみ表示され、arguments はすべての断片を連結した後に解析する必要があります。連結中に中間状態を解析しないでください(それは不完全な JSON です)。

buffers = {} # index -> {"id", "name", "args"}
for chunk in stream:
for tc in chunk["choices"][0]["delta"].get("tool_calls", []):
i = tc["index"]
buf = buffers.setdefault(i, {"id": "", "name": "", "args": ""})
if tc.get("id"):
buf["id"] = tc["id"]
fn = tc.get("function", {})
if fn.get("name"):
buf["name"] = fn["name"]
if fn.get("arguments"):
buf["args"] += fn["arguments"]
# ストリーム終了後に解析
for buf in buffers.values():
args = json.loads(buf["args"])

Claude 形式のストリーミングツールパラメータは、input_json_delta イベントの partial_json 増分で返され、index で累積した後に解析します。詳細は Claude Messages プロトコルを参照してください。

Tool Calling をサポートするモデル

Section titled “Tool Calling をサポートするモデル”

RouteAPI が集約するほとんどの主流モデルは Tool Calling をサポートしています。OpenAI GPT シリーズ、Claude シリーズ、Gemini シリーズなどを含みます。まずモデルリスト API でモデルがアカウントで利用可能か確認してください:

Terminal window
curl https://api.routeapi.ai/v1/models \
-H "Authorization: Bearer $ROUTEAPI_KEY"

この API はモデルが利用可能かどうかと利用可能なエンドポイントのみを返し、Tool Calling 能力は返しません。Tool Calling をサポートするかどうかは、プロバイダーのドキュメントまたは tools を含む実際のリクエスト1回を基準としてください。

異なるモデルは以下の次元で違いがあります。本番環境に移行する前にテスト環境で検証してください:

次元説明
最大ツール数1回のリクエストで宣言できるツール数の上限はモデルによって異なります
並列呼び出し一部のモデルは1ラウンドで複数の tool_calls を返すことをサポートしていません
tool_choice のサポート度required / 特定のツールの指定はすべてのモデルでサポートされているわけではありません
パラメータの複雑さ深くネストされた、または非常に大きな JSON Schema は、一部のモデルで切り捨てられるか無視される可能性があります
ストリーミング増分の粒度arguments の分割方法はモデル間で一貫しておらず、必ず index で累積する必要があります

天気照会ツールサンプル(エンドツーエンド)

Section titled “天気照会ツールサンプル(エンドツーエンド)”

以下の3つのコードスニペットは機能的に同等です:ツールを定義 → 第1ラウンドで tool_calls を取得 → 実行 → 第2ラウンドで結果を返す → 最終応答を取得。

curl(手動で2ラウンド完了):

Terminal window
# 第1ラウンド:ツールを含むリクエストを送信
curl https://api.routeapi.ai/v1/chat/completions \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"messages": [{ "role": "user", "content": "北京は今どんな天気ですか?" }],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された都市の現在の天気を照会します。",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}]
}'
# 第2ラウンド:ツール結果を返す(tool_call_id は第1ラウンドで返された値を使用)
curl https://api.routeapi.ai/v1/chat/completions \
-H "Authorization: Bearer $ROUTEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"messages": [
{ "role": "user", "content": "北京は今どんな天気ですか?" },
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" }
}]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "北京、晴れ、気温23度、湿度45%。"
}
]
}'

Python(OpenAI SDK、自動的に2ラウンド完了):

import json
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["ROUTEAPI_KEY"],
base_url="https://api.routeapi.ai/v1",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された都市の現在の天気を照会します。都市名は中国語のフルネームを使用します。",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名、例:北京"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
},
}
]
def get_weather(city: str, unit: str = "celsius") -> str:
# ここを実際の天気サービス呼び出しに置き換える
return f"{city}、晴れ、気温23度、湿度45%。"
messages = [{"role": "user", "content": "北京は今どんな天気ですか?"}]
# 第1ラウンド
resp = client.chat.completions.create(
model="gpt-5.5", messages=messages, tools=tools
)
msg = resp.choices[0].message
if msg.tool_calls:
# assistant メッセージをそのまま追加しないと、tool_call_id が揃わない
messages.append(msg)
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = get_weather(**args)
messages.append(
{"role": "tool", "tool_call_id": tc.id, "content": result}
)
# 第2ラウンド
resp = client.chat.completions.create(
model="gpt-5.5", messages=messages, tools=tools
)
print(resp.choices[0].message.content)

Node.js(openai パッケージ、自動的に2ラウンド完了):

import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.ROUTEAPI_KEY,
baseURL: 'https://api.routeapi.ai/v1',
});
const tools = [
{
type: 'function',
function: {
name: 'get_weather',
description: '指定された都市の現在の天気を照会します。都市名は中国語のフルネームを使用します。',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '都市名、例:北京' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
},
required: ['city'],
},
},
},
];
function getWeather(city, unit = 'celsius') {
// ここを実際の天気サービス呼び出しに置き換える
return `${city}、晴れ、気温23度、湿度45%。`;
}
const messages = [{ role: 'user', content: '北京は今どんな天気ですか?' }];
// 第1ラウンド
let resp = await client.chat.completions.create({
model: 'gpt-5.5',
messages,
tools,
});
const msg = resp.choices[0].message;
if (msg.tool_calls) {
messages.push(msg); // assistant メッセージをそのまま追加
for (const tc of msg.tool_calls) {
const args = JSON.parse(tc.function.arguments);
const result = getWeather(args.city, args.unit);
messages.push({ role: 'tool', tool_call_id: tc.id, content: result });
}
// 第2ラウンド
resp = await client.chat.completions.create({
model: 'gpt-5.5',
messages,
tools,
});
}
console.log(resp.choices[0].message.content);

データベース照会ツールサンプル

Section titled “データベース照会ツールサンプル”

ツールを内部システムの安全なラッパーとして使用します。重要: SQL はモデルが直接生成するのではなく、モデルがパラメータを選択し、あなたのコードがパラメータ化されたクエリを構築してインジェクションを回避する必要があります。

tools = [
{
"type": "function",
"function": {
"name": "query_order",
"description": "注文番号で注文ステータスと金額を照会します。",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "注文番号、例:ORD-20260917-001",
}
},
"required": ["order_id"],
},
},
}
]
def query_order(order_id: str) -> str:
# パラメータ化されたクエリを使用し、モデルの出力を直接 SQL に結合しない
row = db.execute(
"SELECT status, amount FROM orders WHERE order_id = %s",
(order_id,),
).fetchone()
if row is None:
return json.dumps({"found": False})
return json.dumps({"found": True, "status": row[0], "amount": row[1]})

ツール結果は JSON 文字列として返すことをお勧めします。モデルは構造化フィールドをより安定して解析できます。

複数ツールオーケストレーションサンプル

Section titled “複数ツールオーケストレーションサンプル”

複数のツールを同時に宣言し、モデルが必要に応じて選択するか、並列で呼び出します。ここでは天気 + 為替レートの2つのツールを使用します:

tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された都市の現在の天気を照会します。",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
},
{
"type": "function",
"function": {
"name": "get_exchange_rate",
"description": "2つの通貨間の為替レートを照会します。",
"parameters": {
"type": "object",
"properties": {
"from_currency": {"type": "string", "description": "例:USD"},
"to_currency": {"type": "string", "description": "例:CNY"},
},
"required": ["from_currency", "to_currency"],
},
},
},
]
dispatch = {
"get_weather": lambda city: f"{city}、晴れ、23度。",
"get_exchange_rate": lambda from_currency, to_currency: f"1 {from_currency} = 7.2 {to_currency}",
}
messages = [{"role": "user", "content": "北京の天気はどうですか?ついでに米ドルから人民元への為替レートを教えてください。"}]
# 複数ラウンド / 並列ツール呼び出しを処理するループ、無限ループを防ぐために上限を設定
for _ in range(5):
resp = client.chat.completions.create(
model="gpt-5.5", messages=messages, tools=tools
)
msg = resp.choices[0].message
messages.append(msg)
if not msg.tool_calls:
break
# 並列呼び出し時、tool_calls は複数要素の配列で、それぞれに結果を返す必要がある
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = dispatch[tc.function.name](**args)
messages.append(
{"role": "tool", "tool_call_id": tc.id, "content": result}
)
print(messages[-1]["content"])

Tool Calling は、モデル側、あなたのコード側、上流サービス側の3つのエラーが発生する可能性のある箇所を導入します。

モデルが無効な JSON を返したり、必須パラメータが欠けていたり、enum 範囲外の値を渡したりする可能性があります。必ず:

  • JSON.parse / json.loads を try/catch でラップします。
  • 解析後、schema に従って必須フィールドと値の範囲を検証します。
  • 検証に失敗した場合は、エラーメッセージをツール結果として返し、モデルに自己修正させます。直接例外をスローして会話を終了しないでください:
try:
args = json.loads(tc.function.arguments)
city = args["city"] # 必須フィールドを検証
except (json.JSONDecodeError, KeyError) as exc:
result = f"パラメータ解析に失敗しました:{exc}。有効な city パラメータを再度提供してください。"
else:
result = get_weather(city)
messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})

ツールの背後には実際のサービスがあり、タイムアウトや利用不可になる可能性があります。各ツール呼び出しにタイムアウト上限を設定し、タイムアウト後は結果を明確なエラー説明として返し、モデルが別の戦略に切り替えられるようにします:

try:
result = call_service(args, timeout=5)
except TimeoutError:
result = "サービスがタイムアウトしました。データが取得できませんでした。後で再試行するか、別の方法を使用してください。"

副作用のあるツール(注文、メール送信)に自動リトライを行わないでください。繰り返し実行される可能性があります。冪等設計または最初に確認してから書き込む方が安全です。

ビジネスレベルのエラー(注文が見つからない、権限なし)も、空の値を静かに返すのではなく、モデルに返す必要があります。構造化されたエラー情報を返すと、モデルがユーザーに合理的な説明を提供できます:

messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps({"error": "order_not_found", "order_id": order_id}),
})

Claude 形式では、tool_result ブロックに "is_error": true を設定します。Claude Messages プロトコルを参照してください。

  • Tool Calling 機能、最大ツール数、並列呼び出しのサポートは選択したモデルに依存します。本番環境に移行する前にテスト環境で検証してください。
  • arguments は常に JSON 文字列であり、オブジェクトではありません。明示的に解析する必要があります。
  • ストリーミングシナリオでは、必ず index で arguments の断片を累積し、完全に連結した後に解析してください。
  • 0 または false として明示的に渡されたオプションのパラメータは、ユーザーが明示的に設定したものと見なされ、デフォルトとして破棄されません。
  • 各リクエストの request ID、モデル ID、ステータスコード、トークン使用量を記録してトラブルシューティングを容易にします。エラー構造の詳細は エラーとデバッグを参照してください。