このシリーズの以前の投稿で取り上げたものエージェントに 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 の例 | 例によって形を作り、厳密なルールはありません | フィールド名のずれ、フィールドの欠落、タイプの混合 |
| L2 | JSON Mode⟧ (response_format: json_object, etc.) | 出力は有効である必要があります JSON | Parses, 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モード⟧ | 構造化された出力 / スキーマ | 注意事項 |
|---|---|---|---|
| OpenAI | response_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 つの手順を修正します。
- Generate: call model with Schema; log prompt, Schema version, raw
content. - Parse:
JSON.parse(or SDKparsed); on failure, retry whole response — no half-parse. - Schema validate: run same JSON Schema⟧ via AJV /
jsonschema/ Pydantic; retry or degrade on failure. - ビジネスの検証: カスタム アサート (合計、外部キー);障害時には人間またはルール エンジンが発生します。
開発中は、スキーマと 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 にマージされません。