第2章 構造化出力とツール呼び出し

学習目標

  • 指示文、JSONモード、スキーマに基づく構造化出力がそれぞれ保証する範囲を説明し、適切な方式を選ぶ。
  • 業務の概念からスキーマを導き、ベンダーごとのstrictのルールに合わせて修正する。
  • 構文・スキーマ・意味の3段階で結果を検証するコードを設計する。
  • ツールを定義し、ツール呼び出しのループを予算付きの状態機械として実装し、結果を正確な形式で会話に戻す。
  • 並列呼び出し、ツール選択のオプション、実行権限を組み合わせ、安全なツールループを設計する。

はじめに

ある税務アプリが、領収書の写真から支出項目を抽出して帳簿に入れる機能を作りました。「JSONで答える」という指示だけでもデモでは問題ありませんでした。しかし、本番の最初の週に3回の障害が起きました。1回目は応答の前に「以下が結果です」という文が付いてパースに失敗し、2回目は金額が"12,000ウォン"という文字列で返り、合計計算が止まりました。最後は形式が完璧なのに、合計が各品目の金額の和と一致しませんでした。1つ目は形式を強制しなかったこと、2つ目はスキーマを業務に合わせて設計しなかったこと、3つ目は形式が正しければ内容も正しいと思い込んだことが原因です。

本章は、モデルの答えをプログラムが信頼して使えるようにする2つの機能を扱います。構造化出力は応答の形を固定し、ツール呼び出しはモデルがアプリケーションの機能を要求できるようにします。前半はスキーマの設計と検証、後半はツール定義、呼び出しループ、結果の返却、並列呼び出し、実行権限を順に見ていきます。

重要な概念

構造化出力は型枠です

生地を手で成形すると形は毎回少しずつ違いますが、型枠に流し込めば形が定まります。「JSONで答える」という指示が手での成形なら、構造化出力は型枠です。ベンダーはJSONスキーマを文法(grammar)にコンパイルし、その文法に合うよう生成過程を制限します。こうした方式は、一般に制約付きデコーディング(constrained decoding)と呼ばれます。

方式 保証すること 保証しないこと
指示文 なし(多くの場合は従う) 形式、フィールド、型
JSONモード 有効なJSON スキーマへの準拠
構造化出力(strict) 指定したスキーマへの準拠 拒否・途切れ時の形式、値の正確性、業務ルール

OpenAIのドキュメントは、JSONモード(json_object)は有効なJSONだけを保証し、スキーマへの準拠は保証しないと明記しています。また、JSONモードではメッセージのどこかに「JSON」という言葉が必要で、JSONを生成するよう指示しなければ、モデルが空白だけを出力し続けて上限に達することがあると警告しています。

ベンダーごとの有効化と選択の基準

ベンダー 応答自体の構造化 ツール引数の構造化
OpenAI Responses API text.formatにtype: "json_schema"、name、schema、strict: true 関数定義にstrict: true
Anthropic Messages API output_config.formatにtype: "json_schema"、schema ツール定義にstrict: true
Gemini Interactions API response_formatにtype: "text"、mime_type: "application/json"、schema(generateContent APIではresponseMimeTypeとresponseSchema) 関数宣言のparametersスキーマ

何を使うかは、結果の用途で決めます。OpenAIは、モデルをシステムのツール・機能・データにつなぐなら関数呼び出しを、ユーザーに返す応答の形を決めるならtext.formatを使うよう案内しています。Geminiも、構造化出力は最終応答の形式を定めるため、関数呼び出しは会話中にアクションを実行するためのものと区別しています。

初めてスキーマを使うリクエストは、文法の構築によってレイテンシが長くなります(Anthropicはコンパイル済みの文法を最終使用から24時間キャッシュします)。OpenAIとAnthropicのSDKは、PythonのPydanticモデルやTypeScriptのZodスキーマからスキーマを作り、レスポンスをパースするため、手書きのスキーマとコードの型が食い違う問題を減らせます。

strictスキーマのルールとベンダーの違い

strictモードが受け付けるのは、JSON Schema全体ではなく部分集合です。ベンダーによってその部分集合が違う点が落とし穴です。

