Kvmzen ブログ
← 技術の実践に戻る

OpenAI Structured Outputs 完全ガイド:GPT に JSON Schema 準拠の JSON を安定して出力させるには?

技術の実践 ·約 12 分

コードエディタで JSON Schema と構造化データパイプラインを書く様子

モデルの出力をデータベース、フォーム、下流の Agent に接続するなら、「JSON で返してください」とプロンプトに書くだけでは足りません。OpenAI Structured Outputs は制約付きデコードにより、出力を指定した JSON Schema に固定します。フィールドの欠落、enum の捏造、文字列と数値の型ゆれを防げます。抽出、チケット分類、評価スコア、多段ワークフローを実装するエンジニア向けに、JSON Mode から strict: true までの移行手順を一通り解説します。

100%
対応モデルでの Schema 適合率
2
接続口:Chat Completions / Responses
10
オブジェクトの最大ネスト階層

JSON Mode では安定しない理由

JSON Mode(type: "json_object")が保証するのは「JSON パーサーが読めること」だけです。キー名、必須フィールド、enum の集合、数値型までは保証しません。本番でよく起きる失敗は次の 3 つです。severity の欠落、42"42" になること、enum の外に urgent-plus を作り出すことです。下流が強い型でパースすると、ランダムなサンプルでパイプライン全体が止まります。

能力 JSON Mode Structured Outputs
合法な JSON を出力する はい はい
指定した JSON Schema を守る いいえ(プロンプト頼み) はい(制約付きデコード)
有効化の方法 json_object json_schema + strict: true
代表的なモデル 比較的初期の GPT-4o / GPT-3.5 など gpt-4o-2024-08-06、gpt-4o-mini およびそれ以降のスナップショット
拒否応答の扱い JSON に見える拒否文が返ることがある 独立した refusal フィールドを使う

公式は Structured Outputs を JSON Mode の進化と位置づけています。新規プロジェクトでは、パース失敗後に 3 回リトライするのではなく、最初から Schema を使うべきです。モデルごとの Token 課金を比較するなら、Kimi K3 と GPT-5.5 の API コスト比較も参照してください。構造化出力そのものが出力サイズを大きく増やすことはほとんどなく、請求を押し上げるのはリトライと過剰な推論です。

入口は 2 つ、Schema 規則は同じ
Chat Completions では Schema を response_format に置き、Responses API では text.format に置きます。フィールドのパスは異なりますが、strictadditionalProperties: false、および「すべて required」といった制約は共通です。

最小の実行可能なリクエスト

次のチケット抽出 Schema は、本番でよく使う要件の大半をカバーします。文字列、enum、整数、そして null のユニオン型で任意フィールドを表現します。モデルはすべてのキーを返す必要があります。アカウント ID がない場合はキーを省略せず、明示的に null を返します。

Chat Completions · response_format
{
  "model": "gpt-4o-2024-08-06",
  "messages": [
    {"role": "system", "content": "ユーザーの説明からサポートチケットを抽出してください。"},
    {"role": "user", "content": "保存済みカードで決済ページが 500 になります。今日から発生。アカウント 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": { "type": ["string", "null"] }
        },
        "required": ["summary", "category", "severity", "account_id"],
        "additionalProperties": false
      }
    }
  }
}

Responses API では、同じ Schema を text.format に入れ、typenameschemastrict を兄弟フィールドとして並べます。ある Schema を初めて使うときは、サーバー側で制約をコンパイルするため遅延がやや増えることがあります。その後は同一 Schema がキャッシュされ、通常の遅延に戻ります。

ノート PC 上のコードと構造化 API のデバッグ環境
Schema をインターフェイス契約として扱う:まず同じ定義でローカルの単体テストを通し、その後にモデルへ生成を任せる

strict Schema が満たすべき規則

strict: true を有効にすると、提出するのは「緩い JSON Schema」ではなく、公式がサポートするサブセットです。準拠しない場合は「できるだけ守る」ではなく、そのまま 400 になります。

  • ルートは object でなければならない:ルートに anyOf や配列は使えません。Zod の discriminatedUnion がトップレベルの anyOf を生成する場合は、{ "result": ... } のようにオブジェクトで包みます。
  • すべての object に additionalProperties: false が必要:ネストしたオブジェクトも含みます。1 つ漏れると拒否されます。
  • properties に出したキーはすべて required に入れる:任意の意味は "type": ["string", "null"] か、anyOfnull を加えて表現します。
  • サポートされる型:string、number、integer、boolean、object、array、enum、anyOf
  • 文字列の制約patternformatemaildate-timeuuidipv4 など)。
  • 数値と配列minimum / maximum / multipleOfminItems / maxItems
  • 明示的に未サポートallOfnotif/then/elsedependentRequired などの組み合わせキーワード。
  • 規模の上限:オブジェクト属性は合計で約 5000、ネストは 10 階層まで。属性名、定義名、enum 値の文字列合計は 12 万文字以内。enum 値の総数は最大 1000。
