「AI 開発してます」を見極める 10 の質問 — 素人のための解説|2026-08 版
第 8 章Q6

Q6 ツール呼び出しの信頼性

モデルは関数を「呼ぶ」のではなく「呼んでほしいと言う」だけ。実行と検証はこちらの仕事

Q6. ツール呼び出しの信頼性

関数呼び出し(ツール利用)を実装するとき、引数が正しくない場合や失敗した場合にどう対処していますか? 実際に組んだバリデーションやリトライの例を聞かせてください。

出典: @voidwarriorchan の投稿(2026-08-25)
この章で分かること

この質問は何を聞いているのか

関数呼び出し(function calling / tool calling / ツール呼び出し)とは、モデルに「使えるツールの一覧」(名前・説明・入力の形)を渡しておき、モデルが必要と判断したときに「このツールをこの引数で呼んでほしい」という構造化された要求を返す仕組みである。OpenAIは公式にこう説明する。「Function calling(tool callingとも呼ばれる)は、OpenAIのモデルが外部システムや学習データ外のデータへ接続するための、強力で柔軟な方法を提供する」。ここで重要なのは、モデルが返すのはあくまで「要求」であり、外部システムを実際に叩く(=実行する)のはアプリ側のコードだという点だ。

質問文の「引数が正しくない場合」とは、モデルが返した要求の中身――呼び出したい関数名や引数――が、こちらの期待する形と食い違っているケースを指す。「失敗した場合」とは、引数は正しくても外部APIがダウンしているなど、実行そのものが失敗するケースを指す。前者は入力の正しさの問題、後者は実行結果の問題で、対処の仕方が異なる。「バリデーション」はこの2つを事前・事後にチェックする仕組み、「リトライ」は失敗した呼び出しを何らかの条件でやり直す仕組みである。

つまりこの質問は、モデルが返してきた要求を無条件に信用してよいのか、失敗はどこまで自動で回復させ、どこから人間や上位のロジックに委ねるべきかという、ツール呼び出しを本番で運用した経験があるかを尋ねている。したがってこの質問では、候補者が「モデルは実行しない」という前提を理解した上で、検証・エラー処理・再試行という3層の設計を具体的に語れるかを測ることができる。

モデルは実行しない — アプリとの「契約」としてのツール利用

Anthropicはツール利用をアプリとモデルの「契約」と表現する。「どんな操作が使えて、その入出力がどんな形かはあなたが指定し、いつ・どうやって呼ぶかはClaudeが決める。モデルは自分では何も実行しない。構造化された要求を発行し、あなたのコード(またはAnthropicのサーバー)が操作を実行し、結果が会話に戻される」。続けて「Claudeはあなたの実装を見ることはない。見ているのは、あなたが渡したスキーマ(=入力の形の定義)と、あなたが返した結果だけだ」とも述べる。この2文が、この質問全体の前提になる。

実際のやり取りは、Anthropicではtool_useブロック(id・name・inputを持つ、モデルからの呼び出し要求)とtool_resultブロック(tool_use_id・content・is_errorを持つ、アプリからの結果)のやり取りとして表現される。OpenAIではfunction_callアイテム(call_id・name・JSON(=波括弧と引用符でデータを書く、機械が読みやすいテキスト形式)文字列のarguments)とfunction_call_outputに相当する。初心者がつまずきやすい差が1つある。OpenAIのargumentsはJSON文字列として返ってくるため、そのままオブジェクトとして使うことはできず、必ずパース(=文字列を読み取ってプログラムが扱えるデータに変換すること)する必要がある。

tool_useの間はここへ戻る モデルへ渡す 実行に回す 結果を渡す ①送信 tools付き ②要求を返す (tool_use) ③実行 アプリ側 ④結果を返送 (tool_result) モデルはここで何も実行しない stop_reasonによる分岐 tool_use ③へ進み続行 end_turn/stop_sequence 最終回答で終了 max_tokens 上限を上げ再試行 refusal 終了 pause_turn(既定10反復の上限)はそのまま返送して継続
図 8-1 モデルは②の時点で何も実行しない。要求を返すだけで、実行するのは常にアプリ側(③)である。ループを続けるか抜けるかはstop_reasonで機械的に判定する。

