このガイドを読み終えるまでに、独自のコードからOpenAIの構造化出力を呼び出せるようになります。モデルにJSONスキーマを渡し、strict: trueを設定すると、要求した形式に確実に一致する応答が返されます。最初のリクエストを送信し、応答を読み取り、エッジケースを処理し、ペイロードが実際に準拠していることをアサートするAPIテストコレクションをApidogで生成します。
始める前に必要なもの
構造化出力は、モデルの生成を制約し、指定したJSONスキーマに準拠する出力を生成します。strict: trueを設定したスキーマを渡すと、モデルはそれを侵害するフィールドを出力できなくなります。必要なキーはすべて存在し、すべての型が一致し、すべてのenum値が指定されたもののいずれかになります。これにより、防御的なパースコードを書く必要がなくなり、ペイロードを信頼できるようになります。
これは、代替手段に比べて大きな進歩です。「JSONのみで応答してください」といった自由形式のプロンプトは、機能しなくなるまでしか使えません。思考の逸脱が一度でもあれば、オブジェクトの周りに散文がラップされたり、整数を期待していた場所に日付が返されたりします。構造化出力は、期待を込めた指示を、デコード時に強制される制約へと移行させます。
このガイドを進めるには、次のものが必要です。
OPENAI_API_KEYとして設定されたOpenAI APIキー。- 厳格なスキーマ強制をサポートするモデル(どのモデルかは後述)。
- 目的の形式を記述するJSONスキーマ。
適切なモデルを選択する
構造化出力は、OpenAIの最近のモデル、GPT-4oファミリーからGPT-5シリーズに至るまで利用可能です。OpenAIのドキュメントでは、現在、最新の主力モデル(執筆時点ではgpt-5.5)で新しいプロジェクトを開始することを推奨しています。古いモデルやgpt-3.5時代のモデルはJSONモードをサポートしていますが、厳格なスキーマ強制はサポートしていません。strict: trueに依存する場合は、モデルのバージョンによってサポート状況が異なり、OpenAIがこれらを更新していくため、出荷前に特定のモデルIDがそれをサポートしていることを確認してください。
OpenAIは混同しやすい2つの関連機能をリリースしているため、実際にどの機能を使いたいのかを理解しておくことが役立ちます。
JSONモード (response_format: { "type": "json_object" }) は、出力が構文的に有効なJSONであることを保証します。それだけです。フィールド、型、必須キーについては認識しません。依然として、自分で形式を検証する必要があります。
構造化出力 (type: "json_schema"とstrict: trueを含むresponse_format) は、出力が有効なJSONであると同時に、スキーマに一致することを保証します。OpenAIは、構造化出力をJSONモードの進化版と説明しています。どちらも有効なJSONを生成しますが、スキーマへの準拠を強制するのは構造化出力だけです。新しい作業には、構造化出力が望ましいです。
| JSONモード | 構造化出力(厳格) | |
|---|---|---|
| パラメータ | response_format: {"type":"json_object"} |
type: "json_schema"とstrict: trueを含むresponse_format |
| 有効なJSON | はい | はい |
| スキーマに一致 | いいえ | はい |
| 必須フィールドの強制 | いいえ | はい |
| 型とenumの強制 | いいえ | はい |
| 下流での検証 | 常に必要 | 推奨(下記参照) |
APIに関する注意点:Chat Completionsエンドポイントは上記のようにresponse_formatを使用します。新しいResponses APIでは、text.formatの下でtype: "json_schema"を使用して同じことを表現します。スキーマのルールは同じで、ラッパーのみが異なります。呼び出すエンドポイントの正確なフィールドパスについては、現在のドキュメントを確認してください。
最初のリクエストを行う
サポートチケットを型付きレコードに抽出するとします。厳格なスキーマを持つChat Completionsリクエストの例を以下に示します。
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"messages": [
{ "role": "system", "content": "Extract the ticket into the schema." },
{ "role": "user", "content": "My checkout 500s every time I use a saved card. Started today. 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文字列をcontentとするメッセージを返します。例:
{
"summary": "Checkout returns HTTP 500 when paying with a saved card",
"category": "bug",
"severity": 3,
"account_id": "acct_8842"
}
account_idがnullを伴うanyOfを使用していることに注目してください。これはオプションのフィールドをモデル化する方法であり、独自のスキーマを作成する際に従うべきルールへと直接つながります。
スキーマのサブセットに留まる
構造化出力は、JSONスキーマのサブセットを受け入れます。このサブセットが存在するのは、OpenAIが制約を確実に適用し、コンパイルされたスキーマをキャッシュできるようにするためです。記憶しておくべきルールは次のとおりです。
- ルートはオブジェクトである必要があります。 トップレベルを配列や単なる文字列にすることはできません。リストはオブジェクトのプロパティ内にラップしてください。
- すべてのプロパティは
requiredにリストされている必要があります。 通常の意味での「オプション」はありません。フィールドをnull許容にするには、上記の例のようにnull型とともにanyOfを与えます。 - すべてのオブジェクトで
additionalPropertiesはfalseである必要があります。 これにより、モデルが余分なキーを生成するのを防ぎます。 - サイズ制限が適用されます。 スキーマは、最大約100個のオブジェクトプロパティと最大5レベルのネストを保持できます。深くネストされたスキーマや広範囲にわたるスキーマは拒否されるため、可能な限りフラットにしてください。
- 一部のキーワードは強制されません。
pattern、format、minLength、minimumなどの検証専用キーワードは、モデルによって保証されません。正規表現や数値範囲が尊重される必要がある場合は、応答が到着した後に自分で確認する必要があります。 - 初回呼び出しの遅延。 新しいスキーマでの最初のリクエストは、コンパイルに時間がかかります(通常は数秒、複雑なスキーマの場合は最大1分かかることもあります)。その後はキャッシュされ、高速になります。
検証キーワードは強制されないため、「保証されたJSON」は「保証されたビジネスロジック的に有効」を意味しません。構造は固定されますが、内部の値は依然としてテストに値します。oneOf/anyOf/allOfでオプションまたはユニオンフィールドをモデル化した経験があるなら、そのメンタルモデルはなじみ深いでしょう。スキーマは形式を制約しますが、実際の値は別途アサートします。
拒否と切り捨てを処理する
出力が意図的にスキーマに一致しないケースが1つあります。モデルが安全でないリクエストを拒否した場合、スキーマに沿ったコンテンツの代わりに、メッセージにrefusalフィールドが返されます。パースする前に、コードでこれを分岐処理する必要があります。
msg = response.choices[0].message
if msg.refusal:
handle_refusal(msg.refusal)
else:
ticket = json.loads(msg.content)
拒否はプログラムで検出可能になり、謝罪のためにテキストをスキャンするよりもクリーンです。応答がスキーマに満たない可能性のある他の2つの方法としては、オブジェクトの途中でmax_tokensに達する(JSONが切り捨てられる)こと、または構造化出力がサポートしない並列ツール呼び出しを使用することです。これらを組み合わせる場合は、parallel_tool_callsをfalseに設定してください。
Apidogでテストする方法
厳格モードは、生成時にスキーマを強制します。しかし、テストの必要がなくなるわけではありません。モデルが変更されたり、スキーマがずれたり、チームメイトがrequired配列を編集したり、拒否パスが変わったりする可能性があります。応答が契約に合致しなくなったときに、明確に失敗するテストが必要です。そこにApidogが役立ちます。

役割分担を正確に言うと、OpenAIの厳格モードはスキーマに準拠したJSONを生成するものです。Apidogはモデル側でスキーマを強制するわけではありません。Apidogが行うのは、受け取った応答を期待するスキーマに対して検証することであり、これにより本番環境ではなくCIでずれを検出できます。
ワークフローは次のとおりです。
- リクエストを送信します。 Apidogで
response_formatブロックを使用してChat Completions呼び出しを構築します。繰り返し実行できるようにコレクションに保存します。 - 形式をアサートします。 Apidogで応答アサーションを追加します。
categoryがenum値の1つであること、severityが整数であること、account_idが文字列またはnullであることを確認します。ApidogはJSONスキーマに対して応答を検証できるため、正確なスキーマをアタッチし、ペイロードが逸脱した場合に実行を失敗させることができます。 - CIで実行します。 コレクションをパイプラインに組み込み、モデルやプロンプトの変更があるたびに準拠性を再確認するようにします。静かなスキーマの破損は、ビルド失敗につながります。
- 契約をモックします。 実際のリクエストが存在する前、またはトークンを消費せずに下流のコンシューマをテストするために、スキーマに準拠したサンプル応答を返すモックAPIを立ち上げます。統合が固まる間、フロントエンドとテストは安定した形式に対して実行されます。
最後の点は過小評価されがちです。構造化出力を消費するすべてのものを、同じスキーマを尊重するモックに対して開発・テストし、準備ができたら実際のOpenAI呼び出しに置き換えることができます。Apidogをダウンロードすると、リクエスト、アサーション、モックをすべて一箇所で構築できます。
よくある質問
- 構造化出力が存在する今、JSONモードは非推奨ですか? JSONモードは引き続き機能し、有効なJSONを保証します。スキーマを強制しないだけです。新しいコードには、
strict: trueを使用した構造化出力がより強力な選択肢です。厳格なスキーマをサポートしないモデルの場合、または固定された形式が本当にない場合にのみ、通常のJSONモードを使用してください。 - スキーマのルートは配列にできますか? いいえ。トップレベルはオブジェクトである必要があります。リストは
{ "items": [...] }のようにプロパティにラップし、itemsをrequiredに含めてください。これは初日に多くの人を戸惑わせる点です。 - フィールドをオプションにするにはどうすればよいですか? 構造化出力では、すべてのプロパティが
requiredに含まれている必要があるため、従来のオプションフィールドはありません。「欠落」をnull許容としてモデル化します。つまり、string(または任意の型)とnullをanyOfで使用します。キーは常に存在し、その値はnullになる可能性があります。 - 厳格モードは、検証を完全にスキップできることを意味しますか? 構造は保証されるため、形式のチェックをスキップできます。しかし、
pattern、format、数値範囲などのキーワードはモデルによって強制されず、拒否や切り捨てによって仕様外の応答が生成される可能性があります。適合性テストは依然としてその価値があります。この形式に慣れていない場合は、このJSONスキーマ入門が基本的な構成要素を網羅しており、デプロイごとにチェックを実行できます。 - どのモデルを使用すべきですか? 構造化出力は、GPT-4o以降のモデル、GPT-5シリーズを含むすべてで動作します。OpenAIのドキュメントでは、新しいプロジェクトには現在の主力モデルを使用することを推奨しています。サポートはモデルのバージョンに紐付いているため、依存する前に正確なモデルIDが厳格モードをサポートしていることを確認してください。
まとめ
これで、一連の流れがすべて揃いました。厳格モードをサポートするモデルを選択し、json_schemaとstrict: trueを指定してChat Completionsリクエストを送信し、サポートされるサブセット内にスキーマを保持し、refusalで分岐処理を行い、値レベルのルールは依然としてチェックする必要があることを忘れないでください。そして、テストでそれを証明します。Apidogでリクエストを構築し、スキーマに対して応答をアサートし、統合が安定するまでスタックの残りの部分が進行できるようにモックします。モデルは形式を約束しますが、テストがそれが維持されていることを証明します。