ルール OpenAI Anthropic
additionalProperties すべてのオブジェクトにfalseが必須 すべてのオブジェクトにfalseが必須
任意フィールド すべてをrequiredに入れ、nullを許容する型で表す requiredにないフィールドも許可(任意フィールドの数に上限)
数値・文字列の制約 minimum・maximum・multipleOf、pattern、formatに対応(ファインチューニング済みモデルは非対応) minimum・maximumなどの数値制約、minLength・maxLengthは非対応
再帰スキーマ $refで対応 非対応
最上位(ルート) オブジェクトである必要があり、ルートのanyOfは不可(下位のプロパティでは可) —
出力キーの順序 スキーマの順序 必須フィールドが先、次に任意フィールド
複雑さの上限 プロパティ5,000個、ネスト10階層、列挙値1,000個など strictツール20個、任意パラメータ24個、ユニオン型パラメータ16個

OpenAIのstrictモードで任意の値を表すには、フィールドを省略せず、nullを許容する型を使います。

{
  "type": "object",
  "properties": {
    "order_id": { "type": "string" },
    "memo": { "type": ["string", "null"] }
  },
  "required": ["order_id", "memo"],
  "additionalProperties": false
}

Anthropicで非対応の機能を使うと400エラーになります。AnthropicのSDKヘルパーは、非対応の制約をスキーマから除いてフィールドの説明に移し、Pydanticベースのmessages.parse()やTypeScriptのzodOutputFormat()は、元の制約で応答を再検証します。Zodを使わないjsonSchemaOutputFormat()や、SDKを使わない直接呼び出しでは、同じ検証をアプリケーションで行う必要があります。Geminiも部分集合のみを受け付け、非常に大きいスキーマや深くネストしたスキーマは拒否されることがあります。

業務の概念からスキーマを導きます

スキーマは単なる応答の形ではなく、アプリケーションとモデルの間の契約です。設計では4点を決めます。

  1. 値がない場合の意味。 「フィールドがない」、null、空文字列、空配列は、それぞれ異なる意味にします。領収書に合計が印刷されていない場合と、印刷されていても不鮮明で読めない場合を同じnullにまとめると、後続処理が行き先を失います。理由を表す列挙型フィールドも設けます。
  2. 単位と表現。 金額はウォン単位の整数、日付はISO 8601(2026-10-20)、比率は0~1の実数のように定めます。原文の表現が必要なら、別フィールド(raw_date)で受け取ります。
  3. モデルが作る値とサーバーが付与する値。 注文番号、作成日時、ユーザーIDなど、システムが知る値はモデルに作らせません。
  4. バージョン。 運用中のフィールド変更は、画面、ストレージ、バッチ処理のすべてに影響します。削除や意味の変更に加え、フィールドの追加も、additionalProperties: falseで検証する利用側との互換性を損なう変更になります。schema_versionを設け、利用側がバージョン別に処理を分けるようにし、あわせて更新します。
{
  "type": "object",
  "properties": {
    "schema_version": { "type": "string", "enum": ["2"] },
    "total_krw": { "type": ["integer", "null"] },
    "total_status": { "type": "string", "enum": ["read", "not_printed", "illegible"] },
    "purchased_on": { "type": ["string", "null"], "format": "date" },
    "category": { "type": "string", "enum": ["meal", "supplies", "transport", "other"] }
  },
  "required": ["schema_version", "total_krw", "total_status", "purchased_on", "category"],
  "additionalProperties": false
}

3段階で検証します

構造化出力を使っても、検証は終わりません。3段階に分けると、失敗の原因と対応が明確になります。

段階 問い 失敗したら
0. 応答の状態 拒否や途切れではないか パースしない。第1章の停止理由の処理へ
1. 構文 JSONとしてパースできるか 構造化出力ではまれ。途切れ・拒否を疑う
2. スキーマ フィールド、型、列挙値が正しいか コードのバグかスキーマの不一致。記録して1回リトライ
3. 意味 業務ルールに合うか 人の確認キュー、ユーザーへの確認質問、または保留