図8-1のループはstop_reason(応答が止まった理由を示す値)が"tool_use"である限り回り続け、"end_turn"・"max_tokens"・"stop_sequence"・"refusal"のいずれかで抜ける。max_tokensでtool_useが途中で切れた場合は、max_tokensの値を上げて再試行する必要がある。サーバー側のツール(web検索など)を使うループにはpause_turnという既定10反復の上限があり、到達した応答はそのまま送り返せばよい。

信頼性を作る4つの要素 — 引数の破損・保証範囲・エラー処理・再試行

引数が壊れる典型パターンとstrictモードの保証範囲

公式ドキュメントが挙げる引数破損のパターンは具体的だ。型違い(数値のはずが文字列で返る)、必須パラメータの欠落、スキーマに存在しないパラメータの捏造、enum(列挙値、=あらかじめ決めた候補の中からしか選べない値の集合)の範囲外の値が代表例だ。Anthropicは予約システムの例で「乗客数passengers: intを必要とするとき、strictモードなしではClaudeはpassengers: "two"やpassengers: "2"を返すことがある。strict: trueでは、応答は常にpassengers: 2を含む」と説明する。Anthropicは小型のClaude Haiku系モデルについて「単純なツールには向くが、欠けたパラメータを推測で埋めることがある」とも注意する。OpenAIも「入力がスキーマと全く無関係でも、モデルはスキーマに合わせようとして値を捏造しうる」と注意する。別の落とし穴として、Anthropicは、Claudeのモデル版(Opus 4.6以降)によってJSON中のUnicodeやスラッシュのエスケープ(=特殊文字を安全な表記に置き換える処理)が変わることを挙げ、ツール入力は必ずパースし、文字列のまま比較してはいけないと述べる。

厳密モード(strict mode / strict tool use)は、モデルの出力トークン(=モデルが文章を扱う最小単位)自体を文法で制約し、引数がスキーマに必ず一致するようにする機能だ。Anthropicは「strict: trueを設定すると、モデルのトークンサンプリングをスキーマ準拠の出力だけに制約する(文法制約サンプリングと呼ばれる技術)ことで、Claudeのツール入力がJSON Schema(=JSONの形を「この項目は必須で整数」のように定めた設計図)に一致することを保証する」と説明する。ただし対応するJSON Schemaの機能は部分集合で、Anthropicは再帰スキーマや数値制約が非対応で、1リクエストあたりstrictツール20個・任意パラメータ合計24個という複雑さの上限がある。非対応機能を使うとHTTPステータスコード(=通信の結果を表す3桁の番号。400番台は依頼側の誤り、500番台はサーバー側の障害)400のエラーになり、表にない内部上限(コンパイル後の文法サイズ)を超えた場合も400で「Schema is too complex for compilation.」というメッセージが返る(最終防衛線としてコンパイル180秒のタイムアウトもある。2026-08時点)。OpenAIのstrictはadditionalProperties: falseと全フィールドrequired化(任意項目は"type": ["string", "null"]で表現)が要件で、OpenAIのResponses API(=OpenAIの現行の応答生成API。旧来のChat Completions APIは既定で非strict)はstrictを省略すると自動でstrict化を試み、できない場合は保証なしの「できる範囲で従う」動作(best-effort)へ静かに切り替わる(フォールバック)。切り替わったことは応答内のツール定義がstrict: falseになっていることでしか分からない。

strictなし strict: true そのまま生成 検証へ 文法で制約 保証 例外 モデルの出力トークンは自由 型/必須/未知キーが崩れうる アプリ側でスキーマ検証 文法でトークン生成を制約 スキーマ準拠の出力のみ生成可 型/必須/enum値は保証される 保証が破れる出口 refusal / max_tokens / enum大文字小文字
図 8-2 strictは文法でトークン生成そのものを制約し型・必須・enum値のスキーマ準拠を保証するが、拒否(refusal)や出力の途中終了(max_tokens)ではこの保証が破れる。

