
Structured Output とは、LLM の出力を JSON Schema などの厳密なスキーマに従って強制的に構造化する技術です。
自由テキストで返ってくる LLM の応答をそのままビジネスシステムに渡すと、パース失敗や予期しないフィールド欠損が頻発します。Structured Output を使えば、OpenAI API の response_format に JSON Schema を指定するだけで、業務システムが直接読み込める形式の応答を安定して取得できます。
本記事は、LLM と自社システムを直結したいエンジニア・アーキテクトを対象としています。JSON Schema の設計方針から TypeScript による実装・バリデーション・エラーハンドリング、さらに ERP や非同期キューとの連携パターンまで、実運用に必要な全工程を順を追って解説します。
LLMに「注文内容をJSON形式で返して」と指示しても、ある日は{"item": "コーヒー"}が返り、別の日は「注文内容はコーヒーです。」という文章が返ってくる——そんな経験はないでしょうか。
自由テキスト出力をそのまま業務システムに渡すと、パース失敗や予期しないデータ欠損が発生します。プロンプトで形式を指定しても、LLMは確率的に動作するため出力の一貫性を保証できません。結果として、受け取り側のシステムに複雑な前処理や例外処理を積み上げることになり、連携コストが膨らんでいきます。
Structured Outputはこの問題を根本から解消する仕組みです。モデルの出力をスキーマに強制的に準拠させることで、アプリケーション側は「形式が崩れているかもしれない」という前提を捨てられます。出力形式の安定性が担保されて初めて、LLMを業務システムへ直接組み込む設計が現実的になります。
LLM を業務システムに組み込む初期段階では、「プロンプトで『JSON で返して』と指示すれば十分」と考えがちです。しかし実際には、自由テキスト出力はシステム連携において深刻な不安定要因になります。
従来の自由テキスト出力では、パース処理は「正規表現で拾えれば成功、拾えなければ失敗」という不安定な賭けになりがちでした。Structured Output を使うと、モデルは JSON Schema の型制約に従って出力を生成するため、アプリケーション側のパース処理は大幅に安定します。
安定性が高まる理由は、出力の形状が事前に保証されている点にあります。string / number / boolean などの型が Schema レベルで強制されるため型キャストエラーが起きにくく、required フィールドに指定したキーは必ず出力に含まれます。また、配列・オブジェクトの深さが Schema 通りに固定されるため、予期しない構造変化も起きません。
条件分岐の観点から言えば、出力が単一の固定スキーマに収まる場合は strict: true を設定してパース処理をシンプルに保つのが有効です。一方、複数の出力パターンが想定される場合は oneOf や anyOf を使って Schema 側で分岐を定義し、受け取り側のコードで type フィールドを判定する設計が安全です。
パース処理が安定することで、下流の業務ロジックに与える影響も変わります。
ERP や業務システムと LLM を接続しようとしたとき、「LLM の出力を受け取るためだけに、なぜこれほど多くのパース処理を書かなければならないのか」と感じた経験はないでしょうか。
Structured Output は、この接続コストを構造的に削減します。仕組みのポイントは以下の 3 点です。
たとえば受発注データを LLM で抽出する場合、従来は「品番」「数量」「納期」をテキストから抜き出す独自ロジックが必要でした。Structured Output を使えば、これらのフィールドをスキーマに定義するだけで LLM が整形済みの JSON を返します。ERP 側は受け取った JSON をそのまま API エンドポイントへ POST できるため、中間変換レイヤーの開発コストを大幅に抑えられます。

結論: 実装の成否はモデル選定・スキーマ設計・開発環境の三点が左右する。事前の準備不足が後工程の手戻りを招く。
Structured Output の実装を始める前に、対応モデルの確認・JSON Schema の設計方針・依存ライブラリの整備という三つの準備が必要です。各ステップを順に解説します。
最初は「最新モデルならどれでも Structured Outputs が使える」と考えがちですが、実際にはモデルとバージョンの組み合わせによってサポート状況が異なるため、事前確認が欠かせません。
OpenAI の Structured Outputs(json_schema 方式)は、GPT-4o や GPT-4 Turbo、および gpt-3.5-turbo-1106 以降のモデルで利用できると公式に明示されています。旧来の json_object 方式は非推奨とされており、新規実装では json_schema 方式を選ぶのが基本方針です。
スキーマ設計の良し悪しが、モデルの追従率を大きく左右します。シンプルで明確なスキーマほど、LLM は指示に忠実に従う傾向があります。
設計の基本方針
string だけでなく enum や format(例: "format": "date")を活用し、受け入れ可能な値の範囲を明示しますdescription を省略しない: 各フィールドに description を付けることで、モデルが意図を正しく解釈しやすくなります型定義のケースバイケース判断
フィールドの必須・任意の扱いは用途によって使い分けが必要です。業務システムへの書き込みに使うフィールドは required に含め、補足情報や将来拡張用のフィールドはオプショナルとして nullable で定義するのが基本方針です。一方、LLM に値の有無を判断させたい場合は null を許容しつつ、バリデーション層で後処理することを前提に設計します。
「どのライブラリを入れれば最速で動くのか」——実装に着手する前に、この問いに答えを出しておくことが後の手戻りを防ぎます。
TypeScript を使う場合、最小構成は以下の通りです。
response_format による構造化出力をネイティブサポートzodResponseFormat ユーティリティ: Zod スキーマを JSON Schema に自動変換インストールコマンドの例は次の通りです。
1npm install openai zod zod-to-json-schemaNode.js のバージョンは LTS 版(18 以上)を推奨します。fetch のネイティブサポートが含まれており、SDK の依存関係が安定します。
環境変数の管理には dotenv を併用し、OPENAI_API_KEY をコードに直書きしないことが基本です。CI/CD パイプラインでは GitHub Actions の Secrets や Azure Key Vault などのシークレット管理サービスに委ねる構成が推奨されます。