段階0は見落としがちです。Anthropicのドキュメントは、拒否(refusal)や出力上限への到達(max_tokens)の応答はスキーマに合わないことがあると明記し、OpenAIもstatus: "incomplete"を先に確認するよう案内しています。段階3の例は、「品目金額の和=合計」「返金額≤決済額」「終了日≥開始日」といった関係の検査です。Geminiのドキュメントの表現どおり、出力が構文上正しいJSONでも、値は常にアプリケーションで検証する必要があります。

細かい落とし穴もあります。Anthropicは、構造化出力は列挙値の大文字・小文字までは保証しないと説明し、大文字・小文字を区別せずに比較し、その違いだけで別の列挙値を作らないよう勧めています。また、コンパイル済みのスキーマはメッセージ内容とは別に最終使用から最大24時間キャッシュされるため、フィールド名・説明・列挙値に顧客名などの機密情報を含めないよう案内しています。

ツール定義は実行の契約です

ツール呼び出し(関数呼び出し)は、モデルに「こういう機能がある」と伝え、必要なときに名前と引数を提案させる機能です。モデルは私たちが定義したツールを直接実行しません。実行するのはアプリケーションです(Web検索など、ベンダーが代わりに実行するサーバーツールは例外です)。ツール定義は名前、説明、入力スキーマからなり、モデルにとってはその説明が使用説明書のすべてです。

Anthropicは、詳細な説明をツールの性能に最も影響する要素とし、ツールごとに最低3~4文で、何をするか、いつ使い・使わないか、各パラメータの意味、返さない情報などの限界を記すことを勧めています。

修正前 修正後
"description": "予約照会" "description": "顧客の既存の修理予約を予約番号で照会する。新たに予約可能な時間を探す際は使わない(find_slotsを使用)。予約番号は'R-'で始まる10桁である。返すのは状態、訪問日時、担当技術者名であり、決済情報は返さない。"

ほかの設計原則は次のとおりです。

  • 形式に敏感な引数には例を付けます。Anthropicは、スキーマ検証を通る例を入れるinput_examplesフィールドを提供します。
  • 関連する操作はactionパラメータを持つ1つのツールにまとめ、複数サービスにまたがる場合は、名前に接頭辞(billing_、calendar_)を付けます。
  • 結果には次の判断に必要な情報と安定した識別子だけを含め、すでにわかっている値(ユーザーIDなど)をモデルに引数として指定させません(OpenAI)。
  • ツールの数を少なく保ちます。OpenAIはターン開始時の関数を20個未満に、Geminiは有効なツールを10~20個以下にすることを勧めています。

ツール呼び出しループを状態機械にします

ツール呼び出しは、1回のリクエストで終わらずループします。

  1. ツール定義とともにリクエストする。
  2. レスポンスからツール呼び出し(名前、引数、呼び出しID)を取り出す。
  3. 引数と権限を検証し、アプリケーションで実行する。
  4. 結果を呼び出しIDと対応付け、会話に追加して再リクエストする。
  5. 最終回答なら終了し、再びツール呼び出しが来たら段階3に戻る。

単純なwhile文から始めると、無限の呼び出し、不適切な終了、コストの暴走が起こります。状態機械として考えると、設計から漏れていた状態遷移の経路が見えてきます。状態は「生成中→呼び出し要求→検証→実行→結果返却→生成中」を回り、「完了」か「予算超過」で終わります。予算は、ステップ数、経過時間、トークンコストの3つを設けます。

const budget = { maxSteps: 6, deadlineMs: Date.now() + 30_000, maxTokens: 60_000 };
let usedTokens = 0;
for (let step = 0; ; step++) {
  if (step >= budget.maxSteps || Date.now() > budget.deadlineMs || usedTokens > budget.maxTokens) {
    return handOff("budget_exceeded"); // 人の担当者に接続し、部分結果を保持
  }
  const res = await callModel(messages, tools);
  usedTokens += totalTokens(res.usage); // キャッシュのトークンも合算
  const outcome = classify(res.stop_reason); // 第1章の分類器
  if (outcome !== "needs_tool") return finish(res, outcome);
  const results = await runTools(res); // 検証→実行→結果の共通エンベロープ
  messages.push(assistantTurn(res), toolResultTurn(results));
}

ループを自分で書く必要はありません。Anthropic SDKのTool Runnerのように、ツール実行と結果返却のサイクルを自動処理するヘルパーもあります。ただし、予算、権限検査、監査記録は、どの場合もアプリケーションの責任です。