strictでも「意味の正しさ」までは保証されない。OpenAIはStructured Outputs(構造化出力、=最終応答の本文をJSON Schemaに従わせる機能。strictなツール引数と同じ制約の仕組みを使う)について「間違いは含まれうる」と明記する。Anthropicの保証も、stop_reason: "refusal"(拒否メッセージがスキーマ制約より優先される。HTTP 200で返り課金もされる)や"max_tokens"(出力が途中で切れる)では破れ、enum値の大文字小文字もスキーマと完全一致する保証がない。「型は保証できるが値の妥当性はアプリ側の検証が要る」と言えるかどうかが、この節の理解を測る分かれ目になる。

検証・実行エラーの返し方 — 誰が直せるエラーか

MCP(Model Context Protocol、=異なるAIアプリとツール群を共通の手順でつなぐための公開仕様)は、エラーを明確に2種類に分ける。「①プロトコルエラー(Protocol Errors)は要求の構造自体の問題で、モデルが自力で直せる見込みが低い――未知のツール、壊れた要求、サーバーエラー。標準的なJSON-RPC(=JSONで命令と応答をやり取りする共通手順)エラーとして返す」「②ツール実行エラー(Tool Execution Errors)は、モデルが自己修正して調整済みのパラメータで再試行するために使える、実行可能なフィードバックを含む――APIの失敗、入力検証エラー、ビジネスロジック(=業務ルールに基づく判定処理)のエラー。isError: trueを付けたツール結果として報告する」。仕様は「ツール実行エラーはモデルに見せて自己修正を可能にするべき(SHOULD)」と述べる。この線引きは、後で見る「is_error」の使い方と対応している。

構造の問題 実行時の問題 JSON-RPCで返す isError:trueで返す ツール呼び出しが失敗 壊れた要求・未知のツール名 入力検証の失敗・API失敗 Protocol Error(JSON-RPC) isError: true の結果 モデルに見せても回復しにくい(MAY) モデルへ返し自己修正させる
図 8-3 MCPはエラーを2系統に分ける。構造自体が壊れたプロトコルエラーはモデルに見せても回復しにくく(MAY)、入力検証やAPI失敗はモデルへ返して自己修正の材料にする(SHOULD)。

Anthropicも同じ考え方をとる。ツール実行時のエラーはis_error: trueとともにcontentに理由を書いて返す。公式は「指示的なエラーメッセージを書くこと。"failed"のような汎用的なエラーではなく、何が問題で次にClaudeが何を試すべきかを書く(例: "Rate limit exceeded. Retry after 60 seconds.")。これによりClaudeは推測なしで回復・適応するための文脈を得られる」と述べる。「ツール要求が不正またはパラメータ欠落の場合、Claudeは謝る前に2〜3回、修正を加えて再試行する」とも公式は明記している。

{
  "type": "tool_result",
  "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
  "is_error": true,
  "content": "ConnectionError: the weather service API is not available (HTTP 500)"
}

アプリ側の検証も欠かせない。AnthropicのTool Runner(SDKのループ自動化機構)でも、TypeScript(=プログラミング言語の1つ)向けSDKでZod(=TypeScript用のデータ検証ライブラリ)を使うbetaZodTool()なら入力が検証されるが、JSON Schemaだけで定義するbetaTool()では「Claudeが生成した入力は実行時に検証されない。必要ならツール本体の処理(run関数)の中で検証を行うこと」と明記されている。Pydantic(Python向けのデータ検証ライブラリ)はフィールド単位・モデル単位のバリデータ(=入力値の妥当性を検査する関数)を提供し、検証失敗時にどのフィールドがどんな理由で失敗したかを機械的に含むメッセージを投げる。これをそのままis_error: trueのcontentとしてモデルへ返せば、指示的なエラーメッセージになる。OpenAI Agents SDKにも同様の仕組みがあり、既定のfailure_error_functionはエラー発生をモデルに伝える。存在しないツール名を呼んだときも既定では例外(=プログラムが処理を中断して投げる失敗の通知)で止まるが、tool_not_found_behavior="return_error_to_model"を設定すると、SDKがエラーをモデルへ返してから再度実行し、モデルが別のツールを選ぶか使わずに答えるかを選べるようにする。

