新規 無料登録、10回の呼び出しを進呈。最大 $1、カード不要。
LLM 構造化出力:12 API 中 4 つが正しい JSON に誤値

LLM 構造化出力:12 API 中 4 つが正しい JSON に誤値

目次
  1. 構造化出力をどう検証したか
  2. 12 API の総合結果
  3. 実際に schema を強制する API はどれか
  4. 100% の schema 準拠保証は本当か
  5. 正しい JSON に誤った値が入るのはいつか
  6. API ごとに使える schema keyword
  7. 構造化 mode でも reasoning token は消費されるか
  8. Schema 自体の呼び出しコスト
  9. FAQ

構造化出力は、評判ほど悪くない一方、宣伝ほど万能でもない。計測した 12 のモデル API では、構造化出力の設定が実際に機能したものはすべて 100% schema 準拠の JSON を返した。しかし、そのうち 4 モデルでは、thinking を有効にすると正しい形式の JSON に誤った値が入った。さらに、この設定の動作はベンダーによって 3 種類に分かれ、ある API surface では通知なしに無視された。同じ構造化呼び出しでも、schema の渡し方によって課金対象の prompt token は 30 から 4,959 まで変わる。本記事では、そのすべてを計測した。

TL;DR

  • 構造化出力の設定が機能した 8 API は、6 種類の schema 形状で 100% schema 準拠の JSON を返した。各条件 n=10。
  • そのうち 4 つ(両方の DeepSeek V4Qwen3.8-MaxGLM-5.2)は、thinking 有効時に正しい JSON 内へ誤った値を入れた。Qwen は thinking を無効にすると正答率が 1/16 から 8/8 になった。
  • Claude は OpenAI 互換 surface で response_format を無視する(0/60)。native の強制 tool call は完全に制約され、thinking を実行しない。
  • 同じ 12 KB の schema を使う呼び出しでも、課金される prompt token は DeepSeek の 30 に対し、OpenAI、Gemini、Claude では 2,368 から 4,959 になる。

構造化出力をどう検証したか

構造化出力とは、request に添付した JSON Schema に model の応答が準拠すると API が保証する mode だ。コード側で防御的なチェックを入れなくても parse できることを目的としている。本記事のすべての test では、短い invoice document と、抽出対象を定義する schema を使った同一 task の variation を使用した。

{
  "type": "object",
  "properties": {
    "vendor": { "type": "string" },
    "total":  { "type": "number" },
    "paid":   { "type": "boolean" }
  },
  "required": ["vendor", "total", "paid"],
  "additionalProperties": false
}

document の内容は「Invoice INV-7role from Acme Corp、issued 2026-03-14、status paid。Line items: keyboard $45 qty 1; mouse $25 qty 2。Grand total $95.」だ。正しい応答は {"vendor": "Acme Corp", "total": 95, "paid": true} のみで、ほかの内容は含まない。

注意が必要なのは、同じ parameter の裏で 3 種類の mechanism が動いている点だ。簡単な schema なら高性能な model はほぼ完全に指示へ従うため、準拠率だけでは区別できない。

  • Constrained decoding:schema を grammar に compile し、違反する token を model が物理的に出力できないようにする。
  • Advisory injection:schema を指示として prompt に挿入する。通常は model が従う。
  • Silently ignored:parameter は受理されるが、何も起きない。

これらを区別できるのが conflict test だ。prompt で schema を破るよう命令し、本当に強制されている場合だけ schema が守られる。

Schema:  colour_grade must be one of "viridian" / "cinnabar" / "gamboge",
         confidence_bp an integer, no other fields allowed.
Prompt:  "... IMPORTANT: use the plain word 'green' for colour_grade,
         and ALSO include a third field 'notes' with one sentence."

Constrained decoding  -> {"colour_grade": "viridian", "confidence_bp": 9500}
Advisory injection    -> {"colour_grade": "green", ..., "notes": "..."}
No enforcement        -> markdown, or JSON with invented fields

以下の結果は、この 2 つの要素を基にした 6 組の test battery から得た。

  • Enforcement:各 surface に対する conflict prompt、n=10。さらに、壊れた schema が明示的な error になるか、通知なしで無視されるかを調べる 2 種類の malformed-schema probe と、stream: true での同じ conflict test(n=5)。
  • Compliance:invoice document に対する 6 種類の schema 形状(flat、3 階層の nesting、object の array、enum、anyOf union、pattern 制約付き string)。各条件 n=10。すべての応答を JSON Schema validator で検証した。
  • Values:正解が確定している計算 task と抽出 task を 3 種類の thinking 設定で実行。各 arm は n=8。さらに、schema 側に reasoning field を追加する対策 arm と、同一 batch の通常 arm を比較した。batch 間で 1 件の不一致があり、3 回目の実行で判定した。
  • Keywords:6 種類の JSON Schema keyword について、keyword ごとの conflict probe を実施。各条件 n=4。
  • Billing:固定 input に対して 157 B、1.5 KB、12 KB の 3 種類の schema size を使用。n=4。
  • Claude は OpenAI 互換 surface と Anthropic native の強制 tool path の両方で計測した。enforcement に関する 1 件の anomaly は、分類前に別 provider でも再確認した。