結果を会話に戻します

モデルの呼び出しと結果は、呼び出しIDで対応付けます。形式はベンダーごとに異なります。

ベンダー モデルの呼び出し 結果の返却
OpenAI function_call項目:call_id、name、JSON文字列のarguments function_call_output項目:同じcall_id、output(通常は文字列)
Anthropic tool_useブロック:id、name、オブジェクトのinput 次のuserメッセージのtool_resultブロック:tool_use_id、content、必要ならis_error
Gemini 関数呼び出しステップ:名前、引数、呼び出しID function_result:name、call_id、result

Anthropicは配置のルールが厳密です。tool_resultは該当するtool_useの直後のuserメッセージに置き、間に別のメッセージを挟めず、コンテンツ配列ではテキストより前に置く必要があります。結果の直後にテキストを加えると、空の応答の原因にもなります。OpenAIのargumentsはJSON文字列なのでパースが必要で、戻り値のない関数は成功を示す文字列を返します。

失敗も結果です。Anthropicでは、is_error: trueとともに、何が問題で次に何を試すべきかを記します。「failed」よりも、「在庫システムから応答がない。60秒後に再試行するか、別の店舗を照会する」のほうが適切です。引数が不適切な呼び出しにエラー結果を返すと、Claudeは通常2、3回修正して再試行します。ツールごとに結果の形が違うと解釈しにくいため、共通のエンベロープを定めます。

{ "status": "needs_info", "data": null, "error": null, "hint": "訪問先住所の棟・部屋番号が必要" }

statusはok、error、needs_info程度で十分です。結果のサイズに上限を設け、モデルが知る必要のない個人情報を結果から除いて送ります。

並列ツール呼び出し

モデルは1つの応答で複数のツールを同時に要求できます。Anthropicのドキュメントは、APIは実行順序を決めないため、独立した読み取りは同時に、副作用や状態の共有がある処理、順序が重要な処理は順番に実行するよう案内しています。実行方式によらず、守るべきルールがあります。

  • すべての呼び出しに1つずつ結果を返します。実行しないと決めた呼び出しにも、is_error: trueと「先の呼び出しが失敗したため実行しなかった」などの短い説明を含めた結果を返します。
  • Anthropicでは、すべての結果を1つのuserメッセージにまとめます。結果を別々のメッセージで返すと、モデルが並列呼び出しを避けるように「学習」し、並列利用が減ると警告しています。
  • 並列呼び出しは無効にできます。Anthropicでは、tool_choice内のdisable_parallel_tool_use: true(最上位パラメータではありません)により、autoのとき1応答につき最大1つのツールに制限します。OpenAIではparallel_tool_calls: falseで0個または1個に制限します。

Geminiは、独立した並列呼び出しと、前の結果が次の呼び出しの入力になる合成的(compositional)な呼び出しを区別します。依存関係があれば、アプリケーションで順序を強制します。

tool_choiceと呼び出しの強制

tool_choiceは、モデルがツールを使うか、何を使うかを決めます。

意図 OpenAI Anthropic Gemini
モデルが判断 auto(既定) auto(ツールがある場合の既定) auto(既定)
必ずツールを使用 requiredまたは特定の関数 anyまたはtool any
使用を禁止 none none none
許可リストで制限 allowed_tools — allowed_tools

呼び出しの強制は、モデルごとにサポートが違います。Anthropicによると、Claude Opus 5.5、Sonnet 5.5、Fable 5.1、Mythos 5.1はanyとtoolを400エラーで拒否するため、autoとstrictツールを組み合わせるか、決まったJSON応答が必要なら構造化出力を使います。対応するモデルでも、強制すると、思考過程とツール呼び出し前の説明文が省略されます。Anthropicではtool_choiceを変更するとキャッシュされたメッセージ部分が無効になります。OpenAIは、ツール一覧を維持して呼び出せる範囲だけを制限するallowed_toolsが、キャッシュによる節約の維持に役立つと説明しています。

推論モデルとツールループ