ツール結果を扱う際の原則もある。「ツール結果はしばしば制御が及ばない発信元――ウェブページ、受信メール、サードパーティAPI――からのコンテンツを運ぶ。信頼できないものとして扱うこと」と公式は述べ、逆に自分の指示をtool_resultに混ぜるとClaudeが第三者の指示として疑うため、指示はtool_resultの後のuser(=会話でこちらが送る側の発言。assistantはモデル側の発言)ターンに分けるべきだとも注意している(プロンプトインジェクションへの防御はQ10で詳しく扱う)。

リトライ・タイムアウト・べき等性 — どこまで自動で回復させるか

再試行には少なくとも3つの層がある。①アプリのビジネスリトライ(自分のコードが判断して呼び出しをやり直す)、②モデル自身の訂正(引数不正なら2〜3回、モデルが自分で修正して再試行する)、③SDKのHTTPリトライだ。AnthropicとOpenAI双方のPython SDKは、既定で接続エラー・408・409・429・500以上のエラーを、短い指数バックオフ(exponential backoff、=再試行のたびに待ち時間を倍々に増やす方式)で2回まで自動リトライする(両社のPython SDKドキュメントが同じ既定値を示す)。Anthropicの公式SDKはさらに、応答にretry-afterヘッダーがあればそれに従う。400番台の多くはリクエスト内容そのものの誤りなので、内容を直さずに同じリクエストを再送しても同じ結果になり、自動リトライの対象外である。タイムアウトは既定10分で、タイムアウト自体も再試行の対象に含まれる。公式は「文字列一致でエラーを判定するのではなく、SDKの型付きクラスを、より具体的なものから先に捕捉すること」と推奨しており、支出上限到達によるAnthropicの429にはretry-afterヘッダーが付かず再試行しても復帰しないケースもある。

①ビジネスリトライ(アプリ側) ②モデルの自己修正(2〜3回) ③SDKリトライ(2回・バックオフ) 対象: 408/409/429/5xx 接続エラー(400は対象外) タイムアウトも指数バックオフの対象、400は対象外
図 8-4 外側ほど広い判断(べき等性を前提にしたビジネスリトライ)、内側ほど機械的に狭く対象を絞ったリトライ(SDKのHTTPリトライ)になる。SDKの自動リトライは400のようなリクエスト内容の誤りを対象にしない。

再試行を安全に行うにはべき等性(idempotency、=同じ引数で何度呼んでも環境への追加の影響が出ない性質)が前提になる。MCPのToolAnnotations(ツールの性質を宣言するヒント)にはreadOnlyHint・destructiveHint・idempotentHint・openWorldHintがあるが、既定値は安全側ではない――readOnlyHintは既定false(=書き込みありうる)、destructiveHintは既定true(=破壊的な可能性がある)、idempotentHintは既定false(=べき等でない)。しかも仕様は「これらはすべてヒントであり、挙動を忠実に説明する保証はない。信頼できないサーバーからのToolAnnotationsに基づいて意思決定を行うべきではない」と釘を刺す。MCP仕様は、ヒントの信頼性とは独立に「常にツール呼び出しを拒否できる人間が介在するべき(SHOULD)」とし、機微な操作では実行前に確認を求めツール入力を人に見せることをクライアントに求める(human-in-the-loop、人間の確認を挟む仕組み)。OpenAI Agents SDKの承認フローも安全側に倒す設計で、引数が壊れたJSONなど安全に判定できない場合、承認関数は呼ばれず自動的に手動承認が必要になる――曖昧なら実行を止める、というfail-closedの実例である。

並列呼び出しの結果は1つのメッセージにまとめる

モデルは既定で1回の応答に複数のツール呼び出しを含められる。Anthropicは「APIは実行順序を規定しない」としつつ、「各tool_useブロックに1つずつtool_resultを対応させ、すべて次のuserメッセージにまとめ、テキストより先に置くこと」と定める。この整形を誤り結果を複数のuserメッセージに分けて返すと、並列呼び出しが機能しなくなる――公式は「Claudeに並列呼び出しを避けるよう『教えて』しまう」と明言している。逐次実行中に先の呼び出しが失敗し後続を実行しなかった場合も、未実行分にis_error: trueと理由を付けたtool_resultを返す。

