OpenAIのAPIレスポンスをJSON Schemaで定義する

OpenAI(正確には Azure OpenAI)で API のレスポンスを JSON 形式で扱う際、JSON Schema を活用する機会がありました。
これまで触れる機会がなかったため、その学びをまとめています。

目次

🧠 JSON Schemaとは

JSONデータの構造・型・制約条件を定義するための「仕様(スキーマ)」です。

要するに「このJSONはどういう形で、どんな値が入るのが正しいか」を機械的に定義できるルールブックとなります。

参考サイト:
JSON Schema 公式サイト
https://json-schema.org


📦 目的・メリット

目的説明
データ構造の検証API リクエスト/レスポンスや設定ファイルの構造が正しいかをチェックできる。
自動ドキュメント生成JSON Schema から API ドキュメントを自動生成できる(例:Swagger / OpenAPI 連携)。
バリデーションの一元管理バックエンドとフロントエンドで共通のスキーマ定義を再利用できる。

🧩 基本構文と使い方

✅ 例:基本的なスキーマ定義

{
  "$schema": "<https://json-schema.org/draft/2020-12/schema>",
  "$id": "<https://example.com/schemas/user.schema.json>",
  "title": "User",
  "description": "ユーザー情報を表すスキーマ。ID、名前、メールアドレス、年齢を含む。",
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "description": "ユーザーを一意に識別するID。"
    },
    "name": {
      "type": "string",
      "description": "ユーザーの氏名。"
    },
    "email": {
      "type": "string",
      "format": "email",
      "description": "有効なメールアドレス形式の文字列。"
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "description": "ユーザーの年齢。0以上の整数。"
    }
  },
  "required": ["id", "name", "email"]
}

🔍 このスキーマが意味すること

上記のスキーマはobject型で定義されており、それぞれのフィールドについて下記の制約となっています。
idは整数型、nameは文字列型、emailはemail形式の文字列型、ageは0以上の整数。
idnameemailが必須。

以下補足:

  • $schema : draftのバージョンを指定
  • $id : スキーマのURIを設定。同じドキュメント内または外部のJSONドキュメントからスキーマの要素を参照可能。
  • title:スキーマの名称(ここでは “User”)。
  • description: スキーマの意図。
  • type:スキーマの型。
  • formatminimum:typeごとに使用できるキーワード。文字列形式や最小/最大値などを制限可能。
    ※上記はキーワードの一例です。使用可能なキーワードは公式ドキュメントをご参照ください。
  • required:必須項目。

🧰 応用的な使い方

🔸 oneOf / anyOf / allOf

複数のスキーマを組み合わせることも可能です。

{
  "oneOf": [
    { "required": ["email"] },
    { "required": ["phone"] }
  ]
}

→ 上記の例(oneOf)ではemail または phone のいずれか1つのみの指定を強制します。

🔸 $ref(スキーマの再利用)

別ファイルや同一ファイル内のスキーマを参照することが可能です。

{
  "$ref": "#/definitions/address"
}

{
  "definitions": {
    "address": {
      "type": "object",
      "properties": { "zip": { "type": "string" } },
      "required": ["zip"]
    }
  }
}

🔸 dependencies / if-then-else

条件付きルールを定義することも可能です(Draft 2020-12 以降で機能強化)。

{
  "if": { "properties": { "type": { "const": "company" } } },
  "then": { "required": ["company_name"] },
  "else": { "required": ["personal_name"] }
}

🌍 利用事例

分野利用例
API開発OpenAPI(Swagger)内部でJSON Schema準拠の型定義を使用
フロントエンドReact + Formライブラリ(react-jsonschema-form)で動的フォーム生成
バックエンドAPIバリデーション・DB保存前の整合性チェック
設定ファイルVS Codeのsettings.jsonなどで補完に利用
テストJSONレスポンスをスキーマで検証(例: Jest + AJV)

📜 Draftの種類(仕様バージョン)

JSON SchemaはIETF Draft仕様として継続的に進化しています。

Draft主な特徴
Draft-042013初期によく使われていた(今も古いライブラリで対応)
Draft-062017$id/$ref改善、examples導入
Draft-072018if/then/else導入、contentEncodingなど
Draft 2019-092019$defs導入、バリデーション構文整理
Draft 2020-122020最新の安定版
より強力なリファレンスと条件文サポート

👉 現在は「Draft 2020-12」が推奨されています

🤖 JSON Schema × OpenAI API

背景

OpenAIのAPI(特にResponsesやChat Completions)では、モデル出力を「構造化データ」として制御するためにJSON Schemaが利用できるようになりました。

つまりモデルが「自然文で出力する」のではなく、「決まった形式(JSON)」で確実に出力するよう指示することが可能です。

従来の問題点の例

Q. 次のテキストから住所と電話番号を抽出してください。

A. 住所:東京都千代田区丸の内1-1-1
電話番号:03-1234-5678
  • 出力は人間向けテキスト
  • 機械的に扱うにはパースが必要
  • エッジケースで壊れやすい

JSON Schemaを使うと:

{
  "address": "東京都千代田区丸の内1-1-1",
  "phone": "03-1234-5678"
}
  • 完全に構造化されたJSONで返却
  • パース不要で安全かつ安定

📋 OpenAIがサポートしているJSON Schema仕様