推論モデルは、ツール呼び出しとともに推論情報を返します。この情報は、次のリクエストに受け取ったまま再送する必要があります。

  • OpenAI:ツール呼び出しとともに来た推論項目(reasoning item)を、関数の結果とともに再送します。previous_response_idを使うか、前の出力項目をそのまま入力に入れます。ステートレスで使う場合は、推論項目のencrypted_contentを渡します。
  • Anthropic:ツール結果を返す際、最新のassistantメッセージのthinkingとredacted_thinking(安全上、一部を隠した思考ブロック)を、署名(signature)も含め変更せずに返します。編集、並べ替え、省略は400エラーになり、内容が空のブロックも例外ではありません。
  • Gemini:ステートレスで履歴を管理する場合は、モデルが生成したステップを変更せず再送します。必要な署名がステップ内にあるためです。SDKなら自動処理されます。

問題は、トークンを節約するためにブロックを除外したり、履歴を再シリアライズしたりするミドルウェアで起きがちです。原則は「履歴には追加するだけ」です。

実行権限はサーバーが持ちます

strictモードが保証するのは、引数の形だけです。その引数で実行してよいかは別の問いです。OWASP Top 10 for LLM Applications 2025は、必要以上の機能・権限・自律性によってモデル出力が有害な行動につながるリスクを過剰な委任(LLM06 Excessive Agency)、モデル出力を十分に検証・無害化せず他のシステムに渡すリスクを不適切な出力処理(LLM05 Improper Output Handling)としています。

  1. ユーザー・組織の識別子は、サーバーがセッションから入れる。顧客IDをモデルに引数として選ばせない。
  2. 実行直前に権限と業務ルール(状態、上限、所有権)を再検査する。Geminiも、関数呼び出しを実行前に検証するよう勧めている。
  3. 読み取りツールと書き込みツールを分け、書き込みツールは必要最小限の範囲だけ許可する。
  4. 元に戻しにくい操作は人の承認を経る。承認待ち状態を保存し、承認結果をツール結果として返して会話をつなぐ。
  5. ツール結果に混じる外部テキストは信頼しない(第1章の信頼境界)。

さらに深く

ツールが増えたとき

システムを1つずつつなぐと、ツールは数十、数百個に増えます。コストと正確性がともに悪化します。ツール定義は入力トークンとして課金され(OpenAI、Anthropicともに明記)、Anthropicはツール利用時に別途ツール使用のシステムプロンプトも加えます。似たツールが多いと、モデルの選択ミスも増えます。

戦略 方法 代償
統合 関連する操作をactionパラメータでまとめる 1つのツールの説明が長くなる
ステップごとの部分集合 会話の段階ごとに必要なツールだけを入れる 一覧が変わるとキャッシュが無効になる
許可リスト 一覧は維持し、呼び出せる範囲だけを制限(allowed_tools) 定義のトークン数は変わらない
ツール検索 定義を遅延読み込み(defer_loading)とし、モデルが検索して必要なものだけ読み込む 検索のステップが増え、対応モデルが限られる

Anthropicは数千のツールから必要なものを検索・読み込みするツール検索機能を、OpenAIは最新モデルで遅延読み込みとツール検索をサポートします。Anthropicでは、ツール定義を変えるとキャッシュ全体が無効になるため、段階ごとに一覧を組み直すと節約分が相殺されることがあります。どの戦略でも、ツール選択の正解率を評価セットで測ります。

ストリーミング中の構造化された結果

構造化出力とツール引数も、ストリーミングで受け取れます。画面には早く表示できますが、実行は完了まで待つ必要があります。

  • Anthropicは、ツール入力をinput_json_deltaの部分JSON文字列で送ります。断片を集め、content_block_stopでパースします。大きなパラメータ値をさらに細かく受け取りたい場合は、ツールごとにeager_input_streamingを有効にする細粒度ストリーミングがあります。
  • OpenAIは、response.function_call_arguments.deltaで断片を、.doneで完成した引数を送ります。
  • Geminiは、ストリーミングで届く各断片は、連結すると最終的なオブジェクトを構成する部分JSON文字列であると説明しています。

ルールは単純です。プレビューは部分結果で描画しても、ツールの実行と保存は、完成した引数が3段階の検証を通った後にだけ行います。途中でストリームが切れた場合、Anthropicの説明どおりツール使用ブロックは部分復旧できないため、受け取った断片をつなげて実行せず、新たにリクエストします(第4章)。

