【AIエージェントの解剖学:第2回】知能に手足を与える:Function Callingと構造化出力
【新連載:AIエージェントの解剖学】
※本連載は、何も話せない空っぽの「チャットボット」の前に立ち、彼に命を吹き込みながら、自分の意志で動く「AIエージェント」へとパーツごとに作り上げていく開発ドキュメンタリーです。
1. プロローグ:次のパーツ「言葉を物理的な力に変える神経」
前回、私たちはロボットの脳の空きスロットに、思考と行動の基本回路である「ReAct」のチップを差し込みました。
これにより、彼は「現状を整理し、次に必要な行動を頭の中で計画する」という知的な鼓動を始めました。
しかし、今の彼はまだ「頭の中で考えているだけ」の存在です。
「天気を調べるために、天気予報APIを呼び出す必要がある」と思考することはできても、実際にネットワークの向こう側にあるAPIを叩いたり、PCのファイルを読み書きする具体的な「手足」はまだ持っていません。
今回は、彼の言葉としての意志を、プログラムやAPIを物理的に動作させる手足へと電気的に接続する神経インターフェース、「Function Calling(関数呼び出し)と構造化出力」の神経ケーブルをロボットに装着します。
アームにケーブルを這わせ、外部システムと同期させましょう。
2. 曖昧な言葉を、厳密な「データ構造」へ変換する
LLMの本質は、「入力された文章に続く、確率的にもっともらしい言葉を生成し続けること」です。
しかし、コンピュータのプログラムやAPIを動かすためには、人間が使うような曖昧な言葉ではなく、関数名や引数(引数の名前と型)といった「1文字の狂いもない厳密なデータフォーマット(JSON)」が必要です。
「東京の天気を調べて」という人間の曖昧な要求から、プログラムが処理できる get_weather(city="Tokyo") という厳密な形式を、LLMにどうやって出力させるでしょうか?
Function Callingが登場する以前は、プロンプトに「必ずJSON形式で出力してください。余計な説明文は一切省いてください」と涙ぐましい努力で懇願していました。しかし、それでもLLMは気まぐれに「はい、JSONを出力します: json ... 」と余計な枕詞をくっつけてしまい、プログラム側でパース(解析)エラーを起こして暴走するのが日常茶飯事でした。
この課題をモデルの学習とデコード制御によって解決したのが、「Function Calling」と、JSONの形式を100%保証する「構造化出力(Structured Outputs)」です。
3. Function Callingのデータフロー
Function Callingを実装したエージェントの内部では、以下のようなデータの往復が発生しています。

※図:ユーザー要求とツール定義(スキーマ)からJSONを抽出し、関数を実行して結果を戻すFunction Callingのデータフロー(AI生成ダイアグラム)
-
仕様書の提示:
プログラム側は、ユーザーの質問と一緒に、エージェントが使用できる関数の「仕様書(JSON Schema)」をLLMに提示します。 -
意図の抽出(Tool Call):
LLMは質問と仕様書を読み、「この質問に答えるには、この関数をこのパラメータで呼ぶべきだ」と判断します。そして、人間向けの会話テキストではなく、「関数名と引数を格納したJSONデータ」だけを出力して応答を一時停止します。 -
関数の実行:
プログラム側がそのJSONをパースし、ローカル環境で実際の関数(API呼び出しなど)を実行します。 -
結果の観察:
実行結果(生データ)をLLMに差し戻し、LLMがそれをもとに最終的な回答テキストを作成します。
これによって、曖昧な「言葉の海」と、厳密な「プログラムコードの世界」が、安全かつ確実なJSONの神経系で繋がったのです。
4. 【体験】30行のFunction Callingシミュレータを動かそう
実際のWeb APIを叩かなくても、LLMが提示されたスキーマ(仕様書)を解釈し、プログラム側がその出力JSONを元に関数を動的実行する「Function Callingの動作原理」を体験できるPythonコードです。
手元の環境で実行してみてください。
import json
# エージェントが使えるローカルの関数(手足)
def calculate_distance(speed: float, hours: float) -> str:
distance = speed * hours
return f"計算結果: 移動距離は {distance} km です。"
# 1. プログラム側が定義する「関数の仕様書(Schema)」のシミュレート
tools_schema = {
"name": "calculate_distance",
"description": "速度と時間から移動距離を計算する関数",
"parameters": {
"speed": "float (時速 km/h)",
"hours": "float (時間 h)"
}
}
# 2. ユーザーの曖昧な要求
user_query = "時速60kmで2.5時間走ったら、どれくらい進む?"
print(f"【ユーザー要求】: {user_query}\n")
# 3. LLMが仕様書を元に生成した「Tool Call JSON」(擬似出力)
# 実際はLLMの推論によって、以下の構造化テキストが自動生成されます
mock_llm_json_output = """
{
"call_function": "calculate_distance",
"arguments": {
"speed": 60.0,
"hours": 2.5
}
}
"""
print(f"🤖 LLMからの構造化出力 (JSON):\n{mock_llm_json_output.strip()}\n")
# 4. プログラム側でJSONを解析し、関数を動的に実行する(神経の伝達)
tool_call = json.loads(mock_llm_json_output)
function_name = tool_call["call_function"]
args = tool_call["arguments"]
print("⚡ システム: JSONを解析してローカルの関数を呼び出します...")
if function_name == "calculate_distance":
# 引数を展開して関数を実行
result = calculate_distance(speed=args["speed"], hours=args["hours"])
print(f"🔍 Observation (実行結果): {result}")
このコードを実行すると、LLMが出力したJSON文字列(mock_llm_json_output)をPythonシステムがパースし、安全にローカルの関数 calculate_distance を選択して正しい引数で実行する流れが再現されます。
5. エピローグ:手足が動き始めたロボット

※画像:ロボットのアームへ、言葉(JSON)を電気信号に変えて外部APIへ繋ぐ神経ケーブルを接続するイメージ(AI生成画像)
これで、私たちのロボットのアームに「Function Calling」の神経ケーブルが這わされ、彼の頭脳と外部世界を操作する手足が電気的に同期しました。
しかし、ここでもう一つの課題が浮かび上がります。
世の中には、天気予報、データベース、Slack、ファイルシステムなど、無数の外部ツールが存在します。新しいツールを追加するたびに、私たちが手作業でJSON Schemaを書き、個別の接続プログラム(APIクライアント)をゴリゴリと書くのは、あまりに非効率でスマートではありません。
もし、世界中のすべてのツールとAIを、まるでパソコンとUSBポートのように「挿すだけで使える」共通のプラグ&プレイ規格があったらどうでしょうか?
次回、第3回。
Anthropicが提唱し、AI業界に革命を起こしつつある最新の接続標準、「Model Context Protocol (MCP)の革新」という共通USBソケットを彼に装着します。
ロボットの拡張性が、世界中のすべてのツールへと無限に広がる瞬間を見届けましょう。