Chapter 5

第5章: function calling の正体 ── JSON Schema が支える契約

Claude Code、Cursor、Codex CLI、Aider ── どのプロダクトでも、起動して「fibonacci.py を読んで」と頼むと、画面にはほぼ同じものが現れる。

● Read(fibonacci.py)
  ⎿  23 行を読み込みました

LLM は「続きを書くだけの機械」だった (第1章)。 「考える・動く・観る」のうち、動く の中身が tool 呼び出しだった (第3章)。

しかしここに、一段下の謎が残っている。 テキストを吐くだけのモデルが、なぜ Read(fibonacci.py) のような構造化された命令を返せるのか

答えは ── どのプロダクトの中を覗いても ── 同じひとつの仕掛けに行き着く。 JSON Schema による「契約」 だ。

5.1 そもそも、なぜ “テキストのまま” ではダメなのか

ハーネス (第2章) は、LLM の出力を受け取って 本当にファイルを読みに行く プログラムだ。 LLM が「fibonacci.py を読んでください」という日本語を返してきたら、ハーネスはこれを解釈して実際の関数を呼ばないといけない。

素直に思いつくのは「自然言語のままパースする」やり方だろう。

「ファイル fibonacci.py を読んでください」 → 動詞「読む」を検出 → 目的語「fibonacci.py」を抽出 → read_file("fibonacci.py") を実行

ところがこのやり方は、すぐに破綻する。

自然言語のパースは、揺らぎに弱すぎる。 LLM の意図を確実にハーネスへ届けるには、形が決まった「命令の入れ物」 が要る。

ここで業界が選んだ答えが ── 名前はプロダクトによって違うが ── 本章のテーマ function calling / tool use だ。

5.2 ツールを「カタログ」として LLM に渡す

仕組みを最小単位から組み立てよう。 まずハーネスは LLM に 「あなたが使えるツール一覧」 を最初に渡す。

具体的にはこんなイメージ。

{
  "name": "Read",
  "description": "ファイルの中身を読み取って返す",
  "input_schema": {
    "type": "object",
    "properties": {
      "path": { "type": "string", "description": "読みたいファイルの絶対パス" }
    },
    "required": ["path"]
  }
}

部品はたった 3 つ。

部品意味
nameこのツールの呼び名 (Read, Write, Bash など)
descriptionこのツールが何をするかの自然言語説明。LLM が「いつ使うか」を判断する材料
input_schema引数の形を JSON Schema で厳密に定義
JSON Schema

JSON の値が満たすべき形を、別の JSON で記述するための仕様。「この値はオブジェクトで、path という文字列フィールドを必ず持つ」のような構造制約を機械可読に書ける。Web API の業界で長く使われてきた、枯れた標準。

このカタログを システムプロンプト相当の位置 (第7章で詳述) に流し込んでおくと、LLM はそれを「自分が使える道具のリスト」として認識する。

ここがエージェント設計の急所のひとつだ。 description は LLM への「求人票」 になっている。 「いつ私を呼んでください」を説得力のある言葉で書けるかどうかで、ツールの呼ばれ方が大きく変わる。 description の磨き込みは、次の第6章 (道具設計) で正面から扱う。

5.3 LLM の出力に “tool_use ブロック” が混ざる

カタログを与えられた LLM は、頼みごとを受けてこう考える。

fibonacci.py を読んで と言われた。カタログを見ると Read という道具がある。description にも合致する。これを呼べばよさそうだ」

そして LLM は、普通の文章ではなくカタログのスキーマに沿った構造化データ を出力する。 Anthropic の API ではこれが tool_use ブロック という形で返ってくる。

{
  "type": "tool_use",
  "id": "toolu_01ABC",
  "name": "Read",
  "input": { "path": "/repo/fibonacci.py" }
}

ポイントは 3 つ。

  1. type: "tool_use" という型タグで、「これは喋りではなく道具呼び出しだ」とハーネスに伝える
  2. name がカタログの name と一致する
  3. inputinput_schema の制約を満たしている (path が文字列で、必須項目が揃っている)

ハーネスは型タグを見て即座に分岐する。 普通のテキストなら画面に表示し、tool_use ブロックなら対応する関数を呼ぶ

LLM は内部では「続きを書く機械」のままだ。 ただ、出力する “続き” の中に 構造化された JSON ブロック を混ぜることを学習している。

つまり LLM がしているのは 「文章を書く」と「JSON を書く」の出し分け であり、それを ハーネスが型で見分けて分岐している だけ。 魔法ではなく、出力フォーマットの約束事の話だ。

5.4 なぜ “テキスト” ではなく “構造” なのか

ここで改めて、JSON という構造を介する理由を噛みしめておく。 LLM 側もハーネス側もコストを払って、わざわざ JSON という構造化フォーマットに合わせる。なぜか。

理由 1: パースの安定性

JSON は文法が厳密だ。{} で囲まれ、キーと値がコロンで結ばれる。パーサーはこの形をきっちり検証できる。 「Read me first というドキュメントを開いた」のような自然文と違って、誤検出の余地がほぼない

理由 2: 引数の型保証

input_schema が「path は文字列」と宣言している。 LLM が万一おかしな値を入れようとすれば、ハーネス側でスキーマ違反として弾ける。 これは Web API のリクエストバリデーションと全く同じ思想だ。

理由 3: 複数ツールの同時呼び出し

1 回の応答で 複数の tool_use ブロックを並べて返せる