結果①を返す 結果②を返す 学習してしまう まとめて返す 次の応答 ✕ 誤:resultを別々のuserメッセージで返す ○ 正:1つのuserメッセージにtool_resultをまとめる assistant tool_use×2 userメッセージ① userメッセージ② 以後、並列呼び出しを 避けるようになる assistant tool_use×2 1つのuserメッセージに tool_result①②をまとめる 会話を継続
図 8-5 tool_useが複数あっても、結果は必ず1つのuserメッセージにまとめて返す(正しい例は下段)。

並列を止めたいときの落とし穴もある。Anthropicのdisable_parallel_tool_useはtool_choiceオブジェクトの中に置くパラメータで、tool_useを返す側のリクエストに付けないと効かない。OpenAIはparallel_tool_callsをfalseにすると呼び出しが0か1回に制限される。Anthropicは「独立した読み取り専用の操作は並列で低レイテンシ(=応答までの待ち時間が短いこと)に実行してよいが、副作用・共有状態・順序要件のあるツールは逐次実行の方が適切」と使い分けを推奨する。

実務では何をするのか

Anthropic Claude APIの場合

ツール定義はname(正規表現(=文字列のパターンを表す記法)^[a-zA-Z0-9_-]{1,64}$に一致)・description・input_schema(JSON Schema)の3点が基本で、公式は「説明文は非常に詳細にすること。これがツールの性能を左右する最大の要因だ」と強調する。tool_choiceにはauto(モデルが判断、既定)・any(いずれか1つを強制)・tool(特定のツールを強制)・none(使わせない)の4種類がある。strict: trueを使う場合はスキーマにadditionalProperties: falseとrequiredを明示する必要があり、computer_toolset_20260801・browser_toolset_20260801(この2つにstrict: trueを付けたリクエストは拒否される)とmcp_toolsetは対象外だ(2026-08時点)。ツール定義自体もトークンとして課金され、tool_choiceを変えるとメッセージ部分のプロンプトキャッシュ(前回までの計算結果の再利用)が無効になり再処理される(ツール定義とシステムプロンプトのキャッシュは残る)。

以下はstrict: trueを使うツール定義の構成例である(公式ドキュメントの逐語的な例ではなく、上記の要件を満たす最小構成を示すもの)。

{
  "name": "example_tool",
  "input_schema": {
    "type": "object",
    "properties": { "passengers": { "type": "integer" } },
    "required": ["passengers"],
    "additionalProperties": false
  },
  "strict": true
}

OpenAI APIの場合

OpenAIのベストプラクティスは「①関数名・パラメータの説明を明確かつ詳細に書く。②enumを使い無効な状態を表現できない構造にする。③モデルにすでに分かっている引数を埋めさせない。④初期状態で使えるツール数を少なく保つ(目安1ターンあたり20個未満)」とまとめられている。Agents SDKでは、プログラマが書いた関数の「引数の名前と型」「説明文」を読み取って、ツール定義(JSON Schema)を自動で作る。そのとき「1以上の整数」「最大100文字」のような制約を一緒に書いておくと、定義に載るだけでなく、モデルが返した引数を受け取った時点で同じ制約で検査される(PydanticのField機能)。Agents SDKのガードレール(=入力・出力・ツール呼び出しを実行前後に検査し、違反なら止める安全装置)には入力・出力用に加えてツールガードレールがあり、関数ツール(function_tool)の各呼び出しの直前・直後に自分の検査コードを差し込める(フック)。入力ツールガードレールは実行前に呼び出しをスキップしたり、出力をメッセージに差し替えたり、tripwire(違反検知の合図)を上げたりできる。別に、エージェント単位の入力ガードレールをrun_in_parallel=False(ブロッキング実行)にすると、tripwire発火時にエージェント自体が起動せず「トークン消費とツール実行を防げる。コスト最適化や副作用回避に理想的」とSDKドキュメントは述べる。

MCPでプロトコルとして統一する場合

複数のツール提供元をまたいで同じ手順で呼び出したい場合、MCP(Model Context Protocol)を使う設計もある。ツール定義にはname・title(任意)・description・inputSchema(null不可、パラメータなしのツールは{"type": "object", "additionalProperties": false}が推奨形)・outputSchema(任意)を置く。予約かごのような状態を持つツールでは、期限切れや未知のハンドル(=サーバー側の状態を指す不透明な識別子)を渡された場合に「モデルが新しいハンドルを作って回復できるよう、その旨を伝えるツール実行エラーを返すべき」だとされる。

