チュートリアル

AI Structured Output とは?JSON Schema で LLM の JSON を安定させる

プロンプトの「JSON だけ」は確率を下げるだけです。Structured Output は形をデコード段階で固定し、JSON Schema がフィールド・型・enum を契約にします。

モデルをパイプラインに繋ぐとき、怖いのは文章の拙さではなく、コードが食べられない戻り値です。カンマ欠け、フィールド名の変更、数値が文字列になる。Structured Output が解くのはそこです。モデルは token を出す時点で宣言した形で話し、あとから正規表現で JSON を削り出す前提にしません。JSON Schema はその形の最も一般的な書面です。読み終えたら、詰まっているのが「構文」「形」「業務の正否」のどれか判断でき、Schema のあともローカル検証が要ると分かるはずです。

プロンプトだけの JSON が信頼できない理由

「JSON だけ返して、説明は不要」は最もよくある場当たりです。好みを文脈に書くので効くこともあります。信頼できないのは、好みが拘束ではないからです。モデルはオブジェクトを markdown のコードフェンスで包んだり、必須キーを落としたり、priorityをより「自然な」urgencyに書き換えることがあります。下流がJSON.parseした瞬間、失敗は文言の問題ではなくパイプライン全体の停止です。

より静かな失敗は「parse できるが形が違う」です。itemsは配列のはずがオブジェクトになり、スコアは数値のはずが"0.91"になります。コードはundefinedを読むか文字列結合し、問題はかなり後ろで爆発します。プロンプトはこのドリフトを止められません。デコード段階で不正 token を拒否しないからです。

だから Structured Output を「より厳しいプロンプト」だと思わないでください。生成の一部です。サーバーが Schema を次に許される token 集合へコンパイルし、モデルは括弧の不一致や名簿にないキーを出せません。プロンプトはタスク説明、形は Schema です。

Structured Output が拘束するのはどの層か

三層で考えるとよいです。どれか二つを混ぜると「モデルが適当」に見えます。第一層は構文:parse できる JSON。第二層は形:名前・型・必須・enum が Schema に合うこと。第三層は意味:分類は正しいか、金額は本物か。Structured Output がカバーするのは前二者です。第三層は常にあなたの責任です。

段階 設定するもの 実際の保証
プロンプトの約束 「JSON だけ」 好みであり、契約ではない
JSON モード MIME または json_object 合法 JSON になる可能性は高いが、フィールド名はモデル任せ
Schema モード JSON Schema と厳格フラグ 形・型・必須・enum を Schema が拘束

OpenAI は Structured Outputs を JSON mode の次の段階として書いています。どちらも合法 JSON を出せますが、渡した Schema への一致を保証するのは前者だけです。公式の対照はStructured model outputs。Gemini も「JSON であればよい」と「Schema のフィールドを出す」を分けます。スイッチの詳細はGemini API の JSON 出力ガイドを見てください。本記事では SDK の細部は繰り返しません。

見落としやすい境界もあります。安全上の拒否、打ち切り、ツール呼び出し失敗は、成功オブジェクトに収まらないことがあります。API によっては別のrefusalや空の内容を返します。Schema が拘束するのは「形式で話し始めた区間」であり、「この呼び出しは必ず成功する」ではありません。

JSON Schema が契約になるまで

JSON Schema はもともと JSON 文書の語彙です。型、必須、enum、数値範囲、配列要素。書き方の標準的な解説はUnderstanding JSON Schema。モデルに繋ぐと、同じ語彙に別の仕事が加わります。事後検証だけではなく、生成中に探索空間を狭めます。

エンジニアリングでは、Schema はコンパイル時と実行時で共有する契約です。Pydantic、Zod、Swift の生成可能型は、最終的に言語非依存の JSON Schema に落ちることが多く、クラウドモデルへ渡し、ログに書き、フィクスチャで再生できます。フィールド表は一つだけ保守し、App がtotalCents、API がamount、プロンプトが「金額」と言う食い違いを避けます。

契約には「本当に読むオブジェクト」を書き、「完全な世界モデル」は書かない。任意フィールドが増えるほど、誤記入や空欄の機会が増えます。必須はrequired、閉じた集合はenum、数値範囲はminimum / maximum。プロパティのdescriptionは、プロンプトで再度説明するより安定しがちです。拘束とデコードが一体だからです。

厳格モードにはもう一つよくある規則があります。オブジェクトはadditionalProperties: falseが必要で、宣言したフィールドはすべてrequiredに入れます。本当に任意の値は「required から外す」のではなく、nullを許します。そうしないとモデルがキーを省略し、コードはobj.fieldが必ずある前提のままになります。

各 API が Schema を渡す方法

各社の製品名は Structured Output でも、包むフィールドは違います。まず二つ聞いてください。この Schema は最終回答を拘束するのか、ツール引数なのか。今のモデルスナップショットは本当に厳格モードを支援するか。ドキュメントのキーワードが全エンドポイントで同じ動きだと思わないでください。

OpenAI:json_schema と strict

Chat Completions ではresponse_formatをjson_schemaにし、strict: trueをオンにします。Responses API は同じことをテキスト形式フィールドに書きます。Schema 規則は同じで、外壳だけ違います。次のリクエストはチケットを抽出します。カテゴリは enum、アカウントは null 可です。