実務の場面

砂つぶ会計:領収書抽出のスキーマを作り直します

個人事業主向け帳簿アプリ「砂つぶ会計」は、領収書写真のOCRテキストから支出項目を抽出します。指示文でJSONを受け取っていたところ、パース失敗と型のエラーが1日に数十件起きたため、開発者のソンさんがスキーマを設計し直しました。

最初の案は、合計を任意フィールドにし、読めなければ省略するものでした。しかし、OpenAIのstrictモードではすべてのフィールドがrequiredである必要があり、何よりも「印刷されていない」と「不鮮明で読めない」を区別する必要がありました。前者は品目の合計で代用し、後者はユーザーに再撮影を求めるためです。そこで、total_krwはnullを許容する整数、状態はtotal_status列挙型で受け取りました。金額はウォン単位の整数、日付はISO形式に固定し、元の日付文字列はraw_dateで別に受け取りました。帳簿項目IDと登録日時はサーバーが付けます。

同じ機能をClaudeでも試すと、もう1つ問題が出ました。金額を0以上にするminimum: 0の制約が、Anthropicの構造化出力で非対応だったのです。チームは制約をフィールドの説明に移し、応答後にアプリケーションで再検査しました。意味の検証では、品目の和と総額の差が10ウォンを超えたらユーザーの確認画面へ送るルールを加えました。

JSONモードに例を増やす案はスキーマの不一致を防げず、読めなければ0を入れる案は実際に0ウォンの領収書と区別できないため却下しました。デプロイから1か月後、パース失敗はなくなり、全体の1.2%が合計不一致で確認画面へ進み、ユーザーが直接修正しました。

指先修理センター:修理受付エージェントに境界を設けます

家電修理業者「指先修理センター」は、チャットで修理を受け付けるエージェントを作っています。ツールは保証照会(lookup_warranty)、訪問可能時間の照会(find_slots)、受付作成(create_repair_ticket)の3つです。試験運用で3つの事故が起きました。ユーザーが他人の製品のシリアル番号を伝えると保証内容が照会され、モデルが時間照会と受付作成を同じ応答で同時に要求して空き確認前に受付が作成され、さらに曖昧な要求1件でループが9周してコストを費やしました。

リードエンジニアのトさんは、プロンプトの修正より先に構造を変えました。

  1. 顧客IDをツール引数から除外しました。サーバーがログインセッションから付与し、その顧客が登録した製品のシリアル番号かを実行前に確認します。
  2. create_repair_ticketの必須引数を、find_slotsが返したslot_hold_idとしました。仮押さえには有効期限があり、サーバーは仮押さえがないか期限切れなら実行せず、再照会を求めるneeds_info結果を返します。並列要求でも、依存関係をサーバーが強制するので順序が乱れません。
  3. 読み取りツール2つは並列実行を許可し、同じ応答に書き込みツールが混じれば、読み取りを先に実行してから書き込みを検証します。実行しない呼び出しには、理由を含むエラー結果を返します。
  4. ループの予算を6ステップ、30秒とし、超えたら会話の要約とともに人の担当者へ引き継ぎます。

「他人の製品を照会しない」という指示だけを入れる案は、モデルが従わない場合に止める仕組みがないため、サーバーの検査と併用することにしました。受付作成を強制呼び出しに固定する案は、予定のモデルで非対応であり、情報不足時に質問する余地もなくなるため却下しました。その後、受付エラーはなくなり、担当者への引き継ぎ率は7%にとどまりました。

間違えやすいポイント

  • JSONモードならスキーマも守られます。 JSONモードが保証するのは構文だけです。フィールドと型は、スキーマに基づく構造化出力が保証します。
  • スキーマを通れば内容も正しいです。 形式が正しく、値が誤った結果もあり得ます。合計、日付、状態などの関係は、意味の検証で確認します。
  • 任意フィールドはrequiredから外せばよいです。 OpenAIのstrictモードでは、すべてをrequiredに入れ、nullを許容する型で表します。
  • スキーマに書いた制約はベンダーがすべて守ります。 サポート範囲は異なります。Anthropicは数値や文字列長の制約を受け付けないため、アプリケーションで検証します。
  • 実行しなかった呼び出しは結果を省略できます。 すべての呼び出しに結果が必要です。実行しなければ、その事実をエラー結果で知らせます。
  • strictを有効にしたので、その引数で実行しても安全です。 strictが保証するのは形だけです。権限、所有権、業務ルールは、実行直前にサーバーが検査します。
  • 並列呼び出しの結果はメッセージを分けて送れます。 Anthropicでは1つのuserメッセージにまとめる必要があり、分けると並列呼び出しが減ります。

