AIはJSON Schemaに合致するJSONをどう生成するか:PromptからStructured Outputまで

プロンプトのみのJSON、JSON Modeから各社Structured Outputsまで。JSON Schemaによる出力制約、OpenAI / Gemini / Anthropic比較、検証パイプライン、Tool Callingとの役割分担。

このシリーズの以前の投稿で取り上げたものエージェントに JSON が必要な理由(ツール呼び出しからMCP データフロー)、なぜ JSON スキーマ⟧ がインフラストラクチャになったのか(スキーマ、関数歌声、およびMCPの進化)、 そしてGemini 固有の構造化出力 構成(Gemini API ガイド)。

この記事は縮小表示されます。どのモデル API を使用しているかに関係なく、「JSON で返信してください」から「出力はこの JSON スキーマ⟧ と一致する必要があります」にどのように移行しますか??ベンダーは、2024 年から 2026 年にかけて、構造化出力s⟧ / JSON スキーマ⟧ モードのような名前でこれに集中しました。同じアイデア、わずかに異なるフィールド名、スキーマのサブセット、ツール呼び出し との境界です。

4 つのレベル、それぞれが最後のレベルよりも「厳格」です

チームは通常、モデルから JSON を取得するために 4 つのアプローチを使用します。信頼性は桁違いに異なります。