概念AnthropicOpenAIMCP
入力スキーマinput_schemaparametersinputSchema
呼び出し要求tool_useブロック(id/name/input)function_callアイテム(call_id/name/arguments)tools/call(name/arguments)
引数の形input(オブジェクト)arguments(JSON文字列)arguments(オブジェクト)
結果tool_resultブロックfunction_call_outputcontent/structuredContent
エラーフラグis_error(文字列で内容として表現)isError

回答を見極めるポイント

良い回答に含まれる要素
弱い回答のサイン

深掘り質問の例

つまずきポイント

Anthropicのtool_resultブロックの位置ルールを守らず400エラーになる。tool_resultは対応するtool_useの直後のuserメッセージの先頭に置く必要があり、間に別のメッセージを挟んだり、テキストを結果より先に置いたりすると、tool_use ids were found without tool_result blocks immediately afterというエラーになる。

並列呼び出しの結果を、個別のuserメッセージに分けて返してしまう。これは「以後、並列呼び出しを避けるように」モデルに教えてしまう挙動として公式に明記されており、レイテンシが悪化しても原因に気づきにくい。

OpenAIのargumentsがJSON文字列であることを忘れ、そのままオブジェクトとして扱おうとする。パースし忘れると、引数を取り出そうとした時点でプログラムが失敗する。

拒否(refusal)やストリーミング中のエラーがHTTP 200で返ってくることを知らず、ステータスコードだけでエラー判定してしまう。ストリーミング(SSE、=サーバーが応答を少しずつ送り続ける通信方式)では200を返した後にエラーイベントが来ることがあり、標準的なエラー処理の枠組みに乗らない。

理解度チェック

Q1. モデルはツールをどのように「実行」しますか?

実行しません。モデルが返すのは「このツールをこの引数で呼んでほしい」という構造化された要求(tool_use / function_call)だけで、実際に外部システムを叩いて実行するのは常にアプリ側のコードです。

Q2. strict: trueが保証する範囲と、保証しない範囲を挙げてください。

保証されるのは、出力の型・必須項目・enum値がスキーマに準拠すること(文法制約サンプリングによる形式面の保証)です。保証されないのは値の意味的な正しさで、モデルは形が正しくても中身が間違った値を返すことがあります。また拒否(refusal)や出力の途中終了(max_tokens)ではこの保証自体が破れます。

Q3. MCPのProtocol ErrorとTool Execution Errorはどう違い、なぜモデルに見せる先が違いますか?

Protocol Errorは未知のツール名や壊れた要求など、要求の構造自体の問題でモデルが自力で直せる見込みが低いエラーです。Tool Execution Errorは入力検証の失敗やAPI失敗など、モデルが引数を調整して自己修正できる実行可能なフィードバックを含みます。主に後者をisError: trueの結果としてモデルに返すのは、モデルが直せる種類のエラーに自己修正の機会を与えるためで、プロトコルエラーを見せることも禁じられてはいないが回復は見込みにくい(MAY)。

Q4. SDKの既定リトライが対象とするエラーと、対象外のエラーをそれぞれ挙げてください。

対象は接続エラー・408(Request Timeout)・409(Conflict)・429(Rate Limit)・500以上のサーバーエラーで、指数バックオフで既定2回まで自動リトライされます。400のような、リクエスト自体の内容が誤っているエラーは対象外で、内容を直さない限り同じリクエストを何度送っても同じ結果になるため再試行しても無駄です。

Q5. 並列ツール呼び出しの結果を返すとき、正しいメッセージの組み立て方はどれですか?

すべてのtool_resultブロックを1つのuserメッセージにまとめ、各tool_use_idと対応付けて、テキストより先にtool_resultを置きます。分けて返すと図8-5の通り、以後の並列呼び出しに影響します。

まとめ

次章のQ7「LLMの監視・運用」では、ここまで見てきた個々の呼び出しの信頼性を、本番運用の中でどう継続的に観測し、異常に早く気づくかを見る。

参考リンク