ファインチューン済みモデルはさらに厳しい
ファインチューン済みモデルでは、patternformatminLengthminimumminItems などの制約が現時点でも未対応のことがあります。まず基盤スナップショットで Schema を検証してから、ファインチューンの要否を判断してください。

ツール呼び出しと返信本文

同じ strict は関数 / ツール引数にも使えます。違いは意図です。ツールの Schema は「モデルが何を呼び出すか」を記述し、response_format / text.format は「ユーザーへ返す形」を記述します。抽出、採点、UI 状態の生成は後者、天気の照会、ファイル書き込み、コマンド実行は前者です。ターミナル Agent と GUI 操作の能力を比較するなら、Claude Code と OpenAI Computer Use の関係を参照してください。あちらは「どう行動するか」の層で、本稿が扱うのは「行動の前後にあるデータ契約」です。

SDK でオブジェクトへ直接パースする

JSON Schema を手書きすると required を忘れがちです。公式 SDK には Pydantic / Zod のヘルパーがあり、型から Schema を生成し、応答側ではパース済みオブジェクトを直接受け取れます。以下は Python の典型的な書き方です。

Python · Pydantic
from typing import Literal, Optional
from pydantic import BaseModel
from openai import OpenAI

class SupportTicket(BaseModel):
    summary: str
    category: Literal["billing", "bug", "account", "other"]
    severity: int
    account_id: Optional[str]

client = OpenAI()
completion = client.beta.chat.completions.parse(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "system", "content": "チケットを抽出してください。"},
        {"role": "user", "content": "決済ページが 500、アカウント acct_8842。"},
    ],
    response_format=SupportTicket,
)
ticket = completion.choices[0].message.parsed
if completion.choices[0].message.refusal:
    raise RuntimeError(completion.choices[0].message.refusal)
print(ticket.category, ticket.severity)
Schema を単一の事実源にする
同じ Pydantic / Zod モデルを API リクエスト、単体テスト、下流の検証に共用します。プロンプトにフィールド一覧を再掲しないでください。ドキュメントがコードより先に古くなります。

エラー、拒否応答、実装チェックリスト

本番投入前にこのリストを通せば、「ローカルの curl は通るのにパイプラインだけ 400」という問題の大半を消せます。

  • 400 Unsupported schemaadditionalProperties: false の欠落、required でないフィールド、ルートが anyOfallOf の使用。
  • モデルの拒否:安全ポリシーが発動すると、内容は Schema に入りません。refusal を読み、UI に表示してください。JSON パース失敗としてリトライしないでください。
  • 任意フィールドが空文字になる:契約に null と書いたなら null を受け取ります。DB 投入前に SQL の NULL へ写し、推測で埋めないでください。
  • enum が広すぎる:業務状態は 5〜8 個に絞ります。数百個の enum はコンテキストを消費し、1000 件の上限にも近づきます。
  • キーの順序:出力キーの順は Schema の宣言順と一致します。ストリーミング受信時はフィールド単位で増分パースできます。
  • プロンプトは依然として有効:Schema は形、プロンプトは意味を担います。「severity は 1〜5、5 は全サイト障害」は system に書き、整数範囲は minimum / maximum でさらに固定できます。

運用では次の 3 点を固定することを推奨します。Schema を Git に入れること。拒否、400、タイムアウトを別々に計測すること。玩具入力ではなく、実チケットで回帰サンプルを持つことです。パース層が安定して初めて、分類精度と遅延に集中でき、毎週正規表現を直す作業から離れられます。

Mac mini で抽出パイプラインを回すと、環境の手間が減ります

Structured Outputs のデバッグは短い循環です。Schema を直し、少量のサンプルを流し、パース済みオブジェクトを確認します。macOS では Python、Node.js、Docker、Homebrew がそのまま使え、先に WSL を組む必要がありません。Apple Silicon のユニファイドメモリなら、エディタ、ローカル検証スクリプト、長コンテキスト評価を同時に開いても、型チェックの最中にマシンが swap へ落ちにくくなります。

Mac mini M4 の待機電力はおよそ 4W で、回帰サンプルを一晩走らせる用途に向きます。macOS はクラッシュ率が低く、Gatekeeper と SIP により依存スクリプトが汚染されるリスクも下がります。同価格帯の Windows 機と比べ、無人で Agent 評価を回し続ける場合は安定性と電気代の両方で有利です。

JSON 回帰とモデル比較を常時動かすノードが必要なら、Kvmzen のプランを確認すると、Schema 契約をノート PC から固定スペックのクラウド Mac へ移せます。

期間限定オファー

1 台の Mac を超えた、クラウド上のあなたの開発基地

専有算力 · グローバルノード · 月額サブスクリプション · ハードウェア不要

ホームに戻る
期間限定オファー プランを見る