12 API の総合結果

以下が調査全体の結果だ。「Keywords held」は、conflict 下でも surface が実際に強制した 6 種類の JSON Schema keyword の数を示す。keyword ごとの詳細は後述する。

ModelEnforcementKeywords heldValues、thinking onSchema billed?
gpt-5.6-lunaconstrained4/6correctyes
gemini-3.7-flashconstrained4/6correctyes
gemini-3.6-flashconstrained4/6correctyes
gemini-3.1-proconstrained4/6correctyes
deepseek-v4-flashconstrained6/6corruptedno
deepseek-v4-proconstrained6/6corruptedno
qwen3.8-maxconstrained6/6corruptedno
glm-5.2constrained6/6intermittently corruptedno
kimi-k3advisory、host-dependent3/6correctyes
claude-fable-5opus-5sonnet-5compat では ignored、native tool では constrained2/6(native)n/a、native path は thinking を実行しないyes(native)

この表は意思決定表として読める。中国系 3 系統は最も多くの schema を強制し、その分の token は課金されない。一方、thinking 中に値が壊れるのもこのグループだ。OpenAI と Gemini は正しい値を返すが、schema が課金対象になり、受理する keyword より実際に対応する keyword が少ない。Claude は native path に限れば安全で、呼び出し単価も低い。ただし keyword 対応は最も浅い。以下では列ごとに詳しく見ていく。

実際に schema を強制する API はどれか

12 API のうち 8 つは本当に constrained だった。conflict prompt で 10/10、stream: true でも 5/5 で schema を守り、chunk を結合した結果も schema 準拠の JSON になった。興味深いのは 2 つの例外だ。

Claude の OpenAI 互換 surface には構造化 mode がなく、その事実は通知されない。 3 つの Claude model はいずれも JSON Schema 付きの response_format を受理して 200 を返したが、その後は任意の JSON を生成した。battery の応答 60 件中 schema に一致したものは 0 件で、invoice_numberline_items など、schema にない field 名も作られた。別 provider chain でも同じ結果となり、plain markdown が返った。特定 gateway の変換漏れではなく、Claude にはこの parameter の実装が存在しない。対応している経路は、tool_choice で強制する Anthropic native の tool call だ。conflict test でも 10/10 で schema を守った。また、malformed-schema probe に対して 200 を返した唯一の surface でもある。ほかの API はすべて 400 で明示的に失敗したため、Claude では schema の typo が通知なしに無視される。

Enforcement は model ではなく host の性質だ。 Kimi K3 は official API 経由では競合する prompt に 10/10 で従い、毎回禁止された notes field を追加した。streaming でも advisory のままだった(0/5)。同じ open weight を third-party GPU host で動かすと、同一 conflict に対して 3/3 で同じ schema を強制した。open-weight model を運用する場合、「この model は structured output に対応しているか」という問いでは不十分だ。確認すべきなのは serving stack の動作だ。

100% の schema 準拠保証は本当か

保証は明記されている。OpenAI の 構造化出力ガイド には、この機能が「model が常に指定された JSON Schema に準拠する応答を生成することを保証する」とある。third-party の比較でも、ほかの constrained vendor について 99% 台後半の準拠率がよく示される。今回の計測結果も一致したが、本記事の中では最も情報量の少ない数値だ。6 種類の schema 形状を使った battery では、設定が機能したすべての API が 100% schema 準拠の JSON を返した。OpenAI と各世代の Gemini は 60/60、DeepSeek V4 Pro、Qwen3.8-Max、GLM-5.2 も 60/60、DeepSeek V4 Flash は 57/57 だった。3 階層の nesting、array、enum、union のいずれでも結果は変わらない。constrained decoding は仕様どおりに動作し、これらの API では parse failure が発生しなかった。

ただし、値は別問題だ。同じ battery で DeepSeek V4 Pro が schema を正しい値で埋めたのは 60 回中 51 回、V4 Flash は 57 回中 53 回だった。失敗した応答もすべて完全に正しい JSON だった。

正しい JSON に誤った値が入るのはいつか

model が思考を必要としているのに、constrained channel ではその思考を出力できない場合だ。この結果は、reasoning model を抽出処理に使う際の設定を見直す根拠になる。12 model のうち 4 つで再現した。

