Structured Outputs
構造化出力では、デコード中にAIモデルの応答をJSON Schemaなどのスキーマに従わせるため、アプリケーションがすべての応答を確実に解析できます。
構造化出力とは、あらかじめ定義された形式に従うよう制約されたモデルの応答です。通常は、必須キー、その型、許可される値を指定するJSONスキーマを使い、ソフトウェアが返信を整形し直すことなく解析できるようにします。大規模言語モデル (LLM)に丁寧にJSONを求めると、通常は従いますが、説明文を追加したり、キーのスペルを間違えたり、オブジェクトの途中で出力を止めたりする可能性があり、そのどれもが返信を読むプログラムを壊します。構造化出力は、制約付きデコーディングによってこのリスクを取り除きます。生成の各ステップで、推論エンジンはスキーマに照らして出力の有効性を維持するトークンだけを許可します。OpenAIが2024年8月にAPIへこの機能を追加した際、構造化出力を使ったgpt-4o-2024-08-06は、複雑なJSONスキーマへの準拠評価で100%のスコアを達成したと報告しました。一方、構造化出力を使わないGPT-4(gpt-4-0613)は40%未満でした。
JSONモードと構造化出力の比較#
プロバイダーのAPIでは、2段階の保証が提供されています。OpenAIが2023年11月に導入したJSONモードが保証するのは、返信が構文的に有効なJSONであることだけであり、キーの欠落や誤記は起こり得ます。構造化出力では、返信が指定されたスキーマに一致することを保証します。OpenAIのStructured Outputs APIでは、開発者がPythonのPydanticモデルまたはJavaScriptのZodオブジェクトとしてスキーマを定義します。拒否応答は形式を崩さず、別のフィールドで返されます。AnthropicのClaude構造化出力は各スキーマを文法にコンパイルします。スキーマを初めて使う際はレイテンシーが増えますが、コンパイル済みの文法は24時間キャッシュされます。どちらのプロバイダーもJSON Schemaの一部しかサポートしていません。たとえばAnthropicでは、再帰スキーマやminimumなどの数値制限をサポートしていません。
制約付きデコーディングの仕組み#
各ステップで、LLMは語彙内のすべてのトークンにスコア、つまりロジットを付与し、デコーディングでは得られたソフトマックス分布から選択します。制約付きデコーディングでは、形式を壊すトークンすべてのスコアを事前にマイナス無限大に設定するため、それらのトークンは選択されません。Brandon WillardとRémi Loufは、「Efficient Guided Generation for Large Language Models」で、この処理を効率化する方法を示しました。正規表現またはJSON Schemaを有限状態機械にコンパイルし、語彙全体に対して一度だけ索引を構築して、各状態で有効なトークンを一覧化するため、各ステップでは検索だけで済みます。オープンソースのOutlinesライブラリがこの手法を実装しており、vLLMなどのサービングエンジンは、オープンモデル向けに文法誘導型の生成を提供しています。
以下の例では、Hugging Face TransformersのGPT-2にこの考え方を手作業で適用します。スキーマで固定されたテキスト、つまり波括弧、キー、引用符はコードで記述し、各値ではスキーマが許可するトークンだけをモデルが選べます。
import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")
prompt = "Review: The battery died after two days and support never replied.\nJSON:"
# 制約なしの貪欲デコーディング:返信をスキーマに従わせるものはありません
ids = tokenizer(prompt, return_tensors="pt").input_ids
free = model.generate(ids, max_new_tokens=16, do_sample=False, pad_token_id=tokenizer.eos_token_id)
print("free:", repr(tokenizer.decode(free[0, ids.shape[1] :])))
def choose(text, options):
"""Return the option GPT-2 scores highest after text, with every other token masked out."""
logits = model(tokenizer(text, return_tensors="pt").input_ids).logits[0, -1]
allowed = torch.tensor([tokenizer.encode(option)[0] for option in options])
masked = torch.full_like(logits, float("-inf"))
masked[allowed] = logits[allowed]
return tokenizer.decode(masked.argmax())
# 制約付きデコーディング:キーはスキーマで記述され、モデルが選べるのは許可された値だけです
with torch.no_grad():
text = prompt + ' {"sentiment": "'
text += choose(text, ["positive", "negative"]) + '", "stars": '
text += choose(text, ["1", "2", "3", "4", "5"]) + "}"
print("constrained:", json.loads(text[len(prompt) :]))free: ' The battery died after two days and support never replied.\n\nJSON: The'とconstrained: {'sentiment': 'positive', 'stars': 1}を出力します。制約がないと、GPT-2はJSONを書かずにレビューを繰り返しますが、制約付きのバージョンは常に解析できます。解析結果は、この手法の限界も示しています。1億2,400万パラメーターのモデルは、バッテリー切れとサポートからの返答がないことを肯定的に評価し、星を1つ付けました。スキーマが修正したのは形式であり、その中の値は依然としてモデル自身の判断に基づいています。
用途とトレードオフ#
- データ抽出: 請求書、契約書、メールから氏名、日付、合計、明細項目を直接データベースの行に取り込みます。
- 関数呼び出し: ツール呼び出しは、そのツールのパラメーターをスキーマとする構造化出力です。OpenAI APIとAnthropic APIの厳密なツール使用では、ツール引数にも同じ保証が適用されます。
- エージェント型ワークフロー: ルーティングの判断を列挙型の値として表し、プロンプトチェーンの各ステップ間では、中間結果を型付きオブジェクトとして渡します。
- 視覚言語モデル: 検出結果をラベルとボックス座標のリストとして返すため、描画コードや評価コードでそのまま利用できます。
Ultralytics Platformのクラスプロンプト型スマートアノテーションは、この最後の種類の出力を利用します。OpenAI、Anthropic、Googleなどのプロバイダーが提供するビジョンモデルに、データセットのクラスを検出するよう依頼し、返された各物体を編集可能なバウンディングボックスとして追加します。そのため、各応答ではクラス名と、コードで解析できる形式の座標を指定する必要があります。これらのモデルは信頼度スコアを返さないため、選択中はPlatformで信頼度とIoUの設定が非表示になります。VLM Leaderboardでは、実世界のデータセットでこれらのモデルがどれほど正確に物体を検出するかを比較し、各モデルのボックスをリスト内の順序で順位付けします。
厳密な形式は、推論タスクの精度を損なう可能性があります。「Let Me Speak Freely?」で、Zhi Rui Tamらは、形式の制約によってLLMの推論能力が大幅に低下し、制約が厳しいほど低下幅も大きくなることを発見しました。OpenAIのAPIはスキーマに記載された順序でキーを生成するため、よく使われる対処法は、回答フィールドより前に自由記述の推論フィールドを置き、制約付きの回答を確定する前にモデルが思考の連鎖を書けるようにすることです。










