JSONを絶対に壊さない:LLMの出力構造化(Structured Outputs)を実現する文法制御デコーディングの仕組み

大規模言語モデル(LLM)をシステムに組み込む際、最も開発者を悩ませる問題の一つが「出力形式の不安定さ」だ。プロンプトで「JSONで出力してください」と指示しても、括弧が閉じられていなかったり、不要な解説テキストが混入したりすることで、プログラム側のパース処理がエラーを起こしてしまう。
この課題に対し、API提供会社やOSSコミュニティが導入した解決策が「Structured Outputs(構造化出力)」だ。今回は、モデルの確率制御レベルでJSONなどの文法を100%保証する「文法制御デコーディング」の仕組みを詳しく解説する。
なぜ従来のプロンプト指示では失敗するのか
従来のJSON出力対策は、主に「プロンプトの工夫(Few-Shotなど)」や、出力されたテキストを正規表現や別のLLMで「後処理(ポストプロセス)修正する」アプローチだった。
しかし、これらの方法には根本的な欠陥がある。
- 確率の揺らぎ: LLMは次に続くトークンを確率的に選択するため、どれほど厳しくプロンプトで指定しても、数千回に一回は文法違反(カンマの欠落、エスケープの失敗など)を犯す。
- コストと遅延: 後処理でエラーを検知してリトライを走らせると、APIのトークン消費量と待ち時間が倍増する。
したがって、後から直すのではなく、**「最初から不正なJSONを出力できないようにモデルの口を塞ぐ」**アプローチが必要となった。
文法制御デコーディング(Grammar-based Decoding)の仕組み
「構造化出力」の裏側で動いている技術の多くは、文法制御デコーディングと呼ばれる。これは、モデルが次のトークンを出力(サンプリング)する瞬間に介入する技術だ。
具体的には、以下の3つのステップで動作する。
graph TD
A[モデルが次に続く全トークンの確率を算出] --> B[指定されたJSON Schemaから状態遷移図 FSM を構築]
B --> C[現在の生成文字列に対して '次に出現してよい文字' を特定]
C --> D[それ以外のトークンの選択確率を -inf に書き換える]
D --> E[安全な候補の中から次のトークンをサンプリング]
1. スキーマから有限オートマトン(FSM)への変換
開発者が指定した JSON Schema や、EBNFなどの文法定義を解析し、文字単位での状態遷移図(FSM: Finite State Machine)を構築する。例えば、{"age": の後には必ず「数値の文字(0-9)」か「スペース」しか来られない、という状態遷移を定義する。
2. トークンマスク(Logits Masking)の適用
LLMは文字ではなくトークン単位で処理を行う。文法制御エンジンは、FSMに基づいて「次の文字として許容されるもの」を特定し、その文字から始まるトークンだけを候補として残す。許容されないトークンのロジット(確率の元データ)を -∞(マイナス無限大)に書き換えることで、確率をゼロにする。
3. サンプリングと出力
マスクされた確率分布からトークンを選択する。これにより、モデルはどれほど「間違ったJSON」を出力したくても、数学的に「正しいJSON」しか出力できないようになる。
主要な実装アプローチとライブラリ
現在、この技術は様々なレイヤーで実装されている。
- OpenAI API: 2024年に提供開始された「Structured Outputs」機能。ユーザーが定義したJSON Schemaとモデルの出力を完全に一致させる。内部的には、スキーマをFSMに事前ビルドし、高速にトークンマスクを適用する仕組みを実装している。
- Outlines: ローカルLLM開発で最も普及しているオープンソースライブラリ。vLLMやHugging FaceのTransformersと統合でき、正規表現やPydanticスキーマによる高速な文法制約デコーディングを提供する。インデックス構築を事前に行うことで、生成時のオーバーヘッドをほぼゼロに抑えている。
- llama.cpp: エッジ環境やローカルCPUでLLMを動かすためのフレームワーク。GBNF(Geronimo’s BNF)と呼ばれる独自のバッカス・ナウア表記法を用いて、推論時に高度な出力を強制できる。
開発者が知っておくべきトレードオフ
出力の正確性が100%になる一方で、文法制約デコーディングを導入する際には以下の点に留意する必要がある。
- 初回レスポンスの遅延(Cold Start): 非常に複雑なJSON Schemaを入力した場合、FSMの構築やインデックス変換に初回だけ数秒から数十秒の処理時間がかかることがある。
- ハルシネーションの性質変化: モデルが事実と異なる回答(ハルシネーション)をしようとした場合、文法が固定されているため、JSONの「キー(項目名)」は正しいが「値(中身)」がデタラメになる。文法の正しさは情報の正確性を保証しない。
お役立ち情報
- OpenAI - Structured Outputs Guide
- OpenAI APIにおける構造化出力の概要、設定方法、制限事項などを記載した公式ドキュメント(英語)。
- GitHub - outlines-dev/outlines
- ローカルでの構造化サンプリングをリードするOutlinesのGitHubリポジトリ。
- Zenn - OpenAI APIのStructured OutputsでJSON Schemaの構造を固定する
- 日本語でStructured Outputsの使い方や、実務でJSON出力の安定化をどう図るかを分かりやすく検証したブログ記事。