ひと目でわかるまとめ

テーマ 覚えておくこと
保証範囲 指示文<JSONモード(構文)<構造化出力(スキーマ)。値の正確性は保証外
strictのルール additionalProperties: false。OpenAIは全フィールドrequired+null。Anthropicは数値・長さの制約、再帰が非対応
スキーマ設計 「ない」の意味を区別、単位を固定、サーバーが知る値はサーバーで、バージョンフィールド
検証 状態→構文→スキーマ→意味
ツール定義・ループ 説明がすべて。ループは状態機械+ステップ・時間・トークンの予算
結果の返却 呼び出しIDで対応付ける。全呼び出しに結果。Anthropicは1メッセージ、結果が先
強制呼び出し・推論項目 強制呼び出しは対応モデルが限られる(代替:auto+strict、構造化出力)。推論項目は受け取ったまま返す
権限 識別子はサーバーが入れ、実行直前に再検査

公式ドキュメントで確認する

セルフチェック

2-1. 業務の概念からスキーマを導く際に決める4点を挙げ、「値を読めない」と「もともと値がない」を区別する表現を例で示してください。

2-2. OpenAIとAnthropicのstrictスキーマのルールの違いを2つ挙げ、両社を併用するサービスのスキーマ管理で注意することを書いてください。

2-3. 構文の検証、スキーマの検証、意味の検証を各1文で定義し、3段階より先に確認することを書いてください。

2-4. ツール結果を返す際の共通エンベロープのフィールドを定め、失敗結果に次に試すことを記す理由を説明してください。

2-5. ツール呼び出しループを状態機械として描く際に必要な状態を列挙し、設ける3つの予算と超過時の処理を書いてください。

2-6. モデルに引数として選ばせず、サーバーが直接入れるべき値を3つ挙げ、その理由をOWASP Top 10 for LLM Applicationsのリスク項目と関連付けて説明してください。

2-7. 社内会議室の予約アシスタントを設計してください。ツール一覧、各ツールの説明に必須の内容、サーバーが直接入れる値、実行直前の検査項目を表にまとめてください。

実習課題

実習

スキーマと3段階の検証器(45分)

  1. 領収書、注文書、問い合わせメールから1つを選び、業務の概念からスキーマを導く。「ない」の意味を区別する列挙型、単位、サーバーが付与する値、schema_versionを含める。
  2. 使用するベンダーのstrictのルールに合わせ、非対応の制約を説明とアプリケーションの検査に移す。
  3. 応答の状態→構文→スキーマ→意味の順に検査する関数を作り、各段階の失敗を別の経路(リトライ、確認質問、保留)に送る。
  4. 入力20件を実行する。うち5件は値が欠けているか、互いに矛盾する入力にする。

完了基準: 全20件がどの段階で合格・失敗したかを表で示し、矛盾する入力5件が意味の検証で除外されれば完了とする。

実習

予算付きの状態機械によるツールループ(60分)

  1. 照会ツール2つと変更ツール1つを定義し、strictを有効にする。ユーザーIDはサーバーが入れる。
  2. 変更ツールは照会結果で得た識別子がある場合だけ実行可能にし、実行直前に権限と状態を検査する。
  3. ループにステップ数と時間の予算を設け、超えたら部分結果とともに人へ引き継ぐ。
  4. 変更ツールが3回に1回失敗するよう障害を注入し、失敗を共通エンベロープ形式のエラー結果で返す。実行しなかった呼び出しにも結果を返す。
  5. 会話を10回実行してログを確認する。

完了基準: 全ツール呼び出しに対応する結果があり、検査で止まった呼び出しは実行されず、予算超過の会話が人へ引き継がれれば完了とする。