レベルアプローチあなたがコントロールするもの典型的な失敗
L0プロンプトのみ: 「出力 JSON」ソフト制約```json fences, prose, single quotes, trailing commas
L1プロンプト + いくつかのショット JSON の例例によって形を作り、厳密なルールはありませんフィールド名のずれ、フィールドの欠落、タイプの混合
L2JSON Mode⟧ (response_format: json_object, etc.)出力は有効である必要があります JSONParses, but price may be a string
L3構造化された出力+ JSON スキーマ⟧フィールド、型、列挙型、必須意味上の幻覚、切り捨て、無視されたキーワード

For production extraction, classification, or form filling, aim for L3. L0–L1 suit exploration; L2 when shape varies and you only need JSON.parse. L3 is the contract programs can consume directly.

JSON スキーマ⟧ が制御するものと制御しないもの

JSON スキーマ⟧ドキュメントの構造: フィールド、型、必須キー、列挙型、範囲、配列項目の形状について説明します。ベンダー 構造化出力s⟧ は、プロンプトに貼り付けるだけでなく、そのスキーマをコンパイルして生成します。

Schema can enforce: syntax shape (object / array / string / integer), required, enum, minimum / maximum, additionalProperties: false, nested objects and arrays.

Schema cannot enforce business correctness. Example: “total_cents must equal sum of line items” — assert that in code after Schema validation. Schema also does not fact-check: a well-typed fabricated invoice number is still hallucination.

Tool inputSchema uses the same language; Structured Output constrains the final reply, Tool Calling constrains tool arguments. See データフローガイド.

制約付きデコード: Schema が Prompt に勝る理由

プロンプトは遵守の可能性を高めるだけです。 構造化された出力 を使用します制約付きデコード: 各トークンで、デコーダーは、JSON 構文を壊したり、スキーマに違反したりするトークンを抑制します。

通常、正規表現を削除するマークダウン フェンスを使用せずに、解析可能で形状が正しい JSON が得られます。実装は異なります (FSM、文法、ロジット マスク) が、開発者契約は同じです。プロンプトだけでなくスキーマを API に渡します.

制約付きデコード保証構造、 ないセマンティクス。常に同じスキーマを使用して再検証し、本番環境にビジネス ルールを追加します。

OpenAI、Gemini、Anthropicの比較

同じ概念ですが、フィールド名が異なります。例: 1 つの請求書オブジェクトを抽出します。

ベンダーJSONモード⟧構造化された出力 / スキーマ注意事項
OpenAIresponse_format: { type: "json_object" }response_format: { type: "json_schema", json_schema: { name, schema, strict: true } }strict: true rejects undeclared fields; works with Pydantic model_json_schema()
Google ジェミニresponseMimeType: "application/json"Above + responseJsonSchema or SDK response_schema見るジェミニ ガイド
人類的プロンプト + 解析output_format (Claude structured output) or schema in Messages APIフィールドは SDK とともに進化します。スキーマをフラットに保つ

When migrating vendors, keep the Schema itself standard JSON Schema⟧ (type, properties, required, enum); SDKs only wrap the request. Do not mix OpenAPI 3.0 uppercase types (OBJECT) with JSON Schema⟧ lowercase (object).

優れたスキーマの作成: Pydantic から本番環境まで

Recommended flow: define types in Pydantic / Zod → export JSON Schema⟧ → tune → send to API. Put semantics in description — it enters model context and disambiguates “qty = pieces vs boxes”; type: integer alone cannot.

from pydantic import BaseModel, Field


class LineItem(BaseModel):
    name: str = Field(description="Product name")
    qty: int = Field(description="Quantity, positive integer", ge=1)
    unit_price_cents: int = Field(description="Unit price in cents", ge=0)


class Invoice(BaseModel):
    vendor: str
    currency: str = Field(description="ISO 4217, e.g. CNY")
    items: list[LineItem]
    total_cents: int

schema = Invoice.model_json_schema()
# In production add additionalProperties: false

実際的なルール:

  • Prefer object root over root-level array; { "items": [...] } is more stable on some APIs.
  • Start with type / properties / required / enum, then add additionalProperties, min/max; do not dump full Draft 2020-12 — some keywords are ignored.
  • ネストを浅く保つ;循環参照は拒否されます - スキーマをフラット化します。
  • 分割スキーマとプロンプト: スキーマ = 形状;プロンプト = セマンティクス (「下のテキストから請求書を抽出…」)。

OpenAI 構造化出力s⟧ の例

Chat Completions supports json_schema response format since 2024. With strict: true, output should only contain Schema fields:

from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()

class Invoice(BaseModel):
    vendor: str
    total_cents: int

response = client.chat.completions.create(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "user", "content": "Extract invoice: Acme sold 2 keyboards for 398 CNY."}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "invoice",
            "strict": True,
            "schema": Invoice.model_json_schema(),
        },
    },
)

data = response.choices[0].message.content  # JSON string
import json
invoice = json.loads(data)

Gemini uses response_mime_type + response_json_schema — see the Gemini article. For Anthropic, check current SDK structured output docs — same idea, official field names.

本番パイプライン: 生成 → 解析 → 検証 → 再試行

構造化出力 は、「1 回の API 呼び出しで完了」ではありません。次の 4 つの手順を修正します。

  1. Generate: call model with Schema; log prompt, Schema version, raw content.
  2. Parse: JSON.parse (or SDK parsed); on failure, retry whole response — no half-parse.
  3. Schema validate: run same JSON Schema⟧ via AJV / jsonschema / Pydantic; retry or degrade on failure.
  4. ビジネスの検証: カスタム アサート (合計、外部キー);障害時には人間またはルール エンジンが発生します。

開発中は、スキーマと 2 ~ 3 個のポジティブ/ネガティブ サンプルをリポジトリに保存します。 JSON Toolbox⟧ を構造と Diff にローカルで使用します。REST コントラクト テストと同じ考え方で、コンシューマは LLM です。

よくある落とし穴: JSON モード⟧ がオンになっているときに「説明してから JSON」を要求すること。切り捨て (最大トークンを増やすかタスクを分割する);フロントエンド デモの API キー。スキーマのバージョンがプロンプトからずれています。

Tool Calling との違い

構造化された出力ツール呼び出し / MCP
制約するユーザーへの最終 JSON 返信Tool argument JSON (inputSchema)
副作用なし - データのみホスト / MCP サーバーが実行
一般的な使用方法抽出、分類、フォームへの記入、エージェントへの引き継ぎインベントリ、ファイル、外部 API
失敗時リトライか人間かツールメッセージのエラー → モデルに再度質問

完全なエージェント ループは、次のようになります。構造化出力 が意図を抽出 → ツール呼び出し が動作 → 構造化出力 またはユーザー向けに散文で要約。 構造化出力 を使用して、「支払い API が呼び出された」かのように装わないでください。モデルはそれを呼び出していません。

よくある質問

プロンプトに「JSON を出力してください」というメッセージで十分ですか?

いいえ。プロンプトはコンプライアンスの可能性を高めるだけです。値下げフェンス、末尾のコンマ、フィールド ドリフトは依然として発生します。運用環境では、少なくとも JSON モード⟧ を有効にしてください。理想的には、JSON Schema⟧ をAPI Structured Output チャネル経由で渡し、デコードで不正なトークンが除外されるようにします。

JSON モード⟧ と 構造化出力 の違いは何ですか?

JSON モード⟧ は、有効な JSON テキストのみを保証し、フィールド名、型、または必要なキーは保証しません。 構造化出力 は、JSON スキーマ⟧ を追加し、生成中にトークンをフィルター処理します。形状が安定するため、保存したり次のホップに直接渡すことができます。

OpenAI、Gemini、Anthropic の設定フィールドは同じですか?

同じコンセプトですが、名前が異なります。 OpenAI: json_schema と strict を使用したresponse_format; Gemini: responseMimeType + responseJsonSchema; Anthropic: output_format またはツールの構造化出力。スキーマを標準に保ちます。 SDK はリクエストをラップします。

構造化出力 はツール呼び出し を置き換えることができますか?

いいえ。 構造化出力 は、最終的な JSON 応答を制約します。 ツール呼び出し はツール引数 JSON を制約し、ホストがツールを実行することを要求します。前者は抽出/分類/入力に使用します。後者はインベントリ、ファイル、MCP 用です。完全なエージェント チェーンでは、多くの場合、両方が使用されます。

まだモデルの出力を検証する必要がありますか?

はい。制約付きデコードでは、構文エラーと型ドリフトは削減されますが、セマンティックな正確さ (有効​​な型、捏造された値) は削減されません。本番環境で同じ JSON スキーマ⟧ を再実行します。失敗した場合は再試行、機能低下、またはレビューを行います。

スキーマとサンプル出力をローカルで検証するにはどうすればよいですか?

JSON スキーマ⟧ といくつかのモデル出力サンプルを JSON ファイルとして保存します。構文と構造のチェックにはブラウザーの JSON Toolbox⟧ を使用します。何もアップロードされません。

まとめ

JSON スキーマ⟧ に一致する JSON を AI から取得するには、順序が重要です。最初にスキーマを定義し、JSON モード⟧ / 構造化出力 を有効にしてから、プロンプトを作成します。プロンプト = セマンティクス;スキーマ = 形状。 Pydantic / Zod は作者に優しい表題です。ベンダー API はスキーマ チャネルを公開します。

実際の請求書またはサポート記録をエンドツーエンドで 1 つ実行します: スキーマ → API 呼び出し → 出力をバリデーターに貼り付けます。一致した場合、データベースまたは次のエージェントに接続します。ツール 引数 は引き続き ツール呼び出し / MCP を通過します — 1 つの API にマージされません。