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_salesget_data選択精度が大きく変化
説明指定月のユーザー売上合計を返すデータを取得する誤選択のリスク
引数名start_date, end_datea, 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が悪意のある入力に基づいて意図しないツール実行を行うリスクがあります。入力バリデーション、実行前の権限チェック、破壊的操作の人間承認、実行ログの記録が必要な対策です。