[
  { "type": "tool_use", "name": "Read", "input": { "path": "a.py" } },
  { "type": "tool_use", "name": "Read", "input": { "path": "b.py" } }
]

自然言語だと「a.pyb.py を読んで」を 2 つの操作に分解するのにまた解析が要るが、構造化なら 配列の要素を順に処理するだけ。並列呼び出しは次章 (第6章) のもうひとつの主題でもある。

ツール呼び出しの本質は、自然言語の「意図」と機械の「実行」のあいだに、揺らがない契約 (JSON Schema) を一枚挟むこと。 LLM 側は「呼びたい道具と引数を考えること」に集中でき、ハーネス側は「契約に沿って実行すること」に集中できる。

5.5 業界横断 ── 名前は違うが構造は同じ

この仕組みは Anthropic 固有のものではない。主要 LLM プロバイダはほぼ全員、同じ構造を提供している。呼び名と細部のフォーマットだけ違う。

プロバイダ呼び名スキーマ
Anthropic (Claude)tool useJSON Schema
OpenAI (GPT)function callingJSON Schema
Google (Gemini)function declarationsOpenAPI スキーマ (実体は JSON Schema 系)

最初に “function calling” という名前を市場に広めたのは 2023 年中盤の OpenAI で、業界はその語感のまま定着した。 Anthropic はあとから “tool use” という呼び方を採用し、Google は “function declarations” と名乗っている。しかし三者を並べてみると、name + description + JSON Schema、そして応答に構造化ブロックを混ぜる ── という骨格は完全に同じ だ。

プロダクトに関係なく、LLM とハーネスを繋ぐ「契約面」はほぼ単一の形式に収束した。 だから Claude Code・Cursor・Codex CLI・Aider・Devin のような別チームのエージェントが、それぞれの内部で同じ概念を扱っている。 ツール定義を一度書けば、複数プロバイダのモデルで使い回せる ── という小さな移植性も、この収束のおかげで手に入っている。

実務メモ

モデルによって「ツールを呼ぶのが上手い・下手」の差は実在する。description が曖昧でも適切に呼んでくれるモデルもあれば、明示的に「次は必ず Read を使え」と書かないと自然言語で済ませてしまうモデルもある。同じ JSON Schema を渡しても、Claude / GPT / Gemini で呼び方の癖は違う ── これは本章の契約構造とは別レイヤの、モデル個性の話だ。

5.6 tool_use と tool_result ── 対になるブロック

ハーネスがツールを実行したあとは、その結果を LLM に戻す必要がある (第3章の observe 相当)。 ここでも同じ思想 ── 構造化ブロック ── が貫かれる。 Anthropic では tool_result という型タグ付きの入れ物を使う。

{
  "type": "tool_result",
  "tool_use_id": "toolu_01ABC",
  "content": "1\tdef fibonacci(n):\n2\t    if n < 2:\n..."
}

ポイントはひとつ。

なぜ id で紐付けるのか。複数の tool_use を一度に並べたとき (5.4 の理由 3)、結果も並列に返ってくる。順序ではなく id で対応させる ことで、ハーネス側も LLM 側も、どの結果がどの呼び出しに対応するか取り違えない。

LLM続きを書く機械ハーネス外側のプログラム道具 (Read など)実際にファイルを触る① ツールカタログ (name + description + JSON Schema)② tool_use ブロック (id 付き){"type":"tool_use","id":"toolu_01ABC","name":"Read","input":{...}}③ name で分岐 実関数を呼ぶ④ tool_result (同じ id で紐付け)この一往復が、エージェントの「1 ステップ」を構成する
図 5.1 — function calling の一往復。LLM は構造化ブロックを返し、ハーネスは型を見て分岐し、結果は同じ id を持つ tool_result ブロックで LLM に戻る。

ステップに分解するとこうなる。

  1. 準備: ハーネスがツールカタログを LLM に渡しておく
  2. 判断: LLM がユーザーの依頼を読み、tool_use ブロックを返す (id 付き)
  3. 分岐: ハーネスが typename を見て、対応する実関数を呼ぶ
  4. 観測: 実行結果を tool_result ブロック (同じ id) に詰めて LLM に返す

ここで戻した結果を見て LLM が次の手を考える。 このサイクルそのものは第3章で見た think → act → observe の actobserve の中身であり、繰り返すことで第1章の「動ける」状態が生まれる。

5.7 章を貫く問いへの答え

冒頭の問いに戻ろう。

テキストを吐くだけのモデルが、どうやって関数呼び出しを返しているのか。

答えはこうだ。

つまり「関数呼び出し」と見えていたものは、LLM が JSON を書き、ハーネスが JSON をパースして実行する という、地に足のついた約束事の積み重ねだった。

ここまで腑に落ちると、「LLM がツールを使いこなす」という言い方が、少し違って聞こえてくるはずだ。 LLM 側は JSON を上手に書ける だけ。ツールを実際に動かしているのはハーネスのほう。

この役割分担を頭に置いておくと、次章で「良い道具・悪い道具」を語るとき、なぜ description の言葉選びと粒度の決め方が そんなに 大事なのかが自然に腑に落ちる。

この章の振り返り

この章で読めるようになるツイート / ブログ

次章は、ここで握った契約の上に立つ ── 「良い道具・悪い道具」 の話だ。 description はどう書くか、道具の粒度はどう切るか、権限・副作用はどう線引きするか、複数の tool_use を 並列に並べる とは何が起きているのか。 契約構造を踏まえた、道具設計の急所を見にいく。