結論: スキーマ設計・API パラメータ設定・バリデーションの 3 ステップで実装できる。
Structured Output の実装は、スキーマ定義・API への組み込み・レスポンス検証の順に進めます。各ステップを正しく踏むことで、LLM の出力をそのまま業務システムへ渡せる安定したパイプラインを構築できます。
スキーマ設計の第一歩で多くの実装者がつまずくのは「後から拡張すればいい」という考え方です。実際には、初期設計の段階で型の厳密さとフィールド構成を固めておかないと、下流のシステム連携が壊れやすくなります。
スキーマ定義の基本方針
まず出力に必要なフィールドを洗い出し、それぞれに適切な型を割り当てます。
"type": "string"、数値は "type": "number" または "integer" で明示する"enum" を使い、モデルが任意文字列を返せないよう制約するrequired 配列にすべての必須フィールドを列挙し、オプショナルフィールドは意図的に分離するたとえば受発注データを取得する場合、order_id(string)、amount(number)、status(enum: "pending" / "confirmed" / "cancelled")という最小構成から始めると、モデルの従順性が高まります。
プロンプト設計との連携
スキーマ定義と並行して、システムプロンプトにも構造の意図を明示することが重要です。「JSON のみで返答してください」という指示だけでは不十分で、「以下のフィールドに従い、指定された型で出力してください」と具体的な期待値を示す文言を加えると、モデルがスキーマに沿いやすくなります。
スキーマ定義が完成したら、次は API 呼び出し時のパラメータ設定です。OpenAI API では response_format に {"type": "json_schema", "json_schema": {...}} を渡すことで Structured Outputs が有効になります。旧来の "json_object" 方式は非推奨となっているため、新規実装では "json_schema" 方式を選択してください。
TypeScript での基本的な設定は以下の通りです。
1const response = await openai.chat.completions.create({
2 model: "gpt-4o",
3 response_format: {
4 type: "json_schema",
5 json_schema: {
6 name: "invoice_data",
7 strict: true,
8 schema: invoiceSchema, // 前ステップで定義したスキーマ
9 },
10 },
11 messages: [{ role: "user", content: prompt }],
12});strict フラグはデフォルトで false ですが、業務システム連携では true に設定することを推奨します。
API が JSON を返してきたとき、「スキーマ通りのはずなのに、なぜかパースで落ちる」という経験は現場でよく聞かれます。Structured Outputs を有効にしていても、ネットワーク断やモデルのコンテキスト超過によって不完全な JSON が返るケースがあるため、バリデーション層は必須です。
レスポンス処理は以下の 3 段階で実装するのが堅実です。
JSON.parse() を try-catch で囲み、構文エラーを最初に捕捉するTypeScript + Zod を使った実装例を示します。
1import { z } from "zod";
2
3const OrderSchema = z.object({
4 orderId: z.
結論: 構造化出力を得た後は、ERP への自動投入・非同期キュー・型安全なコード生成という三つの経路でシステムと直結できる。
スキーマに従った JSON を受け取ったら、次は実際の業務システムへどう流し込むかが課題になります。REST API 連携・非同期処理・フロントエンドとの型共有、それぞれの実装パターンを順に解説します。
ERP への自動投入を「まず試しに POST を投げればいい」と考えがちですが、実際は Structured Output によるスキーマ検証を挟んでから送信するほうが、ダウンストリームのロールバック処理をほぼゼロに近づけられます。
LLM が返した JSON を ERP の REST API エンドポイントへ直接渡す場合、以下の流れを標準化することが重要です。
Idempotency-Key)を付与するTypeScript での実装例を簡略化すると、次のようになります。
大量のドキュメントを一括で LLM に投げる場合、同期 API 呼び出しをそのまま並列実行するとレート制限エラーが頻発し、処理全体が不安定になりやすいです。非同期キューを挟むことで、スループットと信頼性を両立できます。
処理件数が少量(数十件程度)の場合は Promise.allSettled による並列実行で十分ですが、数百件以上のバッチ処理では BullMQ や AWS SQS などのメッセージキューを介したワーカー分散が適しています。
バックエンドで Structured Output を実装したのに、フロントエンド側の型定義と乖離してランタイムエラーが起きる——そんな経験はないでしょうか。この問題は「型の単一ソース」を JSON Schema に集約することで解消できます。
型生成の基本フローは次のとおりです。
json-schema-to-typescript(json2ts)などのツールで TypeScript の型定義(.d.ts)を自動生成するこれにより、スキーマを変更すると型定義も自動で更新され、コンパイル時に不整合を検出できます。
実装例のポイントを整理します。
1// 自動生成された型をインポート
2import type { InvoiceOutput } from "@shared/types/invoice";
3
4// APIレスポンスをそのままキャスト可能
5const data = response.data as InvoiceOutput;LLM のレスポンスは strict: true で取得しているため、スキーマ外のフィールドは含まれません。フロントエンドは余分なバリデーションコードを書かずに済みます。

結論: 実装後に発生しやすい失敗は、スキーマ設計・null 処理・バージョン管理の 3 点に集中する。
Structured Output の導入後も、スキーマ起因のトラブルは起きやすいです。以下の H3 では代表的な失敗パターンと回避策を整理します。
スキーマを詳細に設計すれば精度が上がると考えがちですが、実際にはスキーマが複雑になるほどモデルが従えないケースが増える傾向があります。
典型的な失敗例として、次のような構造が挙げられます。
oneOf / anyOf を多用した複合型の分岐このような複雑なスキーマを与えると、モデルは必須フィールドを省略したり、型を誤って解釈したりする出力を返すことがあります。OpenAI の Structured Outputs は JSON Schema のサブセットをサポートしていますが、スキーマが大きくなるほどトークン消費も増え、コンテキスト内での「スキーマ理解」に割り当てられる処理能力が圧迫されます。
回避策は「分割と段階化」です。
$defs を使って再利用可能なサブスキーマを定義し、見通しを良くするまた、strict: true を設定している場合は、スキーマに含まれるすべてのフィールドを required に列挙する必要があります。オプショナルなフィールドを required から外したままにすると、バリデーションエラーの原因になるため注意が必要です(詳細は次のセクションで解説します)。
オプショナルフィールドの扱いは、一見シンプルに見えて実装上の落とし穴になりやすい箇所です。
よくある誤りは、JSON Schema でフィールドを required 配列に含めずに定義した場合、モデルがそのフィールドを完全に省略することがあると想定していないケースです。受け取り側のコードが response.discount_rate に直接アクセスすると、フィールド自体が存在しないため undefined エラーや null 参照例外が発生します。
ここで注意が必要なのは、フィールドが省略される場合と null で返る場合とで挙動が異なる点です。省略された場合はキー自体がオブジェクトに存在せず、null で返る場合はキーは存在するが値が空という違いがあるため、バリデーションロジックを分けて記述する必要があります。
失敗パターンとして頻出するのは、オプショナルフィールドをスキーマに定義するだけで型を ["string", "null"] のように null 許容にしていないケースです。モデルが null を返した瞬間にバリデーションエラーになります。また、受け取り側で ?? や ?. によるデフォルト値処理をせず直接参照しているコードも、キー欠落時にランタイムエラーを引き起こします。回避策はシンプルで、strict: true を設定して全フィールドを required に含めたうえで "type": ["string", "null"] と明示することです。これにより省略と null の両方を意図的に制御できるようになります。
「先週まで動いていたのに、今日から突然パースエラーが出る」——こうした問い合わせは、スキーマのバージョン管理が整備されていない現場で頻繁に発生します。
JSON Schema を更新する際、フィールドの追加は後方互換性を保ちやすい一方、フィールドの削除・型変更・必須フラグの変更は破壊的変更(Breaking Change)になります。LLM 側のプロンプトやスキーマ定義を変更した瞬間に、既存の受信側コードが壊れるリスクがあります。
主な破壊的変更のパターン
string 型だったフィールドを number 型に変更するrequired に昇格させるprice → unit_price)推奨する管理方法
version フィールドを設けて、受信側がバージョンを確認してから処理を分岐できるようにする
Q1. Structured Output はファインチューニングなしでもどのモデルでも使えますか?
すべてのモデルで利用できるわけではありません。OpenAI の場合、response_format に json_schema を指定する Structured Outputs は GPT-4o や GPT(GPT-4 Turbo 系)、gpt-3.5-turbo-1106 以降などの対応モデルでのみ有効です。ファインチューニングは不要ですが、モデルのバージョンによってサポート状況が異なるため、利用前に公式ドキュメントで対応モデル一覧を確認することを推奨します。オープンソース系のモデルでは、同等の機能を提供するかどうかはモデルごとに異なります。
Q2. strict: true と strict: false はどう使い分ければよいですか?
strict: true を設定すると、モデルは定義したスキーマに厳密に従った出力を生成します。業務システムへの自動データ投入など、パース失敗が許されない用途では strict: true が適切です。一方、strict: false(デフォルト)はスキーマへの準拠が緩やかになるため、プロトタイプや試験段階での柔軟な探索に向いています。本番環境では原則として strict: true を選択し、スキーマ設計を十分に検証してから運用に移すことが望ましいです。
Q3. JSON 以外のフォーマット(YAML・XML)にも Structured Output の考え方を適用できますか?
OpenAI の Structured Outputs 機能は JSON Schema を前提として設計されているため、YAML や XML をそのまま強制出力する仕組みは提供されていません。ただし、LLM に YAML や XML のテンプレートをプロンプトで提示し、出力後にパーサーで検証するアプローチは取れます。より確実な方法としては、いったん JSON で構造化出力を受け取り、アプリケーション層で YAML・XML に変換するパイプラインを組む設計が安定性の面で優れています。
Q4. スキーマを変更するとき、既存の連携システムへの影響をどう最小化できますか?
フィールドの追加は後方互換性を保ちやすいですが、フィールドの削除や型変更は破壊的変更になる可能性があります。スキーマには $schema バージョンフィールドを含め、変更時はバージョン番号をインクリメントする運用が有効です。また、新旧スキーマを並行稼働させる移行期間を設け、下流システムが新バージョンへ対応したことを確認してから旧バージョンを廃止する段階的移行が、システム連携の安定性を保つうえで推奨されます。
Q5. Azure OpenAI でも同じ実装方法が使えますか?
Azure OpenAI の Structured Outputs は OpenAI と同じ JSON Schema のサブセットをサポートしており、response_format の指定方法も基本的に同一です。エンドポイントや認証方式は Azure 固有のものになりますが、スキーマ設計やバリデーションロジックはそのまま流用できます。ただし、対応モデルのデプロイ状況やリージョンごとの機能提供タイミングが異なる場合があるため、Azure のドキュメントで最新の対応状況を確認することを推奨します。
「すべてのモデルで使えるはず」と考えがちですが、実際には 対応モデルの確認が先決 です。Structured Outputs(JSON Schema 強制出力)はモデルのアーキテクチャとトレーニングに依存するため、任意のモデルに適用できるわけではありません。
対応状況の整理
response_format: { type: "json_schema" } 方式は、GPT-4o・GPT-4 Turbo・gpt-3.5-turbo-1106 以降のモデルで利用可能とされています"json_object" 方式は非推奨となっており、"json_schema" 方式への移行が推奨されていますファインチューニングの要否
ファインチューニングは 原則不要 です。Structured Outputs はプロンプト設計と response_format パラメータの設定だけで動作します。ただし、以下の点に注意が必要です。
結論から言えば、OpenAI の Structured Outputs は JSON を前提とした仕様であり、YAML や XML を直接スキーマで強制することはできません。
ただし、用途によって現実的な対応策は異なります。
js-yaml 等のライブラリを使って YAML に変換するアプローチが安定しています。スキーマバリデーションは JSON の段階で完結するため、型安全性を損なわずに済みます。fast-xml-parser 等)をパイプラインに挟む方法が現実的です。ただし XML の属性構造など JSON では表現しにくい要素がある場合は、スキーマ設計の段階で変換後の構造を意識した型定義が必要です。プロンプトで「YAML 形式で出力してください」と指示する方法も技術的には可能ですが、この場合は JSON Schema による強制が働かないため、出力の安定性が大きく低下します。業務システム連携では推奨できません。
出力先システムが YAML・XML を要求する場合は JSON で受け取って変換、LLM 出力の信頼性を最優先するなら JSON Schema 強制出力を維持する、という判断軸を持つとよいでしょう。
Chi
ラオス国立大学で情報科学を専攻し、在学中は統計ソフトウェアの開発に従事。データ分析とプログラミングの基礎を実践的に培った。2021 年より Web・アプリケーション開発の道に進み、2023 年からはフロントエンドとバックエンドの両領域で本格的な開発経験を積む。当社では AI を活用した Web サービスの設計・開発を担当し、自然言語処理(NLP)、機械学習、生成 AI・大規模言語モデル(LLM)を業務システムに統合するプロジェクトに携わる。最新技術のキャッチアップに貪欲で、技術検証から本番実装までのスピード感を大切にしている。