項目内容
準拠仕様JSON Schema Draft 2020-12(のサブセット)
フィールド名type, properties, required, enum, format など基本構文は利用可
制限allOfnotdependentRequireddependentSchemasifthenelseは現在サポートしていない
ファインチューニングしているモデルは以下も未サポート
strings: minLengthmaxLengthpatternformat
numbers: minimummaximummultipleOf
objects: patternProperties
arrays: minItemsmaxItems
注意点JSON Schemaとの完全一致を保証できるのはGPT-4o以降のみ。
gpt-4-turbo以前はJSONモードしか使用できない
(JSON構文であることしか保証されないため、厳密にチェックする場合は別途検証が必要)

⚙️ OpenAIでの使用方法

✅ Responses API / Chat Completions API

スキーマを直接指定できるようになっています。

参考URL:
OpenAI Platform, structured outputs(2025年11月閲覧)
https://platform.openai.com/docs/guides/structured-outputs?utm_source=chatgpt.com

実装例:

{
  "model": "gpt-4.1",
  "input": "次のテキストから住所と電話番号を抽出してください: 東京都千代田区丸の内1-1-1 電話: 03-1234-5678",
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "ContactInfo",
      "schema": {
        "type": "object",
        "properties": {
          "address": { "type": "string" },
          "phone": { "type": "string" }
        },
        "required": ["address", "phone"]
      }
    }
  }
}

出力例:

{"address": "東京都千代田区丸の内1-1-1", "phone": "03-1234-5678"}

✅ type、enum、キーワードの指定

出力のブレを抑えるため、typeenum を活用することができます。

参考URL:
OpenAI Platform, Structured outputs – Supported schemas(2025年11月閲覧)
https://platform.openai.com/docs/guides/structured-outputs#supported-schemas


Azure OpenAI ではサポートされるキーワードが限られており、最大ネスト数やプロパティ数にも制限があります(OpenAI: 10階層、Azure: 5階層まで)。

参考URL:
Microsoft Learn, Structured outputs(2025年11月閲覧)
https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/structured-outputs?utm_source=chatgpt.com&tabs=python-secure%2Cdotnet-entra-id&pivots=programming-language-rest#unsupported-type-specific-keywords

ただし、サポートされないキーワードを指定した場合でも、完全に無視されるわけではなく、動作結果が変わることがありました。
例: format: "date" を指定すると、yyyy-MM-dd 形式で返却されました(自然言語で理解できるものは効果があるのではと考えています)。

✅ 意図を明示する

項目の意図をモデルに説明する場合、プロンプトよりもスキーマ自体に制約を書く方が正確です。

例:

"properties": {
  "priority": {
    "type": "string",
    "enum": ["low", "medium", "high"],
    "description": "タスクの優先度。必ずこの3つのいずれか。"
  }
}

ただし、項目についてdescriptionにて説明すると、プロンプトで指定する場合に比べてトークン消費が増えるため、精度が問題なければプロンプトで説明する方が効率的な場合もあります。
(消費トークンの増加については、テキストデータから情報を抽出する機能で使用した際に、40 項目程度の抽出項目があった影響かもしれない)。

✅ 全フィールド必須

全フィールドでキー必須となるので、required に定義した全フィールドを指定する必要があります。
設定しない場合、フィールドの有無に関わらずエラーとなります。

✅ nullを出力する場合

null も許可したい場合は、"type": ["string", "null"] のように型を配列で指定します(anyOfで指定することも可能です)。

{
    "name": "get_weather",
    "description": "Fetches the weather in the given location",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The location to get the weather for"
            },
            "unit": {
                "type": ["string", "null"],
                "description": "The unit to return the temperature in",
                "enum": ["F", "C"]
            }
        },
        "additionalProperties": false,
        "required": [
            "location", "unit"
        ]
    }
}

🧩 構造化出力 機能比較(OpenAI / Claude / Gemini)

項目OpenAIClaude(Anthropic)Gemini(Google)
概要JSON Schemaに厳密準拠した出力を生成できる「Structured Outputs」を公式提供。JSON / XML / カスタムテンプレートで希望の出力形式を指定し、一貫性を高める機能。スキーマ強制は弱い。JSON Schemaに準拠した構造化出力をサポート。APIでスキーマ指定可能。
形式JSON Schema(サブセット)
※使用できる型やキーワード等は公式ドキュメントをご参照ください
JSON / XML / 任意テンプレート形式例示で出力構造を定義JSON Schema(サブセット)
※使用できる型やキーワード等は公式ドキュメントをご参照ください
対応モデルgpt-4o, gpt-4o-mini(Structured Outputs対応)旧: gpt-4-turbo / 3.5-turbo-0613 は限定的Claude 3.5 / 3.7(Opus, Sonnet, Haiku) など。正式なスキーマ制約機能は非明示。Gemini 2.0 Pro / Gemini 2.5 Pro など「Structured outputs: Supported」と明記。

Claude は自由に設定できるが、スキーマ強制力はやや弱めと考えられます。

参考URL:
Claude Docs, 出力の一貫性を高める(JSONモード)(2025年11月閲覧)
https://docs.claude.com/ja/docs/test-and-evaluate/strengthen-guardrails/increase-consistency

参考URL:
Gemini API ドキュメント, 構造化出力(2025年11月閲覧)
https://ai.google.dev/gemini-api/docs/structured-output?hl=ja&example=recipe

役に立ったらシェアしていただけると嬉しいです
  • URLをコピーしました!
  • URLをコピーしました!
目次