JSONモードとは?LLMに有効なJSONだけ返させる仕組みと構造化出力への移行判断を解説【2026年版】
JSONモードとは、LLMのAPIに対して「応答本文を構文として有効なJSON1個にする」よう指定する出力形式です。OpenAI系ではresponse_formatにjson_objectを渡し、Ollamaではformatにjsonを渡します。ここで手に入るのは構文の妥当性だけで、どのキーが入るか、その値が数値か文字列かは指定できません。混同されやすい相手が、スキーマそのものを守らせる構造化出力です。両者の担当は明確に分かれており、スキーマ準拠まで含めた設計はLLM構造化出力の仕組みと実装・見送り条件にまとめてあります。本記事は、その手前にある「構文だけ保証する機能」を実装者の目線で扱います。
まとめ|JSONモードで買えるのは構文だけ、判断は3点で決まる
先に結論を置きます。JSONモードで消えるのは「応答の前後に説明文が付いてパースに失敗する」という一種類の事故だけです。キー名の揺れ、型の揺れ、値の誤りは、機能を有効にしても残ります。したがって受け側には、パース後のスキーマ検証と、失敗時の扱いを必ず残しておく必要があります。
判断は次の3点で決まります。第一に、使う実行基盤がスキーマ指定に対応しているか。対応しているなら新規実装でJSONモードを選ぶ理由はほとんどありません。第二に、返ってくるキー集合を事前に固定できるか。ユーザー定義項目の抽出のように実行時までキーが決まらない用途では、スキーマを書けないためJSONモードが残ります。第三に、既存の検証と後処理を捨てて移行するだけの工数が回収できるか。廃止は告知されておらず、動いているものを急いで壊す必要はありません。
JSONモードとは何か|構文だけを保証する仕組みと機能の位置づけ
JSONモードは、モデルが次に出せるトークンを制限して、出力全体がJSONの文法に収まるよう誘導する仕組みです。OpenAIの公式手引は保証の範囲を「JSON mode will not guarantee the output matches any specific schema, only that it is valid」と書いています。約束されるのは、有効なJSONであることだけです。Microsoft Learnの記述も同じで、スキーマの保証が要るなら構造化出力へ移るよう案内しています。
紛らわしい機能が3つ並ぶので、担当を分けて覚えてください。
| 機能 | 保証する範囲 | 保証しない範囲 |
|---|---|---|
| JSONモード | 構文が有効なJSON | キー名・型・値 |
| 構造化出力 | スキーマ準拠の構造 | 値の事実としての正しさ |
| 関数呼び出し | 呼び先と引数の形式 | 呼ぶ判断そのものの妥当性 |
関数呼び出しは「どの処理を呼ぶかをモデルに選ばせ、その引数を型どおりに作らせる」機能で、応答本文の形式指定とは目的が違います。実装の手順の参照先はFunction Callingの仕組みとOpenAI APIでの実装です。JSONモードは、この3つのうちもっとも約束が少ない層に位置します。
約束が少ないぶん、制約は緩く済みます。スキーマを渡す方式では、モデル側で文法を組み立てる工程が挟まるため、スキーマの表現力に上限が設けられていたり、初回のリクエストに遅延が乗ったりする。JSONモードにはその工程がなく、渡すのは形式の指定だけです。キーが実行時まで決まらない抽出処理や、出力の形を試行錯誤している段階では、この軽さが利点になる場面もあります。ただし利点はそこまでで、形が固まった後もJSONモードのまま運用を続けると、キーと型の検証をすべて自前で抱えることになります。
OpenAIとAzureでの指定方法と、messagesにJSONの語が要る理由
OpenAI互換のチャット補完APIでは、リクエストに出力形式を1行足すだけで有効になります。
response = client.chat.completions.create(
model=MODEL,
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": "You are an extractor. Output JSON only."},
{"role": "user", "content": user_text},
],
)
ここで見落とされやすい条件が1つあります。会話の中に「JSON」という語が現れていないとリクエストが弾かれます。Azure OpenAIの手引が載せているエラー本文はこうです。
BadRequestError: Error code: 400 - 'messages' must contain the word 'json'
in some form, to use 'response_format' of type 'json_object'.
単なる形式的な縛りではありません。Microsoft LearnはOpenAIの説明として「generate an unending stream of whitespace and the request could run continually until it reaches the token limit」を引いています。文法だけを制限しても、何を書くべきかの指示がなければ空白を延々と吐き続ける状態に陥りうる、というわけです。だからシステムメッセージ側に「JSONで返す」という指示を明文で置きます。
Azure OpenAIでの対応は API バージョン 2023-12-01-preview から入りました。なお、この形式指定はチャット補完系のパラメータとして設計されたもので、新しい世代のAPIでは形式指定の書き方が変わります。世代ごとの差はResponses APIとChat Completions APIの違いと移行判断を参照してください。既存案件がどちらの世代に載っているかで、そもそも取れる選択肢が変わります。
Gemini・Claude・ローカルLLMでのJSONモード相当の有無
「JSONモード」という呼び名はOpenAI系の用語で、他の基盤では相当機能の有無も指定方法も揃っていません。2026年8月時点の公式手引を突き合わせると、次のようになります。
| 実行基盤 | 構文だけ保証する指定 | 位置づけ(2026年8月) |
|---|---|---|
| OpenAI・Azure OpenAI | json_object を指定 | 継続提供・構造化出力を推奨 |
| Gemini | MIMEタイプに JSON を指定 | 手引はスキーマ同梱で記載 |
| Claude | 相当する単独機能なし | スキーマ必須の構造化出力のみ |
| Ollama | format に json | スキーマも同じ引数で渡す |
Claudeを使う案件では、この差が設計に直接効きます。公式手引にはスキーマ無しでJSONだけを保証するモードの記載がなく、output_config.formatにjson_schemaを渡す形が正式提供の入口です。ベータ時代のoutput_formatパラメータと専用ヘッダは移行期間中のみ動くと明記されています。加えて、初回リクエストでは文法のコンパイル遅延が乗り、コンパイル済みの文法は最終利用から24時間キャッシュされる、という運用上の性格も書かれている。スキーマを頻繁に差し替える設計だと、この遅延を毎回踏むことになります。
Ollamaはformat1つで両方を兼ねます。文字列のjsonを渡せばJSONモード、JSON Schemaのオブジェクトを渡せば構造化出力です。手引にはOpenAI系と同じ趣旨の注意があり、プロンプトでJSONを指示しないと大量の空白が出る場合があるとしています。ローカル実行では基盤側の対応状況が案件ごとに違うため、まずformatにスキーマを渡せるかを確かめてから設計を決めてください。
JSONモードで起きる4つの失敗と、受け側の実装で吸収する手順
構文が保証されても、業務で使うデータとしては次の4つが崩れます。順に見ていきます。
1. キー名が揺れる。同じ指示でもcustomer_nameとcustomerNameが混ざり、単数形と複数形も安定しません。2. 型が揺れる。金額が数値のときと文字列のときが混在し、値が無い場合にnull・空文字・キー自体の欠落と3通りに分かれる。3. 途中で切れる。出力が上限に達すると、構文として壊れたJSONがそのまま返ります。4. トップレベルが配列にならない。json_objectはオブジェクト1個を返す形式なので、複数件を返したい場合はitemsのようなキーで包む前提で指示を書く必要があります。
受け側は、パースの前に打ち切りを弾き、パースの後にスキーマで検証する二段構えにします。
choice = response.choices[0]
if choice.finish_reason == "length":
raise TruncatedOutput("max tokens reached; do not parse")
data = json.loads(choice.message.content)
validate(instance=data, schema=EXTRACTION_SCHEMA)
再試行は、崩れ方で分岐させると無駄打ちが減ります。構文が壊れているだけなら、同じプロンプトのまま温度を下げて1回だけ引き直す。キーが足りない、あるいは型が想定と違う場合は、不足しているキー名と期待する型だけを追記した指示で引き直す。打ち切りが原因のときは、同じ条件で何度投げても同じ位置で切れるため、抽出対象を分割してリクエストを分けます。区別せずに一律で3回リトライする実装は、3番目のケースで確実に3倍の費用を捨てることになる。ログには崩れの種類を残しておくと、後でスキーマ指定へ移す判断材料がそのまま揃います。
打ち切りの判定を先に置くのは、Microsoft Learnが「You should check finish_reason for the value length before parsing the response」と明記しているためです。部分JSONを解析しようとすると、例外の内容が「モデルの出力がおかしい」に見えてしまい、原因がトークン上限だと気づくまで時間を失います。後段の検証に使うスキーマの書き方はJSON Schemaの記法とDraft 2020-12の実装にまとめてあります。
JSONモードの保証を4層で切り分ける|構文・キー・型・値の担当
ここが本記事の核心です。LLMの出力に対する「正しさ」は1枚ではなく、4つの層に分かれます。層ごとに誰が責任を持つかを先に決めておくと、設計の議論が止まらなくなります。
| 層 | 内容 | 担当させる先 |
|---|---|---|
| 第1層 | 構文が有効なJSONか | JSONモードで足りる |
| 第2層 | 必要なキーが揃うか | 構造化出力か自前検証 |
| 第3層 | 型と列挙が合うか | 構造化出力か自前検証 |
| 第4層 | 値が事実として正しいか | 参照データと業務照合 |
言い切ります。JSONモードで買えるのは第1層だけです。第2層と第3層をプロンプトの書き方で押さえ込もうとすると、指示文が肥大化したうえに、モデルを差し替えるたびに再調整が発生します。スキーマ指定が使える基盤なら、その手間を払う前に構造化出力へ移したほうが安い。逆にスキーマが使えない基盤では、第2層と第3層はコード側の検証で受けると決め、プロンプトには期待しないほうが壊れにくくなります。
そして第4層は、どの出力形式を選んでも解決しません。抽出した金額が原本と一致しているか、参照した条文が実在するかは、形式の話ではないからです。この層の性質と対策の考え方はハルシネーションの意味・原因・種類と対策で扱っています。要件定義の場で「JSONで返るから正しい」と受け取られると後で必ず揉めるため、保証されるのは形であって内容ではない、という一文を仕様書に残してください。
受託開発でJSONモードを選ぶ条件と、構造化出力へ移すときの手順
新規実装で第一候補になるのは構造化出力です。そのうえで、JSONモードを残してよい条件を3つに絞ります。
条件1:実行基盤がスキーマ指定に対応していない。自社ホストの推論基盤が古い、あるいは既存案件がAPIバージョンを固定していて上げられない場合、選べるのは構文保証までです。条件2:キー集合が実行時に決まる。帳票の項目名をユーザーが定義するような用途では、スキーマを事前に固定できません。条件3:移行の工数が回収できない。出力が数キーで、検証と再試行がすでに動いているなら、廃止告知がない以上は据え置きが合理的です。
逆に、次の場面では選ばないでください。キーが固定で下流がデータベース投入やAPI連携に繋がる場合、崩れの影響が業務側まで届くため構造化出力に寄せます。出力に応じて処理を分岐させたい場合は、そもそも応答本文ではなく関数呼び出しの担当です。複数のモデルを差し替えて比較したい場合も、スキーマを固定できるほうが評価が揃います。
移行するときは、いきなり切り替えず4段階で進めます。第一に、現行のJSONモード出力を100件ほど溜めて、実際に出ているキーと型の分布を書き出す。第二に、その分布からJSON Schemaを起こし、requiredは本当に必須の項目だけに絞って、余分なキーを弾く設定を入れる。第三に、構造化出力へ切り替えたうえで旧経路と並走させ、差分が出る入力だけを目視で確認します。第四に、差分が収まってから再試行や後処理の自作コードを削っていく。順序を逆にして先にコードを削ると、切り分けができなくなります。
受託開発で組み込む場合は、要件定義の段階で「保証されるのは構文か、スキーマか、値か」を発注側と合意しておいてください。ここを曖昧にしたまま進むと、受け入れテストで値の誤りが出たときに責任分界が定まりません。LLMを業務システムへ組み込む設計や、抽出結果を後続処理へ渡すエージェントの実装はAIエージェント開発で対応しています。どの層をモデルに任せ、どの層をコードと業務で受けるかの線引きから、要件として書き起こします。
よくある質問
JSONモードを使えばJSONのパースエラーは完全になくなりますか?
なくなりません。構文としては有効なJSONが返るよう制限されますが、出力がトークン上限に達して途中で切れた場合は壊れたまま返ります。Microsoft Learnも、解析の前にfinish_reasonがlengthでないかを確認するよう案内しています。パース処理を例外なしで書けると考えず、打ち切りの判定と例外処理は残してください。
messagesにJSONと書き忘れると、どんなエラーになりますか?
結果は400番のリクエストエラーです。エラー本文は「’messages’ must contain the word ‘json’ in some form」で、会話のどこかに「JSON」という語が必要だと示されます。システムメッセージに「JSONで返す」という指示を入れておけば条件を満たします。この縛りは、指示がない状態で形式だけ固定するとモデルが空白を延々と出力しうるという性質への対処です。
JSONモードは廃止予定なので、今すぐ移行しないと動かなくなりますか?
2026年8月時点で、廃止の告知は確認できません。OpenAIの手引もMicrosoft Learnも「継続してサポートされているが、可能なら構造化出力を推奨する」という書き方にとどまっています。したがって、動いている実装を緊急で止める理由はありません。新規実装はスキーマ指定を第一候補にし、既存は改修の機会に合わせて移すという順番で足ります。
スキーマを渡せない場面ではJSONモードとプロンプト指示のどちらが確実ですか?
併用してください。どちらか一方ではなく、JSONモードで構文を固定し、プロンプトでキー名と型と欠損時の表現を明示するのが実務の形です。そのうえで、返ってきたデータをコード側のスキーマ検証にかけます。プロンプトだけだと説明文が混ざる事故が残り、JSONモードだけだとキーと型が揺れる。役割が違うので、片方で置き換えることはできません。
出力が途中で切れて壊れたJSONが返るのは、どんな条件のときですか?
指定した出力トークン上限に達したときと、会話全体がモデルのコンテキスト長を超えたときです。どちらもfinish_reasonがlengthになります。対処は上限を引き上げるか、入力側を短くするかの二択で、部分JSONを補完して読もうとするのは避けてください。抽出件数が多い処理では、1リクエストで全件返させず、件数を区切って複数回に分けるほうが安定します。