Function Callingは、LLMに「ツール(関数)を選び、引数を組み立て、呼び出しを依頼する」力を与える仕組みで、AIエージェントの実効力を決定づける中核機能です。実装の成否は、ツール定義の文言の明瞭さ・入力バリデーション・権限チェックの3点で決まります。雑に設計すれば即座にセキュリティインシデントの温床になる点は、心に留めておくべきです。
Function Callingとは何か
Function Callingとは、LLMがユーザーの意図を読み取り、あらかじめ定義された関数(ツール)のうちどれを呼び出すべきかを判断し、必要な引数をJSON形式で出力する機能です。LLM自身は関数を実行しません。あくまで「どの関数をどの引数で呼ぶか」を指示するだけで、実際の実行はアプリケーション側が担います。これによりLLMは、天気API、社内データベース、決済処理、ファイル操作など、外部システムと安全に連携できるようになります。
【Function Callingのフロー】
[ユーザー入力] 「東京の明日の天気を教えて」
|
v
[LLMがツール判断] get_weather(city, date) を選択
|
v
[LLM出力] {"tool": "get_weather", "args": {"city":"東京","date":"明日"}}
|
v
[アプリがAPI実行] 実際のHTTPリクエスト送信
|
v
[結果をLLMに返却] {"temp": 18, "weather": "晴れ"}
|
v
[LLMが最終回答生成] 「明日の東京は晴れ、18度の予報です」
Function CallingはAIエージェントの中核概念であり、MCP(Model Context Protocol)やガードレール設計、権限設計と切り離せない関係にあります。
Function Callingの実装
主要LLMプロバイダはいずれもFunction Callingに相当するAPIを提供していますが、呼び出し形式は微妙に異なります。ここではOpenAIとAnthropic Claudeの2例を示します。まずはOpenAI APIの基本実装です。
from openai import OpenAI
import json
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定都市の天気を取得",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名"},
"date": {"type": "string", "description": "日付"}
},
"required": ["city", "date"]
}
}
}]
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "東京の明日の天気は?"}],
tools=tools
)
tool_call = response.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
print(args) # {"city": "東京", "date": "明日"}
続いてAnthropic ClaudeのTool Use APIです。基本構造はOpenAIと似ていますが、入力スキーマの記述方法と戻り値の扱いに違いがあります。
import anthropic
client = anthropic.Anthropic()
tools = [{
"name": "get_weather",
"description": "指定都市の天気を取得します",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名"},
"date": {"type": "string", "description": "日付"}
},
"required": ["city", "date"]
}
}]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "大阪の明後日の天気を教えて"}]
)
for block in response.content:
if block.type == "tool_use":
print(block.name, block.input)
両者ともJSON Schemaで引数を記述する点は共通です。入力スキーマは後のバリデーション基盤にもなるため、最初から丁寧に設計することをおすすめします。
ツール設計のベストプラクティス
ツール設計で最も重要なのは「説明文の明瞭さ」です。LLMはツール名とdescriptionを読んで選択判断をするため、曖昧な記述は即座に選択ミスにつながります。たとえば「get_data」のような名前は最悪で、何のデータを返すのか分かりません。「get_user_monthly_sales」のように、対象・粒度・期間まで含めるのが理想です。
| 項目 | 良い例 | 悪い例 | 影響 |
|---|---|---|---|
| 関数名 | get_user_monthly_sales | get_data | 選択精度が大きく変化 |
| 説明 | 指定月のユーザー売上合計を返す | データを取得する | 誤選択のリスク |
| 引数名 | start_date, end_date | a, b | 引数構築の正確性 |
| 引数説明 | YYYY-MM-DD形式の日付 | 日付 | フォーマット誤りの多発 |
| 戻り値スキーマ | 明示(JSON型) | 暗黙 | 後続処理の安定性 |
| 副作用 | 明記(「このツールはDBを更新します」) | 言及なし | 予期せぬ破壊的操作 |
説明文はLLMにとってのドキュメントです。人間が読んで即座に理解できる文言であれば、LLMも高精度で選択します。逆に、プログラマ向けの簡潔すぎる記述はLLMを混乱させます。
複数ツールの管理とルーティング
ツール数が10を超えてくると、LLMの選択精度は明らかに低下します。これは、入力として渡すツール定義の全体トークン量が増え、各ツールへの注意が薄まるためです。現実的な対策は、ツールをカテゴリ分けして階層化することです。
| パターン | ツール数 | 管理方法 | メリット | デメリット |
|---|---|---|---|---|
| フラット | 〜10個 | すべて一括提示 | 実装が簡単 | 増えると精度低下 |
| カテゴリ分離 | 10〜50個 | カテゴリ単位で提示 | 選択精度が向上 | 2段階推論が必要 |
| 動的ロード | 50個以上 | 意図分類→必要ツールだけロード | スケールしやすい | 初期コスト増 |
| RAGベース | 100個以上 | 類似度検索で候補抽出 | 大量ツールに対応 | 検索誤りのリスク |
50個を超えるツールを抱えるシステムでは、ツール定義自体をベクトル検索にかけ、上位5〜10個だけをLLMに提示する設計が定石になってきました。これは実質的に、ツール選択をRAGの問題として扱う発想です。
セキュリティとガードレール
Function Callingの最大のリスクは、LLMが意図しない操作を引き起こすことです。たとえば「レポートを送信して」という入力に対し、LLMが本番DBの全レコードを削除するツールを誤選択しないとは限りません。この類のインシデントを防ぐには、実行前チェックが必須です。
def execute_tool(tool_name: str, args: dict, user_role: str):
PERMISSIONS = {
"viewer": ["get_weather", "get_report"],
"editor": ["get_weather", "get_report", "update_report"],
"admin": ["*"]
}
allowed = PERMISSIONS.get(user_role, [])
if "*" not in allowed and tool_name not in allowed:
raise PermissionError(f"role={user_role}は{tool_name}を実行できません")
if tool_name.startswith("delete_") and user_role != "admin":
raise PermissionError("破壊的操作はadminのみ許可")
return globals()[tool_name](**args)
破壊的操作(delete、update、send)は原則として人間の承認を挟むか、別途のアクセス制御を通すべきです。実行ログの記録もLLMOpsの観点から必須で、事後監査に備えてすべてのツール呼び出しを構造化ログとして残します。
まとめ
- Function CallingはLLMがツールを選択する仕組みであり、実行自体はアプリ側が担う
- ツール名と説明文の明瞭さが選択精度を左右する
- ツール数が10を超えたらカテゴリ分けや動的ロードで管理する
- 破壊的操作は権限チェック・人間承認・実行ログ記録の三重の防御を敷く
よくある質問
Function Callingとは何ですか
LLMがユーザーの入力に基づいて適切なツール(関数)を選択し、パラメータを生成してAPI呼び出しを行う仕組みです。LLMが外部システムと連携して実際のアクション(データ取得、計算、操作)を実行できるようになります。
Function Callingのツール数に上限はありますか
API仕様上は数十から128個程度のツールを定義できますが、ツール数が増えるとLLMの選択精度が低下します。実用的には10から20個以内に収め、ツールのカテゴリ分けやルーティングで管理することを推奨します。
Function Callingのセキュリティリスクは
LLMが悪意のある入力に基づいて意図しないツール実行を行うリスクがあります。入力バリデーション、実行前の権限チェック、破壊的操作の人間承認、実行ログの記録が必要な対策です。