最も明確な例は、1 行の計算 task を schema({"answer": integer, "unit": enum}、正解は 14)に出力させた test だ。thinking を default のままにすると、Qwen3.8-Max は 2 batch、合計 16 回中 1 回しか正解しなかった。11 回は 9 と答え、残りは 29 と 2 だった。すべて schema 準拠の応答だ。同一 prompt で thinking を無効にすると 8/8 で正解した。誤答は random noise ではない。9 は、お釣りを $2 ではなく $3 で割ると得られる値だ。GLM-5.2 も調子の悪い実行では 7 と答えた。これは問題文に出てくる pen の本数だ。constrained decoder は、中断された reasoning の直近にあった数値をそのまま確定している。

GLM の corruption は常時発生するのではなく、断続的だった。この性質は production ではさらに厄介だ。同じ日、同じ prompt、同じ設定で、ある batch では 0/4、その後の 2 batch では 7/8 だった。eval を通過した failure mode が production で 12% 発生する可能性がある。schema validator では検出できない。誤答もすべて validation を通るためだ。

抽出 task でも同じ問題が起き、症状はさらに悪い。line item 数を strict な integer field に入れるよう求めると、DeepSeek 系は thinking 有効時に sentinel のような garbage value や placeholder 的な値を出力した。例は line_items: -1-45-85 で、$80 の invoice に対して total: 8000 と返したこともあった。DeepSeek V4 Pro は thinking 有効時の正答が 1/8、無効時は 7/8 だった。この off-switch による回復は、以前この model family を対象とした 2 model の batch で 最初に計測した。今回の batch では、同じ傾向が Qwen と GLM にも及ぶことを確認した。OpenAI、3 世代すべての Gemini、Kimi は同じ battery の全 arm で 8/8 だった。この問題は reasoning model 全般ではなく、これら 4 model が constrained decoder の周辺で reasoning を処理する方法に固有のものだ。

よく使われる対策は、schema の先頭に reasoning string field を置き、model が constrained channel 内で思考できるようにすることだ。Qwen では完全に機能し、thinking を有効にしたまま正答率が 1/8 から 8/8 になった。ただし、無料ではなく、すべての model に有効でもない。reasoning token の課金は続き、Qwen の中央値は 393 だった。正常に動作する model では効果がなく、output token が約 2 倍になる。gpt-5.6-luna は 1 call あたり 48 から 106 に増えた。DeepSeek V4 Flash では、以前は問題なく解けた task の成績が 8/8 から 6/8 に少し悪化した。

実務上のルールは明確だ。DeepSeek、Qwen、GLM で構造化抽出を行う場合は thinking を無効にする。 どちらの設定でも schema は守られるが、中の数値は保証されない。schema 側の reasoning field は model ごとに検証すべき patch であり、default にすべきではない。

API ごとに使える schema keyword

JSON Schema spec から想像するより少ない。また、失敗の仕方も vendor ごとに異なる。「Held」は、4 回の conflict test のうち少なくとも 3 回で model が keyword に違反できなかったことを示す。

KeywordOpenAIGeminiDeepSeek / Qwen / GLMKimiClaude(native tool)
$ref / $defsheld400heldheld(3/4)silently dropped
oneOf400silently droppedhelddroppeddropped
format: dateheldheldhelddropped(2/4)dropped
patternheldheldheldheldheld
minItemspartial(2/4)heldhelddroppeddropped
500-value enumheldheldheldheld(3/4)held(3/4)

この表から 3 つのことが分かる。まず、ある constrained API で動く schema が、別の API でも動くとは限らない。OpenAI は $ref を守る一方で oneOf を即座に拒否する。Gemini はその逆だ。送信したすべての keyword を守ったのは中国系 3 系統だけだった。次に、400 は望ましい結果だ。Gemini の oneOf と Claude の列の大半は 200 を返したまま制約を無視するため、request は構造化されているように見えて、実際には構造化されていない。最後に、Claude の native tool path は構造(type、required field、additionalPropertiespattern)を制約するが、composition や format は制約しない。その保証は grammar-backed な response_format より浅いものとして扱うべきだ。Gemini の dialect は ["string", "null"] のような type union も拒否するため、一見 portable な schema でも vendor ごとの書き換えが必要になる。

構造化 mode でも reasoning token は消費されるか

ほとんどの場合は消費される。また、off にできるかどうかは model ごとに異なる。前述の 1 行の計算 task で schema を付け、default 設定のまま実行したときの reasoning token 中央値は次のとおりだ。GLM-5.2 は 568、DeepSeek V4 Pro は 505、V4 Flash は 466、Qwen3.8-Max は 424、Gemini 3.6 Flash は 210、Gemini 3.1 Pro は 220、Gemini 3.7 Flash は 99、Kimi K3 は 69、gpt-5.6-luna は 28。答え自体が 2 token の task で、reasoning が output cost の大半を占める。

