OpenAI・Gemini・Claude の構造化出力を徹底比較 — JSON スキーマ準拠を確実に得る方法

OpenAI・Gemini・Claude の構造化出力を徹底比較 — JSON スキーマ準拠を確実に得る方法

OpenAI・Gemini・Claude の構造化出力を API コード例付きで比較。Constrained Decoding の仕組み、Required/Optional フィールドの挙動差、Nullable 型の方言差(JSON Schema vs OpenAPI)まで掘り下げ、プラットフォーム間の実装上の違いを整理します。
2026.07.13

はじめに

LLM の出力を下流処理に流すとき、「返ってきた JSON がスキーマに準拠していない」という問題に悩まされたことはないでしょうか。

プロンプトで「JSON で返して」と指示するだけでは、余計なキーが混入したり、型が違ったり、そもそも JSON として壊れていたりすることがあります。各プラットフォームはこの課題に対して異なるアプローチで「スキーマ準拠の保証」を実現しています。

この記事では、OpenAI・Gemini・Claude の構造化出力の仕組みを API コード例付きで比較し、さらに「Required vs Optional フィールド」「Nullable 型の書き方の違い」といった実装時にハマりやすいポイントまで掘り下げます。

3社の構造化出力アプローチ

まず、各プラットフォームがどのような仕組みでスキーマ準拠を実現しているかを整理します。

llm-structured-output-openai-gemini-claude-comparison-mechanism

OpenAI — response_format + strict: true

OpenAI は response_format パラメータに JSON Schema を直接渡す方式です。strict: true を指定すると Constrained Decoding(制約付きデコーディング)が有効になり、モデルの出力が 100% スキーマに準拠することが保証されます。

from openai import OpenAI
client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Extract: John is 30, lives in Tokyo"}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "person",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "age": {"type": "integer"},
                    "city": {"type": "string"}
                },
                "required": ["name", "age", "city"],
                "additionalProperties": False
            }
        }
    }
)
# message.content に JSON 文字列が返る
# {"name": "John", "age": 30, "city": "Tokyo"}

ポイント:

  • strict: true は Constrained Decoding を有効にし、スキーマ準拠を 100% 保証する
  • additionalProperties: false がすべてのオブジェクト階層で必須
  • すべてのプロパティを required に含める必要がある(後述)
  • JSON Schema のサブセットのみサポート(oneOf 不可、$ref は限定的)

Gemini — responseMimeType + responseSchema

Gemini は responseMimeType で出力形式を指定し、responseSchema で構造を定義する 2 段構えの方式です。

from google import genai
from google.genai import types

client = genai.Client()

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Extract: John is 30, lives in Tokyo",
    config=types.GenerateContentConfig(
        response_mime_type="application/json",
        response_schema={
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "city": {"type": "string"}
            },
            "required": ["name", "age", "city"]
        }
    )
)
# response.text に JSON 文字列が返る
# {"name": "John", "age": 30, "city": "Tokyo"}

ポイント:

  • response_mime_type 単体 = 有効な JSON は保証されるがスキーマ準拠は保証されない
  • response_schema を追加すると、制約付きデコーディングでスキーマに準拠した出力が得られる
  • enum 指定で分類タスクにも対応
  • Pydantic モデルを直接スキーマとして渡すことも可能

Claude — Structured Outputs / Tool Use によるスキーマ抽出

Claude は 2025 年後半に Structured Outputsresponse_formatjson_schema を指定する方式)をネイティブサポートしました。それ以前から利用されている Tool Use + tool_choice 強制 も引き続き有効な手段です。ここでは、より広く使われている Tool Use 方式を例示します。

import anthropic
client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Extract: John is 30, lives in Tokyo"}],
    tools=[{
        "name": "extract_person",
        "description": "Extract person info",
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "city": {"type": "string"}
            },
            "required": ["name", "age", "city"]
        }
    }],
    tool_choice={"type": "tool", "name": "extract_person"}
)

# tool_use ブロックの input フィールドから取得
tool_block = next(b for b in response.content if b.type == "tool_use")
result = tool_block.input
# {"name": "John", "age": 30, "city": "Tokyo"}

ポイント:

  • Structured Outputs(response_format: json_schema)によるネイティブ JSON mode も利用可能(2025年後半〜)
  • Tool Use のスキーマ + tool_choice 強制でも高い信頼性のスキーマ準拠を実現
  • Tool Use 方式の出力は message.content のテキストではなく、tool_use ブロックの input フィールドに格納される
  • OpenAI の strict mode より広い JSON Schema サポート

比較まとめ

