LLM は文章しか生成できないはずなのに、なぜ Claude Code は実際にコマンドを実行できるのか?
あなたは毎日のように Claude Code へ「このバグを直して」と頼み、実際に手元のファイルが書き換わり、テストコマンドが実行されるのを見ている。だが LLM の正体は次のトークンを予測して文章を生成するモデルのはずで、ターミナルへの接続経路など持っていない。文章しか生成できないはずのモデルが、なぜ実際にコマンドを実行できているのだろうか。
- LLM は関数を直接実行できない。呼びたい関数名と引数を
input_schema(JSON Schema)に沿った構造化ブロックとして生成するだけで、実行するのは呼び出し元のアプリケーションコードだ。 - ツールを呼ぶかどうかの判断は特別な分類器ではなく、通常の文章生成と同じ次トークン予測の延長であり、既定の
tool_choice: autoのもとモデルが1トークンずつ決めている。 - 主要プロバイダは制約付きデコーディングで JSON 生成時に無効なトークンをマスクし、構造的にスキーマへ準拠した引数を保証している。ただし値の意味的な正しさまでは保証しない。
「ツールを渡す」とは実際に何をしているのか
Claude Code が Bash や Edit を実行できるように見えるのは、モデル自体がターミナルへの接続を持っているからではない。API を呼ぶたびに、リクエストの中に tools という配列で関数の定義を一緒に送っているだけだ。定義の中身は名前・説明文、そして input_schema という JSON Schema で、「この関数はこういう引数を受け取る」という形を宣言する。モデルの重み自体はここで一切変わらない。ツールの一覧は毎回のプロンプトの一部として渡される、ただの情報にすぎない。
この状態でモデルに質問を投げると、答え方の選択肢が増える。文章で回答することもできるし、「この関数をこの引数で呼びたい」という意思表示を、定義された input_schema に沿った構造化ブロックとして出力することもできる。Anthropic はこれを tool_use ブロック、OpenAI は tool_calls と呼ぶが、どちらも発想は同じで、モデルの出力は最後までテキストのままだ。
モデルが生成するのは「呼びたい」という意思表示の JSON だけであり、実際に天気 API を叩いたりファイルを書き換えたりするコードは、その JSON を受け取った側のアプリケーションが持っている。この分業関係が、文章生成モデルのはずの LLM が実際にターミナルを操作しているように見える理由だ。
LLM はレストランのウェイターに近い。ウェイターは客の注文を聞いて厨房への注文票を書くが、鍋を振って料理を作ることはしない。tool_use ブロックはこの注文票にあたり、実際に「作る」(関数を実行する)のはウェイターの背後にいる厨房、つまりあなたのアプリケーションコードだ。ウェイターが注文票を正確に書けることと、実際に料理を作れることはまったく別の能力であり、LLM が「言葉で注文を書く」以上のことをしていない点がこの仕組み全体の前提になっている。
モデルは「呼ぶか呼ばないか」をどう決めているのか
ツール呼び出しを判断しているのは、モデルとは別に動く分類器のような仕組みではない。既定の tool_choice は auto で、この設定ではモデルは通常の文章生成と同じ次トークン予測の延長として、リクエストの内容がどのツールの説明に合致するか、答えがすでに会話の中にあるかを見ながら、文章ブロックを出すか tool_use ブロックを出すかを1トークンずつ決めていく。Anthropic のドキュメントも、この境界はシステムプロンプトの指示で誘導できる(steerable)ものであり、固定のルールベース判定ではないと説明している。
tool_choice には他に、特定のツールを強制する設定や、ツールを一切使わせない none もある。Claude Code が毎回律儀に「ツールを使うか考えてから」動いているように見えるのは、この判断が特別な例外処理ではなく、モデルが出力するトークン列そのものの一部として毎回自然に発生しているからだ。
引数の JSON は本当にスキーマ通りになるのか
以前の function calling は、モデルにフリーテキストとして JSON を出力させ、それをアプリ側で JSON.parse して初めて壊れていたことに気づく、という不安定な仕組みだった。現在は主要プロバイダが制約付きデコーディングという手法でこの壊れ方を構造的に防いでいる。input_schema の JSON Schema を文法(grammar)に変換し、次のトークンを選ぶたびにその文法から外れるトークンの確率をゼロにする(トークンマスキング)ことで、生成される JSON がスキーマの型・必須フィールドから外れないことを保証する。
OpenAI は 2024 年 8 月に Structured Outputs の Strict Mode でこの方式を採用し、Anthropic もツール定義に strict: true を付けることでスキーマ準拠を保証できるようにしている。ただしこれが保証するのは構造の妥当性だけで、値の意味的な正しさまでは保証しない。location が文字列型であることは保証されても、そこに実在しない地名が入っていないことまでは保証されない。
なぜ1回のやりとりで完結しないのか
ツールを1つ使うだけでも、モデルの呼び出しは最低2回になる。1回目のレスポンスでモデルは stop_reason: "tool_use"(OpenAI では finish_reason: "tool_calls")を返して生成を止め、アプリ側に実行を委ねる。アプリはその tool_use ブロックが指す関数を実際に実行し、結果を tool_result として会話履歴に追加したうえで、もう一度同じ会話をモデルに送り直す。モデルはその結果を読んでようやく最終的な回答を文章として生成する。
あなたのアプリ
│ ①依頼 + tool 定義
▼
LLM
│ ②tool_use を生成して停止
▼ (stop_reason: tool_use)
あなたのアプリ
│ ③関数を実際に実行
▼
tool_result を会話に追加
│ ④もう一度LLMを呼ぶ
▼
LLM
│ ⑤最終回答を生成
▼
ユーザーへ返答
Claude Code が「ファイルを読んで→編集して→テストを実行して」と何ステップも動き続けるのは、この2ラウンドの往復を tool_result を挟みながら繰り返しているだけであり、1回の巨大な推論で全工程を終わらせているわけではない。1つのレスポンスに複数の tool_use ブロックが含まれる並列呼び出しも可能で、その場合はアプリ側が複数の関数を実行してから、まとめて結果を返す。
Claude Code 自体がこの仕組みで動いている。ふだん目にしている Bash・Edit・Read の実行はすべて tool_use の具体例であり、社内で MCP サーバーを自作したり、RAG 検索をツールとして組み込んだりする際は、この「モデルは意思表示だけ、実行はアプリ側」という往復構造を理解しておくと、ツールが呼ばれない・引数がおかしい・応答が返ってこないといった不具合の切り分けが早くなる。
Anthropic の公式ドキュメントに載っている get_weather ツールの往復例(抜粋・簡略化)を読み、どこまでが LLM の出力で、どこからがアプリ側の仕事かを見分けてみる。
// ① あなたが送るリクエスト(抜粋)
{
"tools": [{
"name": "get_weather",
"input_schema": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"]
}
}],
"messages": [{"role": "user", "content": "SFの天気は?"}]
}
// ② モデルからの応答(抜粋) — ここまでがLLMの仕事
{
"stop_reason": "tool_use",
"content": [{
"type": "tool_use",
"id": "toolu_01A...",
"name": "get_weather",
"input": {"location": "San Francisco, CA"}
}]
}
// ③ ここから先はあなたのコードの仕事
// 実際に天気APIを呼び、結果を次のリクエストに足す
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_01A...",
"content": "15度、一部曇り"
}]
}
②の input はモデルが生成した文字列にすぎず、実際に天気サービスへ問い合わせて「15度、一部曇り」という値を作っているのは③のあなたのコードだと分かるはずだ。もしアプリ側が③を作る前に権限チェックをせず、モデルの出す tool_use を無条件に実行していたら、という点も合わせて考えてみると、tool_choice や入力検証を自分のコードに置く意味が見えてくる。
- LLM が直接 API を叩いたりファイルを操作したりしている — モデルが生成するのは「この関数をこの引数で呼びたい」という構造化データだけで、実際にコードを実行するのは呼び出し元のアプリケーション側だ。
- ツール呼び出しの引数は JSON.parse に失敗しうる不安定な自由記述テキストだ — 主要プロバイダは生成時に JSON Schema を文法として使い無効なトークンをマスクする制約付きデコーディングで構造的な妥当性を保証していることが多く、値の意味は別として構造が壊れる心配は小さくなっている。
- ツールを使う会話は1回の API 呼び出しで完結する — ツール呼び出しのたびに最低2回のモデル呼び出しからなる往復が発生し、Claude Code が何ステップも動き続けるのはこのループを繰り返しているだけだ。
- tool use(Function Calling)
- LLM が構造化された関数呼び出しの意思表示を生成し、実行は呼び出し元のアプリが行う仕組み。
- input_schema(JSON Schema)
- ツールが受け取る引数の型・必須フィールドを定義する JSON Schema。ツール定義とともに毎回のリクエストに含めて送る。
- tool_choice
- モデルがツールを呼ぶかどうか・どのツールを呼ぶかを制御するリクエストパラメータ(既定は auto)。
- tool_use ブロック / tool_result
- モデルが呼び出し意思を示す出力ブロックと、アプリがその実行結果を返す入力ブロックの対。
- 制約付きデコーディング(constrained decoding)
- JSON Schema を文法として使い、生成時に無効なトークンをマスクして出力の構造的妥当性を保証する手法。
- Tool use with Claude — tool_use ブロックと tool_result の往復、strict モードによるスキーマ保証を一次情報として確認できる。
- How tool use works — ツール呼び出しのループ構造と、クライアント側で実行するツール/サーバー側で実行するツールの違いを解説している。
- Function calling | OpenAI API — finish_reason: tool_calls や並列呼び出しなど、OpenAI 版の同じ仕組みの用語と挙動を確認できる。