チュートリアル

Gemini API で JSON を出力するには?Structured Output と JSON Schema の実践ガイド

モデルに「JSON だけ返して」と頼むのはやめましょう。Structured Output で形を固定し、JSON Schema でフィールドを縛れば、下流のコードが安定して解析できます。

Gemini に JSON を頼むと、前置き文、欠けたカンマ、「わかりやすい」フィールド名が混ざります。プロンプトの「JSON のみ」は確率を下げるだけで契約ではありません。Structured Output は契約をデコード側に移します。MIME を宣言し Schema を付ければ、その形で token が出ます。読み終われば JSON モードと Schema モードを選べ、動くリクエストを書け、それでもローカル検証が必要なことがわかります。

なぜ構造化出力が必要か

下流が JSON.parse した瞬間、失敗は文言の問題ではなくパイプライン停止です。分類は固定 enum、抽出は安定キー、ツール呼び出しはパラメータオブジェクトが要ります。自由記述のコストは高く、壊れた JSON 一回で再試行・ログ・ユーザーの再クリックになります。

より静かな失敗は「parse できるが形が違う」ことです。items が配列であるべきなのにオブジェクトが来たり、score が数値であるべきなのに "0.9" が来たりします。コードは undefined を読み、バグはずっと後で爆発します。Structured Output が直すのは形であり正誤ではありません。合法で Schema に沿った JSON は取れても、分類が正しい保証はありません。本番では業務ルール検証が残ります。静かになるのはパース層です。

公式の能力説明は Gemini Structured output ドキュメント を参照してください。標準 JSON Schema のキーワードとキー順の強化は Structured Outputs の更新 にあります。書く前に Understanding JSON Schema を見て、「仕様上書ける語」と「今のモデルが実際に縛る語」を混同しないでください。

JSON モードと Schema モード

スイッチは二段です。一段目は「JSON として parse できる」ことだけ。二段目は「宣言した Schema に合う JSON」です。名前付きフィールドを読むなら二段目。探索的抽出でキー自体もモデルに任せるときだけ一段目を検討します。

段階 設定するもの 実際の保証
JSON モード responseMimeType: application/json 多くの場合は合法 JSON。名前と入れ子はまだモデル任せ
Schema モード MIME タイプ + responseSchemaまたはresponseJsonSchema 形・型・必須・enum を Schema が拘束

JSON モードだけのとき、公式も「強いヒント」であり不正出力の小さなリスクを残すと注意します。オブジェクトとして必ず parse したいなら Schema も送ります。Schema は入力 token に入るので、同じ説明を prompt に再掲しないでください。重複は品質とクォータを削ります。

responseSchema か responseJsonSchema か

responseSchema は OpenAPI 3.0 風のサブセットです。REST の型名は OBJECTSTRING のように大文字が多いです。フラットなオブジェクト、enum 分類、propertyOrdering でキー順を固定する用途に向きます。$ref / $defs は解釈しないため、再帰ツリーや定義の再利用はインライン化しかなく、すぐ膨らみます。

responseJsonSchema は Gemini 2.5 以降向けで、より標準に近い JSON Schema を扱い、anyOf$refminimum / maximumadditionalPropertiestype: nullprefixItems などをカバーします。Pydantic や Zod から生成して渡すと摩擦が減ります。新しいモデルは宣言順のキーを保つので、ログ diff とゴールデンテストに向きます。

選び方は三つ。フラットな分類・抽出ならどちらでもよい。再帰・共有定義・ユニオンなら responseJsonSchema を優先。キー順を固定するなら、今のエンドポイントが propertyOrdering をまだ尊重するか確認し、全 API が同じだと思い込まないこと。

Schema の書き方

本当に読むオブジェクトだけ書いてください。世界の完全モデルは不要です。任意フィールドを増やすほど、誤記入の隙が増えます。必須は required、enum は enum、数値範囲は minimum / maximum。プロパティの description は prompt で再説明より安定しがちです。拘束がデコードと一緒に動くからです。

次の 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 を見てください。required に入れないことだけでは nullable になりません。null を明示しないと、モデルがキーを省略し、コードは obj.field が必ず在ると仮定したままになります。

入れ子はインラインで十分です。同じ構造が三度目、または木が自分を参照するときだけ $defs + $ref を使います。早すぎる抽象化は拒否理由を読みにくくします。サーバが Schema を拒むと、展開後の巨大オブジェクトと向き合うことになります。

Python と JavaScript の実装

公式 SDK のよくある書き方です。モデル名はアカウントで使える 2.5 / 新しめの ID に置き換え、サンプル名を本番固定だと思わないでください。

Python:MIME タイプ + JSON Schema

from google import genai

