Dev Study
← AIを部品として組み込む仕組み
レッスン 2 / 12·約 8 分·第20期 AIを部品として組み込む仕組み

スキーマが縛るのは形まで

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

前回『呼ぶたびに引き直している』では、同じ入力でも毎回分布から引き直すことを見ました。今回は、引く範囲をスキーマで縛ると、何が縛れて何が残るのかを見ます。

まず結論

構造化出力は、生成の途中でスキーマに合わない語を候補から外します。縛れるのは形で、許された候補のどれを引くかは、モデルの分布のままです。

その形の約束にも、公式文書自身が例外を3つ挙げています。値の正しさと例外の検出は、受け取る側のコードに書くしかありません。

? 読む前に予測

Anthropic の構造化出力の文書(2026-09 時点)は、スキーマへの適合をどう書いているでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

マスクが決めるのは、次に置ける語の集合だけ

LLM は呼ぶたびに分布から1つを引く部品です。では、引いてよい範囲をこちらで縛ればどうなるか。それが構造化出力です。

Anthropic の文書(2026-09 時点)は、仕組みを Structured outputs guarantee schema-compliant responses through constrained decoding と書き、スキーマを文法に変換して出力を縛ると説明します。生成中に許されない語を候補から外すという考え方の論文に Willard & Louf があり、要旨は効果を guaranteeing the structure of the generated text と書きます。保証の対象は構造です。

形を縛ることと値を縛ることは別です。"status" の後に "ok" と "ng" のどちらかしか置けないようにはできても、どちらを置くかは、残った候補に対するモデルの重みで決まります。

制約付きの生成で、縛られるものと残るもの
モデルの分布次の語ごとの重み
文法が、許されない語を消す
残った候補スキーマに合う語だけ
残りから引く
形文法どおり
値重みしだい
  • ・模型のマスクは候補を削るだけで、残った候補どうしの重みの比は変えない
  • ・生成が途中で打ち切られれば、形も閉じないまま返る
具体実物

模型で、形と値を別々に数える

標準ライブラリだけで作った模型です。スキーマは {"status": "ok"|"ng", "count": 整数}、入力に対する正答は {"status":"ng","count":2} とします。模型の分布は入力と無関係に "ok" と 7 を重く引くよう、手で偏らせてあります。Anthropic の実装を再現したものではありません。

$ python3 l2-d2.py の先頭3行と、$ python3 l2-recount.py(Python 3.14.6, macOS)コマンド出力
unconstrained: '0"ok" hereSure hereSure"ok"Sure'
constrained max_tokens=20: {"status":"ok","count":2} -> parses: {'status': 'ok', 'count': 2}
constrained max_tokens=5: {"status":"ok", -> parse error: JSONDecodeError
seed 1: parsed 1000/1000, content-correct 17/1000, status counts {'ok': 843, 'ng': 157}
seed 2: parsed 1000/1000, content-correct 20/1000, status counts {'ok': 835, 'ng': 165}
analytic P(correct) = P(ng)*P(count=2) = (1/6)*(1/10) = 0.016666666666666666
max_tokens=5: parse failures 1000/1000

l2-d2.py の4行目は数えずに書かれた文字列だったので省き、明示的に数え直した l2-recount.py の出力を続けています。

制約なしの出力は語の断片の羅列で、JSON になりません。制約ありでは形が整いパースできますが、値は "ok" で正答と違います。max_tokens=5 で打ち切ると、制約ありでも {"status":"ok", で止まり、パースに失敗します。1000回引くと、パースは1000回とも通り、中身の正答は17回(種1)でした。種を変えると20回で、理論値は 1/60(約1.7%)です。この1.7%は模型の数字であり、実際の API の正答率ではありません。

✓ ここまでの確認

模型では、制約ありの生成が1000回すべてパースできたのに、中身が正答と一致したのは17回でした。なぜですか。

抽象原理

形の約束にも例外があり、見え方が違う

形なら確実かというと、同じ文書が限定を付けています。冒頭の Always valid に対し、Invalid outputs の節は While structured outputs guarantee schema compliance in most cases, there are scenarios where the output may not match your schema: と書き、3つを挙げます。

Invalid outputs の3つ(Anthropic 文書、2026-09 時点)表
例外 stop_reason 返り方
拒否 (Refusals) refusal 200。スキーマに合わないことがある
上限到達 max_tokens 途中までで、合わないことがある
enum の大小文字 (特別な値なし) エラーなしで正常終了に見える

拒否は 200 で返ります。3つ目は no error and no special stop_reason で、文書自身が Compare enum values case-insensitively と受け手での対処を勧めています。

値の範囲はどうか。minimum や maxLength は非対応で、API に直接送ると 400 です。Python などの SDK は制約を外して送り、応答を元のスキーマで手元で検証します(your code still enforces all constraints through validation)。範囲の確かさは、SDK の中ですら部品の外側に書かれています。

具体実物

UNIQUE 制約は、破られると拒否として見える

別コースの第17期「一意性は、宣言したときだけ存在する」で見たとおり、表に UNIQUE を宣言すると、重複の確認は INSERT の時点でデータベースが行い、後から来た重複は文ごと失敗します。違反は呼んだ側にエラーとして見えます。

その UNIQUE も宣言した分しか守りません。PostgreSQL では既定で NULL どうしは等しいと見なされず、NULL を2回入れると2行入ります。その範囲は文書に書かれています。

構造化出力のスキーマも宣言です。違いは破れ方の見え方で、拒否は 200 で返り、enum の大小文字ずれは何の印もなく通ります。第14期「型は、線を越えない」で見た、線を越えると一致を担保する者がいない構図と同じで、形の約束の外に残る確かめは受け取る側のコードが引き受けます。

✓ ここまでの確認

文書が挙げる3つの例外のうち、`stop_reason` を見ても見分けられないのはどれですか。

⇄ 2つを見比べる
具体例 A

構造化出力のスキーマ

形は多くの場合スキーマどおりになるが、値はモデルの重みで決まる。拒否は 200 で返り、enum の大小文字ずれはエラーも特別な stop_reason も伴わない。

具体例 B

UNIQUE 制約

重複はデータベースが INSERT の時点で確かめ、文を失敗させる。守るのは宣言した分だけで、既定では NULL の重複は通る。

どちらも「宣言すれば守られる」と説明されがちです。何が違いますか。

宣言が守らなかったとき、呼んだ側に何が見えるかを比べてください。

この章のまとめ

  • 構造化出力が縛るのは 形 です。許された候補のどれを引くかは、モデルの重みのままです。
  • 模型では、パース 1000/1000 に対して中身の正答は 17/1000。形と値は別に数えます。
  • 2026-09 時点の Anthropic 文書は、冒頭の Always valid と、in most cases と例外3つの両方を書いています。
  • 拒否は 200、打ち切りは形も壊し、enum の大小文字ずれは 印なし で通ります。
  • UNIQUE は違反を拒否として見せます。構造化出力は見せません。確かめは受け取る側に書きます。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

Python SDK に `minimum: 100` 付きのスキーマを渡すと、2026-09 時点の文書ではどう扱われますか。

用語集(3語)
構造化出力 (structured outputs)
応答を指定した JSON スキーマに従わせる API の機能。Anthropic の文書は constrained decoding による、と書く。縛るのは形で、値の正しさは縛らない。
制約付きの生成
生成の各段で、文法が許さない語を候補から外してから引く考え方。Willard & Louf はこれで生成される文の構造を保証すると要旨に書く。
stop_reason
応答が止まった理由を示す欄。拒否は refusal、上限到達は max_tokens。enum の大小文字ずれは特別な値を持たない。