{
  "model": "gpt-4o-2024-08-06",
  "messages": [
    { "role": "system", "content": "Extract the ticket into the schema." },
    { "role": "user", "content": "Checkout 500s on a saved card. Account acct_8842." }
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "support_ticket",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "summary": { "type": "string" },
          "category": {
            "type": "string",
            "enum": ["billing", "bug", "account", "other"]
          },
          "severity": { "type": "integer" },
          "account_id": {
            "anyOf": [{ "type": "string" }, { "type": "null" }]
          }
        },
        "required": ["summary", "category", "severity", "account_id"],
        "additionalProperties": false
      }
    }
  }
}

応答は「全体が JSON」として扱ってください。先に正規表現でコードフェンスを削らない。SDK の parse ヘルパーは型付きオブジェクトへデシリアライズできます。拒否分岐も処理してください。安全ポリシーが発火すると、成功 Schema に合わないことがあります。

Gemini とその他のスタック

Gemini は MIME タイプで「これは JSON」と宣言し、responseSchemaまたはresponseJsonSchemaでフィールドを固定します。後者は標準 JSON Schema に近く、anyOf、$ref、数値範囲に向きます。詳細と Python / JS の例はGemini Structured Output チュートリアル。

Apple の on-device モデルは@Generableのようなコンパイル時の形であり、手書きの JSON Schema ファイルではありません。プロセスを出て HTTP を叩き、ログを書くときは、やはりシリアライズ可能な JSON が必要です。三つの呼び出しチェーンの分け方はApple AI Agent と JSON。外部 Agent が MCP や REST を使うとき、ペイロードはほぼ必ず JSON で、Schema はアダプタ層が揃える表のままです。

本番に出せる Schema

次の Schema はチケット分類を模しています。カテゴリは閉じ、優先度は整数、要約は文字列。Schema が持つべきなのは形であり、「このチケットを業務上紧急にすべきか」ではありません。

{
  "type": "object",
  "properties": {
    "category": {
      "type": "string",
      "enum": ["billing", "bug", "feature"],
      "description": "Ticket category"
    },
    "priority": {
      "type": "integer",
      "minimum": 1,
      "maximum": 5
    },
    "summary": {
      "type": "string"
    }
  },
  "required": ["category", "priority", "summary"],
  "additionalProperties": false
}

配列の要素はitemsで記述します。タプル寄りの固定長リストは JSON Schema 経路ならprefixItemsを見てください。ネストはまずインライン。同じ構造が三度目、または木ノードが自身を指すときだけ$defs + $refを使います。早すぎる抽象はエラーを読みにくくします。サーバーが Schema を拒否したとき、展開後の巨大オブジェクトと向き合うことになります。

Prompt と Schema に構造を二重に書かないでください。重複した説明はモデルを二つの話の間で揺らし、入力 token も浪費します。タスク説明はプロンプト、名前と型は Schema だけ。ローカル型がフィールドを変えたら、リクエストの Schema も一緒に変えます。そうしないと本番で新しいキーや欠けたキーが静かに現れます。

拘束したあともローカル検証は必要

Structured Output はパース層をかなり静かにします。分類が正しいことや、数値が業務上限に合うことは保証しません。enum は集合を制限できても、誤った選択は止められません。優先度 5 は合法で、「本当は 2 であるべき」も合法 JSON です。重要経路は抜検するか規則を乗せてください。

本番ではモデルの戻り値を普通の JSON として見てください。まずJSON フォーマットでネストを確認し、JSON 検証で parse できることを確かめ、同じオブジェクトをJSON Schemaに入れてローカルで再検証します。ゴールデンセットとの比較はJSON Diffで、キー順や nullable のドリフトが一目で分かります。

フィクスチャは三件残してください:ticket.valid.json、ticket.missing-field.json、ticket.wrong-enum.json。一つ目は happy path、あとの二つはローカル検証が本当に拒否することを確認します。モデル側がすでに拘束した誤りは、ローカルでも再現できるべきです。そうしないと、モデルを替える日や厳格モードを切った日に、オフラインのパイプラインで初めて気づきます。

よくある質問 FAQ

Structured Output と JSON mode の違いは?

JSON mode は parse できる JSON を保証します。Structured Output はその上で、渡した JSON Schema への一致を保証します。名前、型、必須、enum が契約どおりです。コードが名前付きフィールドを読むなら後者を使います。

Schema があるのに「JSON だけ」と書くべき?

短い一文は残してよいです。フィールド表をプロンプトに写さないでください。形は Schema が正です。二重の説明は品質を下げ、クォータも浪費します。

厳格モードで任意フィールドをどう表す?

多くの厳格実装は追加プロパティを閉じ、列挙したフィールドをすべて必須にします。任意値は nullable にします。string と null のユニオンであり、required からキーを消すのではありません。

Schema を通っても業務は間違う?

間違います。Schema は形を見ても真偽は見ません。誤分類、金額の幻覚、拒否すべきのに無理に埋める、いずれも合法 JSON になり得ます。本番では規則、抜検、人の確認が残ります。

まとめと次の一歩

Structured Output は文言の技巧ではありません。JSON の形を「希望」から「デコード拘束」へ変えます。JSON Schema はその拘束の最も一般的な書き方です。必須、enum、範囲、余分なキーの禁止が、下流が安定してJSON.parseし、期待するフィールドを読めるかを決めます。各 API のスイッチ名は違っても、層は同じです。構文、形、意味。混ぜないでください。

次は、本当に読む小さな Schema を書き、厳格モードで抽出か分類を一本通し、ブラウザで戻り値をローカル検証してください。Gemini のリクエスト例は前の記事、Agent 引数が JSON に落ちる様子は Apple の記事を開いてください。