ブログ • AI / JSON Schema
AI に JSON Schema が必要な理由:Structured Output、Function Calling、JSON Schema を整理する
ChatGPT、Gemini、DeepSeek を繋ぐと、ドキュメントにFunction Calling、Structured Output、JSON Schema が同時に出てくる。三つの機能に見えるが、同じ契約言語の三つの使い方だ。
JSON Schema は「この JSON がどう見えるべきか」を記述する。現在の主流は2020-12。Function Calling はツール引数に、Structured Output は最終回答に使う。AI に必要なのは JSON が流行だからではなく、自然言語を下流コードが安定して消費できないからだ。
この記事では:
- 三つの概念がそれぞれ何を縛るか
- 自然言語出力だけでは足りない理由
- Function Calling が schema でツール引数を縛る方法
- Structured Output が schema で最終回答を縛る方法
- 同じ schema を二箇所で再利用し、選び方と検証まで
まずこれだけ:モデルが JSON を出すことと、schema に適合する JSON を出すことは別物だ。JSON Schema が定義するのは後者。Function Calling は「どのツールを、正しい引数で呼ぶか」;Structured Output は「最終回答の形」。以下、なぜ → 何か → ツール引数 → 最終回答 → 選び方 → 検証、の順で分解する。
三つの概念を一枚の表で
まず三語を一枚の表に置く。言語としての JSON Schema は共有するが、縛る対象が違う。
| 概念 | 縛る対象 | 典型フィールド / 規格 | 解く問題 |
|---|---|---|---|
| JSON Schema | JSON のフィールド、型、必須、enum | JSON Schema 2020-12 | 契約そのもの:データがどう見えるべきか |
| Function Calling | tool_calls.arguments |
tools[].parameters |
どのツールを呼ぶか、引数は合法か |
| Structured Output | 最終回答の JSON | response_format.json_schema / responseSchema |
結果をそのまま保存・パイプライン投入できるか |
三語が混ざるのは、ドキュメントが機能名と契約言語を一緒に書くからだ。層を覚える:JSON Schema は言語、Function Calling と Structured Output は二つの使い方。
AI が自然言語だけでは足りない理由
プロンプトに「JSON を返して」と書くだけだと、次の失敗が起きる。
| 失敗モード | 典型的な症状 | 根本原因 |
|---|---|---|
| 構文エラー | 引用符欠け、末尾カンマ、単引用符 | モデルが散文のように生成し、token 列が制約されない |
| フィールドのドリフト | 同じプロンプトでフィールド名が二回違う | schema 契約がなく、モデルが自由に書く |
| 型エラー | 数値が文字列に、配列がオブジェクトに | プロンプトが曖昧で、type の硬制約がない |
| 回帰できない | 同じケースを fixture 比較できない | 自然言語には安定したフィールド集合がない |
デモでは再試行や二次パースで隠せる。本番の Agent、パイプライン、自動化では下流が落ちる。JSON Schema は形式契約:生成前にフィールド、型、必須を宣言する。
JSON Schema は契約であり、コメントではない
JSON Schema は JSON 構造を記述する標準。AI 統合では人向けコメントではなく、機械が実行できる契約だ:
-
1
フィールド名と型を宣言する
properties の各 key が出力フィールド、type が string / integer / array などを指定する。
-
2
必須と enum を限る
required が必須フィールド、enum が取りうる値を制限し、モデルの「創造的」な値を防ぐ。
-
3
余分なフィールドを止める
additionalProperties: false は未宣言 key を禁止する。Strict Schema は通常これを強制する。
{
"type": "object",
"properties": {
"ticket_id": {
"type": "string",
"description": "Support ticket ID, e.g. TCK-1042"
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
},
"summary": {
"type": "string",
"description": "One-sentence problem summary"
}
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
この schema は tools にあるか response_format にあるかを気にしない。問うのは「この JSON は合法か」だけ。次の二節で Function Calling と Structured Output に載せる。
Function Calling:ツール引数を schema で縛る
Function Calling は、推測ではなく構造化されたツール呼び出しを返す。各ツールのtools[].parameters が JSON Schema だ:
{
"model": "gpt-4o",
"messages": [
{ "role": "user", "content": "Create a high-priority ticket for checkout timeout." }
],
"tools": [
{
"type": "function",
"function": {
"name": "create_ticket",
"description": "Create a support ticket from the user request.",
"parameters": {
"type": "object",
"properties": {
"ticket_id": { "type": "string" },
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" }
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
}
}
]
}
モデルが返すtool_calls[0].function.arguments は JSON 文字列だ。parameters に「できるだけ」合わせるが、毎回合法とは限らない。欠け、型違い、構文壊れは起きる。パースと検証は業務層の仕事。
Agent ループ、MCP inputSchema、より詳しいツールの書き方はAI Agent と JSON Schema の解説を参照。
Structured Output:最終回答を schema で縛る
Structured Output が縛るのはツール引数ではなく最終回答。OpenAI はresponse_format、Gemini はresponseSchema。中身はどちらも JSON Schema:
{
"model": "gpt-4o-2024-08-06",
"messages": [
{ "role": "user", "content": "Extract a support ticket from this email." }
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"ticket_id": { "type": "string" },
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" }
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
}
}
}
strict: true を付けると、推論時に schema が token を導く。生成してから祈るのではなく、適合するものだけを生成する。
ChatGPT と Gemini の設定差、JSON Mode と Strict Schema の保証範囲は2026 AI Structured Output ガイドを参照。
同じ schema、二つの載せ方
本稿の要点:ツール引数と最終回答で、ずれる二セットのフィールドを書かない。チケットは三フィールドのまま。変わるのは載せ先だけ。
TICKET_SCHEMA = {
"type": "object",
"properties": {
"ticket_id": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"summary": {"type": "string"},
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": False,
}
# 1) Function Calling: constrain tool arguments
tools = [{
"type": "function",
"function": {
"name": "create_ticket",
"description": "Create a support ticket.",
"parameters": TICKET_SCHEMA,
},
}]
# 2) Structured Output: constrain the final answer
response_format = {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": True,
"schema": TICKET_SCHEMA,
},
}
一つの契約、三つの着地:
-
1
ツール呼び出し
モデルが create_ticket を選び、arguments は schema に適合しなければならない。
-
2
最終回答
ツールが不要なときも、最終 JSON は同じフィールドで、そのまま保存できる。
-
3
業務検証
データが tool_calls でも message.content でも、同じ parse + validate を通す。
四つの経路の選び方
2026 年の LLM 構造化データは、だいたい四つの経路がある:
| 経路 | API 設定 | 保証すること | 向く場面 |
|---|---|---|---|
| プロンプトのみ | 特別な設定なし | 形式を保証しない | 試作、人が読む |
| JSON Mode | response_format: json_object |
合法な JSON オブジェクト | 簡単な抽出、緩いフィールド |
| Function Calling | tools[].parameters |
arguments は parameters にできるだけ合わせる | 外部ツール / DB 書き込み / HTTP |
| Structured Output | json_schema + strict: true |
schema への厳密な適合 | 最終結果を保存・パイプライン投入する |
Agent の四層(LLM、Function Calling、MCP、JSON Schema)の組み立ては2026 AI Agent 技術スタックを参照。
本番の書き方と検証
Structured Output と Function Calling の成功率は schema の質に依存する。業務層はそれでも検証する。
-
1
各 property に description を書く
モデルは description を読んで値を決める。「Support ticket ID, e.g. TCK-1042」は裸のフィールド名より正確。
-
2
散文で示唆せず enum を使う
文中の「low/medium/high」より
enumの方が確実。 -
3
additionalProperties: false を付ける
OpenAI Strict は通常これを強制する。根オブジェクトに
additionalProperties: falseを付け、未宣言キーの混入を防ぐ。 -
4
ネストを深くしすぎない
3 層超や大量の oneOf は失敗率を上げる。複雑な形は複数回の呼び出しに分ける方が安定する。
import json
from jsonschema import validate, ValidationError
def parse_model_json(raw: str, schema: dict) -> dict:
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON: {e}") from e
try:
validate(instance=data, schema=schema)
except ValidationError as e:
raise ValueError(f"Schema mismatch: {e.message}") from e
return data
ChatGPT、Gemini、DeepSeek のどれでも、同じ parse → validate → 業務ロジック。開発中は schema と実出力を JSONNote に貼る方が print より速い。
JSONNote でモデル JSON をデバッグ
遅いのは API を書くことではなく、返ってきた JSON のどこが契約に反するか見ることだ。JSONNote はブラウザでローカルに動く:
-
1
schema と出力を検証する
API の JSON と schema をJSON Schema ページに貼ると、契約に合わないフィールドがすぐ分かる。
-
2
API 応答を整形する
JSON フォーマッタで構文とインデントを確認する。
-
3
二回の呼び出しを比較する
JSON Diff で二回の structured output や tool arguments を比較し、回帰チェックする。
よくある質問
Function Calling と Structured Output の違いは?
Function Calling は tool_calls.arguments(どのツールを、何の引数で)を縛る。Structured Output は最終回答の JSON を縛る。どちらも JSON Schema だが層が違う。Agent ではよく併用する:tools が外部動作、Structured Output が最終の構造化結果。
JSON Mode だけで十分ですか?
不十分。JSON Mode(response_format.type=json_object)は合法 JSON オブジェクトだけを保証し、フィールドと型は保証しない。本番の複雑な形には Structured Output(json_schema + strict:true または Gemini responseSchema)を使う。
Strict Schema を付けても手動検証は必要ですか?
必要。API 層の制約は業務層の validate の代わりにならない。空 content、途中切断、エッジケースは残る。try/except の parse + JSON Schema validate が本番の標準。
JSON Schema はどの版を使うべきですか?
2026 年の主要 LLM API と MCP ツール契約は JSON Schema 2020-12。type、properties、required、enum、items、additionalProperties などをサポートする。
1 つの schema をツールと最終出力の両方に使えますか?
使える。同じオブジェクト定義を tools[].parameters と response_format.json_schema.schema の両方に置ける。単一の真実を保ち、二重契約のずれを防ぐ。
まとめ
AI に JSON Schema が必要なのは、モデル出力を「データっぽいもの」から「機械が検証できる契約」に変えるからだ。
JSON Schema は言語。Function Calling はツール引数に、Structured Output は最終回答に使う。
schema を一つ書き、二箇所に載せ、業務層で validate する。JSONNote でローカルに整形・検証・Diff。デモから本番への最短経路だ。