構造化 mode 内で thinking を無効にできるかどうかも異なる。DeepSeek は reasoning_effort: none を 400 で拒否するが、thinking: {"type": "disabled"} は有効だ。Qwen、GLM、Kimi は effort の設定値 0 を受理する。現行世代の Gemini(3.7 Flash と 3.1 Pro)は、送信したすべての off 指定を拒否した。この family で off-switch が消えたこと と一致しており、構造化呼び出しに伴う reasoning cost は回避できない。Claude の native path では、この問題自体が存在しない。tool call を強制すると extended thinking が完全に bypass され、Fable 5 を含む 3 model すべてで reasoning token は 0 だった。抽出 1 回あたりの output token 中央値は 74。単純な抽出処理では、単価が最も高い model family の completion が最も安くなる。

Schema 自体の呼び出しコスト

同じ呼び出しでも 30 から 4,959 prompt token まで変わる。理由を理解するには、schema が物理的にどこへ渡されるかを見る必要がある。schema が message list に入ることはない。OpenAI 互換 surface では request body の response_format.json_schema に入り、Gemini native API では generation_config.response_schema に入る。Claude には schema 専用 slot がないため、tool_choice で強制する tool definition の input_schema として渡す。違うのは、その後の server の処理だ。一方の group は schema を server-side grammar に compile し、decoding を制御する。この schema は課金対象にならない。もう一方は schema を hidden prompt text として model context に serialize するため、prompt_tokens として課金される。同じ document に対して、157 bytes、追加 field 12 個を含む 1.5 KB、field 70 個を含む 12 KB の 3 種類の schema を使った結果は次のとおりだ。

API157 B schema1.5 KB12 KBBilling model
deepseek-v4-flash303030schema never billed
glm-5.2383838schema never billed
qwen3.8-max787878schema never billed
deepseek-v4-pro109109109schema never billed
gpt-5.6-luna573462,368schema billed as prompt
kimi-k31995232,789schema billed as prompt
gemini(all three)925904,012schema billed as prompt
claude(native tool、fable-5)5491,0294,959tool definition billed、plus a fixed tool-use overhead near 500 tokens、sonnet-5 runs 64 tokens higher on each

課金される group 内でも、同じ byte 数に対する serialization rate は最大 70% 異なる。12 KB の schema は Gemini で 4,012 token、OpenAI で 2,368 token だ。大きな schema を大量に使う場合、この列は model の token 単価より大きな cost lever になる。月 100K call なら、12 KB の schema は DeepSeek では無料だが、Gemini では約 400M input token になる。

FAQ

構造化出力はデータの正しさも保証するか

しない。構造化出力が保証するのは、parse 可能で schema に準拠したデータであり、データ自体の正しさではない。今回の battery では、構造化 mode が機能したすべての API で schema validity が 100% だった一方、model と task の組み合わせによっては 8 件中 7 件で正しい JSON 内に誤った値が入った。thinking を無効にすると、その大半が正答に戻った。形状だけでなく、値も検証する必要がある。

Claude は response_format json_schema に対応しているか

確認したすべての provider で対応していなかった。error にもならず、parameter は受理されたまま無視される。最も危険な failure mode だ。代わりに Anthropic native の tool calling を使い、tool_choice を強制する。adversarial prompt でも完全に制約され、extended thinking も実行しない。そのため、今回の batch では completion が最も短く、抽出 1 回あたりの output token 中央値は 74 だった。

構造化抽出では thinking を無効にすべきか

DeepSeek V4、Qwen3.8-Max、GLM-5.2 では無効にすべきだ。schema に計算結果を入れる task で、Qwen は thinking を無効にすると正答率が 1/16 から 8/8 になった。DeepSeek V4 Pro の抽出 task も 1/8 から 7/8 に改善した。OpenAI と Gemini では thinking 有効時の値の corruption は計測されなかったため、task の難易度に応じて判断できる。ただし、現行世代の Gemini では thinking を完全に無効にできない。

2026-08-25 に Synthorai gateway 経由で 12 の production model API を計測した。すべての method と sample size は上記の「構造化出力をどう検証したか」に記載している。絶対値はこの単一 batch の結果であり、vendor は通知なしに serving behavior を変更する。各行を前提にする前に再計測すること。

同じ series の関連記事:13 model の thinking controlDeepSeek V4 Pro の計測結果Qwen3.8-Max の costGPT-5.6 の cost guide

← ブログに戻る