client = genai.Client()
schema = {
    "type": "object",
    "properties": {
        "category": {"type": "string", "enum": ["billing", "bug", "feature"]},
        "priority": {"type": "integer"},
        "summary": {"type": "string"},
    },
    "required": ["category", "priority", "summary"],
}

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Classify this ticket: invoice PDF cannot be downloaded.",
    config={
        "response_mime_type": "application/json",
        "response_json_schema": schema,
    },
)
print(response.text)

すでに Pydantic なら Model.model_json_schema()response_json_schema に渡し、model_validate_json(response.text) でローカル再検証します。第一層は API の形、第二層は「合法に見えて意味がおかしい値」(優先度 99 など)を型で拒否することです。

JavaScript:generationConfig

const response = await ai.models.generateContent({
  model: "gemini-2.5-flash",
  contents: "Classify this ticket: invoice PDF cannot be downloaded.",
  config: {
    responseMimeType: "application/json",
    responseJsonSchema: {
      type: "object",
      properties: {
        category: { type: "string", enum: ["billing", "bug", "feature"] },
        priority: { type: "integer" },
        summary: { type: "string" },
      },
      required: ["category", "priority", "summary"],
    },
  },
});
const data = JSON.parse(response.text);

REST なら同じフィールドを generationConfig に置きます。OpenAPI 風の responseSchema は一部エンドポイントで型名が大文字のままです。JSON Schema の小文字 object と混ぜないでください。応答はすぐ JSON.parse。正規表現でコードブロックを削らないこと。JSON MIME を宣言したなら、全体を JSON として扱います。

よくある落とし穴

  • 構造を prompt と Schema の両方に書くと、モデルが二つの説明の間で揺れ、品質が落ちます。
  • 巨大 Schema:深い入れ子、深すぎる $ref、広すぎる anyOf は拒否されたり拘束が弱まったりします。小さなオブジェクトで通してから足してください。
  • 真実性を Schema に丸投げしないでください。enum は集合を限るだけで、誤選択は止められません。重要な経路は抜き取りやルールを。
  • ローカル型と Schema のドリフト。Pydantic / Zod を変えてリクエスト側 Schema が古いと、本番でキーが増減しても静かです。
  • ハッピーパスだけでは足りません。空配列、nullable、長い文字列、不正 enum(ブロックされるべき)を足してください。

もう一つの運用問題:ログに response.text だけ残さないこと。モデル、Schema ハッシュ、prompt 版を一緒に。壊れの多くは「既定が変わった」か「Schema の微修正が拒否された」です。この三点がないと「昨日まで動いた」になります。

本番:検証・比較・やり直し

API の本文は信用できないバイト列です。parse し、同じ Schema で再検証し、内部型へ写します。失敗時は原文を記録(秘匿はマスク)し、再試行か縮退かを決めます。まったく別の Schema で再試行すると、モデル不安定なのか契約変更なのか切り分けできません。

デバッグではサンプルを本サイトのツールに貼るのが早いです。JSON フォーマット で入れ子を見、JSON 検証 で構文を取り、契約を JSON Schema に入れてインスタンスが通るか見ます。フィールドが増減したら JSON Diff で二回の応答を比べてください。目視のログより速いです。

抽出パイプラインを設計するなら、まず「理想出力」を手で書き、Schema ツールで型を逆算し、その Schema を Gemini リクエストに戻します。契約の源は一箇所——チャットの口約束ではなく、テストできる文書——になります。

よくある質問 FAQ

application/json だけで足りますか?

探索段階なら可。固定フィールドを読むなら Schema も出してください。でなければ JSON に似た散文であり、API ではありません。

Structured Output は関数呼び出しの代わりになりますか?

なりません。関数呼び出しはツール選択と引数埋め。Structured Output は今回の回答の形です。コード実行や外部 API ならツール。型付きデータ一塊なら Structured Output で往復を減らせます。

なぜ Schema が拒否されますか?

そのエンドポイントが未対応のキーワード、再帰が深すぎる、responseSchema と responseJsonSchema の方言混在が典型です。オブジェクト一つ・フィールド三つまで減らして通してから足してください。

出力は必ず正しいですか?

いいえ。形が合法でも事実は誤り得ます。金額、メール、チケット分類はルールか抜き取りが要ります。

まとめと次の一歩

安定した JSON は、より長い「JSON だけ出して」ではなく MIME と Schema です。フラットな仕事なら responseSchemaまたはresponseJsonSchema のどちらでもよく、$ref、ユニオン、Pydantic/Zod 生成なら後者です。応答後もローカル検証し、失敗例を整形・Schema・Diff で回帰テストにしてください。

次は、いちばん壊れやすいエンドポイントを「一つの Schema、一回のリクエスト、一回のローカル validate」にしてください。そこを静かにしてから横展開します。