構造化出力(Structured Output)─ LLMから確実にJSONを取り出す
結論から言うと、**構造化出力(Structured Output)**を使えば、LLMのレスポンスをあらかじめ定義したJSONスキーマに沿って確実に受け取ることができる。エージェント開発でよくある「JSONのパースエラー」や「必須フィールドが欠けている」といった問題を根本的に解消できる。
なぜ構造化出力が必要なのか
エージェントが情報を抽出・分類する場合、後続の処理はLLMの出力を構造化データとして扱いたい。しかし通常のテキスト生成では「JSON形式で出力して」と指示しても、モデルが余計なコメントを付けたり、キーを省略したりすることがある。JSONパースが失敗するとエージェント全体が止まり、デバッグが難しくなる。
構造化出力は制約デコーディング(constrained decoding)という手法を内部で使い、スキーマに違反するトークンを生成段階でブロックする。Anthropicのドキュメントは「Always valid: No more JSON.parse() errors」と説明しており、スキーマに合ったJSONが必ず返ってくる保証が得られる。
JSONスキーマとは何か
JSONスキーマ(JSON Schema)はJSONドキュメントの構造を定義する語彙だ。type(データ型)、properties(フィールド定義)、required(必須フィールドの一覧)などのキーワードを使って「どんなデータを受け取りたいか」を記述する。
{
"type": "object",
"properties": {
"name": { "type": "string" },
"score": { "type": "integer" },
"passed": { "type": "boolean" }
},
"required": ["name", "score", "passed"],
"additionalProperties": false
}
このスキーマを渡すと、モデルは必ずname・score・passedの3フィールドを持つオブジェクトを返す。additionalProperties: falseを設定すると余分なフィールドも防げる。
使い方
ClaudeのAPIではoutput_config.formatにjson_schemaタイプのスキーマを渡すことで構造化出力が有効になる。
response = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "山田太郎(yamada@example.com)を抽出して"}],
output_config={"format": {"type": "json_schema", "schema": my_schema}}
)
PythonではPydanticモデルをmessages.parse()に渡すとスキーマ変換を自動で行える。TypeScriptではZodスキーマが使えるなど、各言語向けのSDKヘルパーが用意されている。
ツール使用との違い
tool_use(ツール呼び出し)でもJSONの引数を受け取れるが、目的が異なる。
| 構造化出力 | ツール使用 | |
|---|---|---|
| 目的 | 最終レスポンスをJSON化 | 外部ツールへの命令 |
| 実行主体 | アプリ側でデータを受け取るだけ | エージェントが外部関数を呼ぶ |
情報抽出・分類・変換など「結果をそのまま使いたい」場合は構造化出力、外部APIや計算処理を呼び出したい場合はツール使用が適切だ。両者は組み合わせて使うことも多い。
注意点
構造化出力にはいくつかの制限がある。再帰的なスキーマ(自分自身を参照する定義)や、minimum・maximumなどの数値制約はサポートされない。また、初回リクエスト時はグラマーのコンパイルに追加レイテンシが生じるとされるが、以降24時間はキャッシュされる。
モデルの**安全フィルター(リフューザル)**やmax_tokens超過が起きた場合は、スキーマ準拠が保証されない点も覚えておきたい。
まとめ
- 構造化出力は「スキーマに合ったJSONを必ず返す」保証を与え、エージェントの信頼性を高める
- JSONスキーマで
type・properties・requiredを定義するだけで使えるシンプルな仕組み - データ抽出・分類などの用途に最適で、ツール使用とは目的が明確に異なる
- 再帰スキーマや数値制約などの制限と、初回のコンパイルレイテンシに注意する