OpenAI Gemini Claude
メカニズム response_format.json_schema responseMimeType + responseSchema Tool Use + tool_choice
スキーマ保証 strict: true → 100% responseSchema → 制約付きデコーディング Tool schema → 高い信頼性
JSON のみモード type: "json_object"(スキーマなし) responseMimeType 単体 なし
Constrained Decoding あり(明示的) あり あり(Structured Outputs で保証)
出力の取得場所 message.content response.text tool_use.input
スキーマ方言 JSON Schema サブセット OpenAPI スタイル JSON Schema(フルサポート)

Required vs Optional — 「必須フィールド」の落とし穴

構造化出力を使い始めると、すぐにぶつかるのが「フィールドをオプショナルにしたい」というケースです。ここで 3 社の挙動が大きく異なります。

llm-structured-output-openai-gemini-claude-comparison-required-nullable

OpenAI: すべてのフィールドが required 必須

OpenAI の strict: true モードでは、すべてのプロパティを required 配列に含めなければならない という制約があります。

{
  "strict": true,
  "schema": {
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "nickname": { "type": ["string", "null"] }
    },
    "required": ["name", "nickname"],       // nickname も required
    "additionalProperties": false
  }
}
// 出力: {"name": "John", "nickname": null}  ← フィールドは必ず存在、値が null
// フィールド自体が欠落することはない

「オプショナル」を表現するには、型を ["string", "null"] にして required だが nullable とします。フィールドのキー自体が出力から欠落することはありません。

Gemini: 真のオプショナルフィールドが可能

Gemini では required に含めないフィールドは出力から完全に省略される可能性があります。nullable の構文も OpenAPI スタイルの nullable: true を使います。

response_schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "nickname": {"type": "string", "nullable": True}
    },
    "required": ["name"]   # nickname は required に含めない
}
# 出力: {"name": "John"}              ← nickname キー自体がない
# 出力: {"name": "John", "nickname": null}  ← これも有効

Claude: 標準 JSON Schema のルールに従う

Claude の Tool Use スキーマは標準的な JSON Schema に準拠しており、required に含めないフィールドは省略可能です。

input_schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "nickname": {"type": ["string", "null"]}
    },
    "required": ["name"]   # nickname はオプショナル
}
# 出力: {"name": "John"}                     ← OK
# 出力: {"name": "John", "nickname": null}    ← OK
# 出力: {"name": "John", "nickname": "JD"}    ← OK

Required の挙動比較

OpenAI (strict) Gemini Claude
オプショナルフィールド 不可 — 全プロパティ required 必須 可能 可能
「オプショナル」の代替手段 "type": ["string", "null"] required から除外 required から除外
フィールド欠落 絶対に起きない 起こりうる 起こりうる

OpenAI の strict mode を使う場合、下流のパーサーは 「キーは必ず存在するが値が null かもしれない」 という前提で実装します。一方、Gemini や Claude を使う場合は 「キー自体が存在しない可能性がある」 ことを考慮する必要があります。

Nullable 型 — JSON Schema vs OpenAPI の方言差

「値が null になりうる」ことを表現する構文が、プラットフォームによって異なります。これは JSON Schema と OpenAPI 仕様の歴史的な違いに由来します。

JSON Schema 方式: type 配列

OpenAI と Claude は JSON Schema の標準構文である type 配列を使います。

{ "type": ["string", "null"] }

これは「string または null のいずれか」を意味します。JSON Schema の Union Type です。

OpenAPI 方式: nullable キーワード

Gemini は OpenAPI(旧 Swagger)スタイルの nullable キーワードを使います。

{ "type": "string", "nullable": true }

意味は同じですが、構文が異なります。

Nullable 構文の比較

構文 方言
OpenAI "type": ["string", "null"] {"nickname": {"type": ["string", "null"]}} JSON Schema
Gemini "nullable": true {"nickname": {"type": "string", "nullable": true}} OpenAPI
Claude "type": ["string", "null"] {"nickname": {"type": ["string", "null"]}} JSON Schema

実装上の注意点として、スキーマ定義を複数プラットフォームで共有する場合は、この方言差を吸収する変換レイヤーが必要になります。

まとめ

  • OpenAIstrict: true + additionalProperties: false で最も明示的にスキーマ準拠を保証する。ただし全フィールド required 制約があり、オプショナルは nullable で代替する
  • GeminiresponseMimeType + responseSchema の 2 段構えで、API としては最もクリーン。OpenAPI スタイルの nullable に注意
  • Claude は Tool Use + tool_choice 強制というやや間接的な方法だが、JSON Schema のフルサポートにより最も柔軟

どのプラットフォームを使う場合も、以下を意識すると良いです。

  1. 「JSON を返して」とプロンプトで指示するだけでは不十分 — 必ず専用の構造化出力機能を使う
  2. オプショナルフィールドの扱いはプラットフォームで異なる — 下流パーサーの実装に直結する
  3. スキーマの方言差に注意 — マルチプラットフォーム対応時は変換レイヤーの検討が必要

Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

この記事をシェアする

AI白書

関連記事