
**LLM構造化出力(Structured Output)**は、言語モデルの応答が事前定義されたスキーマに準拠することを保証する仕組みです。単に有効なJSONであるだけでなく、指定した正確なフィールド、型、制約を持つスキーマ準拠のJSONであることを意味します。主要なプロバイダーはすべてこれをネイティブでサポートしており、本番環境向けのLLMアプリケーションの構築方法を一変させました。
クイックサマリー:構造化出力の概要
時間がない方のために、2026年の現状をまとめます。
| 側面 | 詳細 |
|---|---|
| 何なのか | LLMからのスキーマ強制応答。「ベストエフォート」ではなく、構造が保証される |
| サポートしているプロバイダー | OpenAI、Anthropic、Gemini、Cohere、xAI (Grok)、さらにOllama/vLLM経由のローカル実行 |
| 主要な仕組み | 制約付きデコーディング。サンプリング前に不正なトークンをマスクする |
| JSON Mode vs Strict Mode | JSON Mode = 構文的に有効なJSONのみ。Strict Mode = スキーマ完全準拠 |
| Pythonライブラリ | スキーマ定義には Pydantic (BaseModel + Field) |
| TypeScriptライブラリ | スキーマ定義には Zod (z.object + .describe) |
| 最適な入門アプローチ | ネイティブSDK経由で Pydantic または Zod を使用する OpenAI |
| 最適な本番用ライブラリ | Instructor (Python) または ネイティブSDK (TypeScript) |
| 最大の落とし穴 | 回答フィールドの後に推論フィールドを配置すること。モデルが思考前に決定を下してしまう |
| レイテンシオーバーヘッド | 初回呼び出しで50-200ms(スキーマコンパイル)、以降はキャッシュされる |
では、各要素を詳しく見ていきましょう。
LLM構造化出力とは?
構造化出力は、LLMが有効なJSONを返すことを願うことと、それを保証することの違いです。構造化出力を有効にすると、モデルは物理的にスキーマに違反するトークンを生成できなくなります。JSON Schema(またはPydanticモデル、Zodスキーマ)を定義し、APIに渡すと、毎回それに一致する応答が返ってきます。
なぜこれが重要なのでしょうか?構造化出力以前、開発者は脆弱な正規表現パーサーを書き、すべてのLLM呼び出しを try/catch JSON.parse ブロックで囲み、それでも「ほぼ正解」の応答(有効なJSONだがフィールドが欠けていたり型が間違っていたりするもの)に対処していました。そのようなバグのクラスは完全に消滅しました。
構造強制には3つのレベルがあり、明確な進化を表しています。
- プロンプトエンジニアリング、「これらのフィールドを含むJSONを返してください」。信頼性は低く、モデルは80〜90%の確率でしか従いません。
- JSON Mode、構文的に有効なJSONであることを保証しますが、スキーマは強制しません。
{"name": string, "age": number}を期待しているときに{"foo": "bar"}が返ってくる可能性があります。 - Strict Mode / 制約付きデコーディング、100%のスキーマ準拠を保証します。モデルは文字通り不正なトークンを出力できません。これが2026年における「構造化出力」の意味です。
2026年初頭現在、OpenAI、Anthropic、および Google Gemini はすべてネイティブな構造化出力をサポートしています。エコシステムは収束しました。
結論:本番環境で正規表現や JSON.parse を使ってLLM応答を解析しているなら、難しいやり方をしています。 ネイティブな構造化出力はその失敗モード全体を排除します。
JSON Mode vs Strict Mode:実際は何が変わったのか?
名前が似ているため、多くの開発者がこの区別で混乱します。しかし、これらは別物です。
| 機能 | JSON Mode | Strict Mode (構造化出力) |
|---|---|---|
| APIパラメータ | type: "json_object" | strict: true を伴う type: "json_schema" |
| 有効なJSONを保証 | はい | はい |
| スキーマ準拠を保証 | いいえ | はい |
| 仕組み | 事後のトークンバイアス | 制約付きデコーディング (FSM) |
| 予期しないフィールドを返す可能性 | あり | なし |
| 必須フィールドを省略する可能性 | あり | なし |
| 型強制 | なし | 完全 (string, number, array など) |
| 使用すべき場面 | 事前にスキーマを持っていない場合 | 本番環境でのすべて |
タイムライン: OpenAIは2023年後半にJSON Modeを導入しました。これは前進でしたが、開発者はすぐに「有効なJSON」だけでは不十分であり、スキーマ準拠のJSONが必要だと気づきました。2024年8月、OpenAIはStrict Modeを搭載した構造化出力をリリースし、制約付きデコーディングを使用してスキーマ準拠を保証しました。2025年から2026年にかけて、主要なプロバイダーはすべて同じアプローチを採用しました。
JSON Modeには依然として狭いユースケースがあります。応答の形状を事前に全く知らず、非構造的な探索のために何らかの有効なJSONが欲しい場合です。しかし、本番環境では稀です。
結論:本番環境でのすべてにStrict Modeを使用してください。 スキーマ縛りのユースケースにおいて、JSON Modeは事実上廃止されています。スキーマがある場合(そしてあるべきです)、strict: true で type: "json_schema" を使用してください。
制約付きデコーディングは実際にどのように機能するのか?
99.9%ではなく、文字通り100%のスキーマ準拠を可能にする仕組み here です。
Strict Modeを有効にしてプロバイダーにJSON Schemaを送信すると、そのスキーマは**有限状態機械(FSM)**にコンパイルされます。このFSMは、スキーマを通るすべての有効なパスを表します。各トークン生成ステップで、推論エンジンはどのトークンが出力を有効なパス上に留め、どれが留めないかをチェックします。不正なトークンのロジットはサンプリング前に負の無限大に設定されるため、選択される確率はゼロになります。
<!-- IMAGE: constrained-decoding diagram showing FSM token masking during structured output generation -->これは強化版のオートコンプリートのようなものだと考えてください。モデルが {"rating": を出力し、スキーマで rating が整数であると指定されている場合、次に許可されるトークンは数字トークンだけです。引用符、文字、括弧などはすべてマスクされます。モデルが「そうしたい」と思っても、"five" を出力することはできません。
これは、XGrammar(vLLM、SGLang、およびほとんどのローカル推論サーバーの背後にあるエンジン)や Outlines(制約付き生成用のオープンソースPythonライブラリ)で使用されているのと同じコアメカニズムです。APIプロバイダーはこれを推論インフラストラクチャに組み込んだだけです。
知っておくべきトレードオフが1つあります。新しいスキーマでの最初のリクエストでは、FSMが構築される間にコンパレーションレイテンシの影響(通常50-200ms)を受けます。同じスキーマでの subsequent リクエストはキャッシュされたFSMを使用し、オーバーヘッドはほぼゼロです。また、微妙な品質に関する考慮事項もあります。トークン語彙を制限すると、創造的または自由形式のフィールドで出力品質が低下することがあるため、スキーマは真に構造化されたデータに焦点を当ててください。
結論:制約付きデコーディングこそが、「通常は機能する」と「常に機能する」を分けるものです。 これが構造化出力を生産準備完了にするエンジニアリングです。
マルチプロバイダー実装:OpenAI、Anthropic、Gemini
他のガイドでは示されていないことがあります。3つの主要プロバイダーすべてで実装された同じ抽出タスクです。非構造化テキストから構造化された製品レビューを抽出します。
すべてのプロバイダーで共有されるPydanticスキーマ:
from pydantic import BaseModel, Field
from typing import Literal
class ProductReview(BaseModel):
reasoning: str = Field(description="Think through the review before scoring")
rating: int = Field(description="Rating from 1-5", ge=1, le=5)
sentiment: Literal["positive", "negative", "neutral"]
pros: list[str] = Field(description="Key positive points")
cons: list[str] = Field(description="Key negative points")
summary: str = Field(description="One-sentence summary")OpenAIの実装
from openai import OpenAI
client = OpenAI()
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "Extract a structured review from the text."},
{"role": "user", "content": review_text}
],
response_format=ProductReview, # Pydantic model directly
)
review = response.choices[0].message.parsed # Typed ProductReview objectOpenAIの実装は最も成熟しています。parse() メソッドはPydanticモデルを直接受け取り、型付けされたオブジェクトを返します。1つの制約:OpenAIの Strict ModeはJSON Schemaのサブセットをサポートしています。$ref はなく、anyOf は制限されており、すべてのフィールドは additionalProperties: false で必須である必要があります。
Anthropicの実装
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": f"Extract a structured review:\n\n{review_text}"}
],
output_config={
"format": {
"type": "json_schema",
"json_schema": ProductReview.model_json_schema()
}
}
)
import json
review_data = json.loads(response.content[0].text)
review = ProductReview(**review_data)Anthropicのネイティブ構造化出力は、JSON Schemaとともに output_config.format を使用します。これは2026年初頭にGA(一般提供)になりました。Anthropicはまた、「フェイク」ツールを定義し tool_use 経由で抽出する古いパターンもサポートしていますが、純粋な抽出にはネイティブ構造化出力の方がクリーンです。
Geminiの実装
from google import genai
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=f"Extract a structured review:\n\n{review_text}",
config={
"response_mime_type": "application/json",
"response_schema": ProductReview, # Pydantic model directly
}
)
import json
review = ProductReview(**json.loads(response.text))GeminiはPython SDKで response_schema 経由でPydanticモデルを直接サポートしています。独自の機能:Geminiはスキーマ内の propertyOrdering を尊重するため、フィールドの出力順序を制御できます(推論優先パターンに有用)。
プロバイダー比較
| 機能 | OpenAI | Anthropic | Gemini |
|---|---|---|---|
| APIパラメータ | response_format | output_config.format | response_schema |
| スキーマ入力 | Pydantic または JSON Schema | JSON Schema | Pydantic または JSON Schema |
| Strict mode | strict: true | json_schema で暗黙的 | 暗黙的 |
| ストリーミング | はい(部分的なJSON) | はい | はい |
| 拒否処理 | message.refusal フィールド | エラーレスポンス | エラーレスポンス |
| ツール使用の代替 | はい | はい(元の手法) | はい |
| スキーマコンパイルキャッシュ | はい(サーバーサイド) | はい | はい |
| プロパティ順序付け | ネイティブサポートなし | なし | はい (propertyOrdering) |
結論:OpenAIは parse() メソッドにより最も洗練されたDXを提供します。Anthropicは最も有能な基盤モデルを提供します。Geminiのプロパティ順序付けは uniquely 有用です。 3つとも仕事はこなしますので、既存のプロバイダーとの関係に基づいて選択してください。
Python開発者向けPydanticパターン
Pydantic は、Pythonで構造化出力スキーマを定義するための事実上の標準です。重要なパターンは以下の通りです。
説明付きの基本スキーマ
from pydantic import BaseModel, Field
from typing import Literal, Optional
class ExtractedEntity(BaseModel):
reasoning: str = Field(description="Think step by step about the entity")
name: str = Field(description="Full name of the entity")
entity_type: Literal["person", "company", "location"]
confidence: float = Field(description="Confidence score 0.0-1.0", ge=0.0, le=1.0)
context: Optional[str] = Field(description="Surrounding context, if relevant")これらの description 文字列は単なるドキュメントではありません。これらはモデルに送信されるJSON Schemaの一部となり、モデルが生成する内容に直接影響を与えます。これらをスキーマ 内 の プロンプトエンジニアリング と考えてください。
ネストされたモデル
class Address(BaseModel):
street: str
city: str
country: str
postal_code: Optional[str] = None
class Company(BaseModel):
reasoning: str = Field(description="Analysis of the company details")
name: str
industry: Literal["tech", "finance", "healthcare", "retail", "other"]
headquarters: Address # Nested model
key_products: list[str] = Field(description="Top 3 products or services")ネストは最大2〜3レベルに抑えてください。深くネストされたスキーマはエラー率を増加させ、スキーマコンパイルを遅くします。
推論優先パターン
これは最も影響力のあるスキーマ設計パターンです。回答フィールドの 前 に reasoning フィールドを配置します。
# Reliable JSON from Any LLM: Pydantic + Zod Patterns for 2026
class ClassificationBad(BaseModel):
category: Literal["spam", "ham"]
confidence: float
# Good -- model reasons through the problem first
class ClassificationGood(BaseModel):
reasoning: str = Field(description="Analyze the text before classifying")
category: Literal["spam", "ham"]
confidence: float = Field(ge=0.0, le=1.0)LLMはトークンを左から右へ生成します。category が最初に来ると、モデルはカテゴリを選び、その後でそれを合理化します。reasoning が最初に来ると、モデルは問題を解決し、その後 でカテゴリを確定します。これはスキーマに組み込まれたChain-of-Thoughtです。
JSON Schemaエクスポート
# Generate the JSON Schema for any Pydantic model
schema = ProductReview.model_json_schema()
# Pass this to any provider that accepts raw JSON Schema結論:Pydantic + 記述的なフィールド + 推論優先の順序付けは、Python構造化出力の三拍子です。 これら3つのパターンをマスターすれば、90%のユースケースに対応できます。
TypeScript開発者向けZodパターン
Zod はTypeScript版のPydanticであり、構造化出力ワークフローにおいて同样に中心的な存在です。
説明付きの基本スキーマ
import { z } from "zod";
const ProductReview = z.object({
reasoning: z.string().describe("Think through the review before scoring"),
rating: z.number().int().min(1).max(5),
sentiment: z.enum(["positive", "negative", "neutral"]),
pros: z.array(z.string()).describe("Key positive points"),
cons: z.array(z.string()).describe("Key negative points"),
summary: z.string().describe("One-sentence summary"),
});
// Infer the TypeScript type automatically
type ProductReview = z.infer<typeof ProductReview>;Pydanticの Field(description=...) と同様に、Zodの .describe() はJSON Schemaの一部となり、モデルの出力を導きます。
OpenAI Node SDKとの統合
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
const client = new OpenAI();
const response = await client.beta.chat.completions.parse({
model: "gpt-4o-2024-08-06",
messages: [
{ role: "system", content: "Extract a structured review." },
{ role: "user", content: reviewText },
],
response_format: zodResponseFormat(ProductReview, "product_review"),
});
const review = response.choices[0].message.parsed; // Typed!Vercel AI SDKとの統合
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
const { object: review } = await generateObject({
model: openai("gpt-4o"),
schema: ProductReview,
prompt: `Extract a structured review:\n\n${reviewText}`,
});
// review is fully typed as ProductReviewVercel AI SDK は generateObject() でZodをネイティブに使用し、最もクリーンなTypeScript統合を実現しています。統一されたAPIを通じて、OpenAI、Anthropic、Gemini、および他のプロバイダーで動作します。
JSON Schema変換
import { zodToJsonSchema } from "zod-to-json-schema";
const jsonSchema = zodToJsonSchema(ProductReview);
// Use with any provider that accepts raw JSON Schema結論:Zod + .describe() + Vercel AI SDKは、TypeScript構造化出力スタックです。 Node/Next.jsエコシステムにいる場合、これが最小抵抗の経路です。
構造化出力 vs 関数呼び出し:いつどちらを使うか?
これは最も一般的な混乱の原因の一つです。どちらもスキーマ涉及し、構造化データを返しますが、異なる問題を解決します。
構造化出力は言います:「この正確な形状でデータを私にください。」これは抽出、分類、フォーマット用です。非構造化テキストから構造化情報を引き出しています。
**関数呼び出し(ツール使用)**は言います:「ここに取れるアクションがあります。どれを実行するか決定し、引数を提供してください。」これは、モデルが複数のツールから選び、アクションをトリガーするエージェントワークフロー用です。
歴史的に混乱するのは理解できます。Anthropicの元の「構造化出力」は文字通り関数呼び出しでした。extract_review というフェイクツールを定義し、引数を取得していました。それはまだ機能しますが、純粋な抽出にはネイティブ構造化出力の方がシンプルです。
| シナリオ | ベストアプローチ | 理由 |
|---|---|---|
| テキストからデータを抽出 | 構造化出力 | 直接的、低レイテンシ、単一スキーマ |
| カテゴリに分類 | 構造化出力 | 1回の応答、1つのスキーマ |
| どのツールを呼び出すか決定するエージェント | 関数呼び出し | モデルが複数のツールから選択 |
| マルチステップオーケストレーション | 関数呼び出し | 順次ツール呼び出し |
| データ抽出 AND 次のアクション決定 | 両方 | 抽出には構造化出力、オーケストレーションには関数呼び出し |
構造化出力は、AIエージェントシステム内のツール呼び出しパイプラインを動力源としています。これらが本番ワークフローにどのように適合するかについては、ビジネス向けAIエージェントガイドをご覧ください。
結論:データの形状がわかっている場合は構造化出力を使用してください。モデルがアクションを選択する必要がある場合は関数呼び出しを使用してください。 実際には、ほとんどのアプリケーションで両方を使用します。データ抽出には構造化出力、エージェントオーケストレーションには関数呼び出しです。
本番パターン:エラー、リトライ、ストリーミング
デモで構造化出力を動作させるのは簡単です。本番環境で信頼性を維持するには、拒否、検証失敗、ストリーミングの3つを処理する必要があります。
拒否処理
場合によっては、モデルが要求された出力の生成を拒否することがあります。通常、安全性フィルターが入力をフラグ立てたためです。この場合、構造化出力APIはスキーマを返しません。拒否を返します。
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
# ALWAYS check for refusal before accessing parsed content
if response.choices[0].message.refusal:
print(f"Model refused: {response.choices[0].message.refusal}")
else:
review = response.choices[0].message.parsed拒否チェックをスキップして拒否に対して .parsed にアクセスしようとすると、None が返され、下流で混乱するエラーが発生します。必ず最初にチェックしてください。
検証フィードバック付きリトライパターン
スキーマ準拠は制約付きデコーディングによって保証されますが、意味的 正しさは保証されません。モデルは {"rating": 1, "sentiment": "positive"} を返すかもしれません。有効なスキーマですが、矛盾した内容です。そこで検証+リトライが登場します。
import instructor
client = instructor.from_openai(OpenAI())
# Instructor handles retries automatically
review = client.chat.completions.create(
model="gpt-4o",
response_model=ProductReview,
max_retries=3, # Retries with validation error feedback
messages=[
{"role": "user", "content": review_text}
],
)Instructor はリトライ時に検証エラーをモデルにフィードバックするため、自己修正が可能です。Instructorを使わない手動リトライパターンの場合:
from pydantic import ValidationError
for attempt in range(3):
try:
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
review = response.choices[0].message.parsed
# Run additional semantic validation here
break
except ValidationError as e:
messages.append({"role": "assistant", "content": str(response)})
messages.append({"role": "user", "content": f"Validation error: {e}. Fix it."})構造化出力のストリーミング
大規模な構造化応答、長い配列、多数のフィールド、複雑なネストされたオブジェクトの場合、ストリーミングにより部分的な結果を段階的にレンダリングできます。
import instructor
client = instructor.from_openai(OpenAI())
# Stream partial results as fields populate
review_stream = client.chat.completions.create_partial(
model="gpt-4o",
response_model=ProductReview,
messages=[{"role": "user", "content": review_text}],
)
for partial_review in review_stream:
# Fields populate one by one as tokens stream in
if partial_review.summary:
print(f"Summary so far: {partial_review.summary}")1つの落とし穴:個々のストリーミングチャンク自体はスキーマ準拠ではありません。rating がまだ None の間に reasoning フィールドが入力されている可能性があります。 accordingly UIを計画し、未入力フィールドにはローディング状態を表示してください。
結論:拒否チェックは必須です。検証フィードバック付きリトライは意味的エラーを検出します。数秒以上かかる応答にはストリーミングの価値があります。
構造化出力ライブラリの比較
ネイティブAPIを通じて構造化出力を使用できますが、ライブラリは検証、リトライ、ストリーミング、マルチプロバイダーサポートを追加します。現状は以下の通りです。
Instructor は11K以上のGitHubスターと月間300万を超えるダウンロード数を誇る最も人気のあるオプションです。OpenAI、Anthropic、Gemini、Cohere、Ollamaなどを統一されたPydanticベースのインターフェースでラップします。主要な機能:検証フィードバック付き自動リトライ、create_partial() によるストリーミング、そして極めてシンプルなセットアップ(instructor.from_openai(client))。Pythonチームならここから始めてください。
BAML は異なるアプローチを取ります。カスタムDSLによるスキーマファーストです。.baml ファイルでスキーマを定義し、Python、TypeScript、Rubyなどのクライアントを自動生成します。そのSAP(スキーマ整合パーシング)アルゴリズムは、 messy なモデル出力を gracefully に処理します。クロスランゲージチームや、LLM層とアプリケーション層の間の契約が必要な場合に最適です。トレードオフ:追加のビルドステップと新しい構文の学習が必要です。
LangChain は、プロバイダーに依存しない構造化出力のために .with_structured_output(schema) を提供します。すでにLangChainエコシステムにいる場合は便利です。トレードオフ:重い依存関係であり、抽象化が必要なプロバイダー固有の機能を隠してしまう可能性があります。
ネイティブAPI、response_format / output_config を直接使用する呼び出しは、プロバイダーSDK以外の依存関係を必要としません。完全な制御と可視性が得られます。シンプルなユースケースや、最小限の抽象化を好むチームに最適です。
| ライブラリ | 言語 | プロバイダー | 自動リトライ | ストリーミング | GitHubスター | 学習曲線 |
|---|---|---|---|---|---|---|
| Instructor | Python, TS | 15+ | はい | はい | 11K+ | 低 |
| BAML | Python, TS, Ruby, Go | すべて (DSL非依存) | はい | はい | 7K+ | 中 |
| LangChain | Python, TS | 20+ | 部分的 | はい | 100K+ | 中〜高 |
| ネイティブAPI | 任意 | SDKごと1つ | なし | はい | N/A | 低 |
適切な構造化出力ライブラリの選択は、より広範なAIスタック決定の一部です。フルスタックの詳細は SaaS向け最佳AIスタックガイド で解説しています。
Instructor、BAML、Mirascopeなどの詳細な比較については、近日公開予定の LLM構造化出力向け最佳ライブラリ をご覧ください。
結論:PythonではInstructorから始め、TypeScriptではネイティブAPIを使用してください。 クロスランゲージスキーマ契約が必要な場合はBAMLに移行してください。構造化出力のためだけにLangChainを使用するのは避けましょう。過剰仕様です。
スキーマ設計のベストプラクティス(および一般的な間違い)
スキーマ設計は出力品質に直接影響します。重要なパターンと、精度を損なう間違いは以下の通りです。
回答の前に推論を配置する
Pydanticのセクションで触れましたが、これが最も影響の大きい設計決定であるため繰り返します。
# Before: model guesses the answer, then rationalizes
class Bad(BaseModel):
answer: str
reasoning: str
# After: model thinks first, then commits
class Good(BaseModel):
reasoning: str = Field(description="Think step by step")
answer: strLLMは左から右へ生成します。フィールド順序はプロンプト順序です。推論を最初に配置することで、モデルは回答を確定する前に問題を解決しなければなりません。
アンチパターン表
| 間違い | 問題 | 修正 |
|---|---|---|
| 回答フィールドの後の推論フィールド | 思考前にモデルが決定 | 推論を回答の前に移動 |
| 深いネスト(4レベル以上) | エラー率上昇、コンパイル遅延 | 2〜3レベルに平坦化 |
| フィールド説明なし | モデルが意図を推測 | .describe() / Field(description=...) を追加 |
| null処理の欠如 | モデルがフィールドを埋めるために値を幻覚 | Optional / .nullable() を使用 |
| 過度に大きなスキーマ(50+フィールド) | コンパイルタイムアウト、品質低下 | 複数の呼び出しに分割 |
| 曖昧なenumオプション | モデルが間違ったカテゴリを選択 | 具体的で重複しないオプションを使用 |
nullを明示的に処理する
ソーステキストにデータがない可能性があるフィールドは、オプションにしてください。データが存在しない場合に必須フィールドを強制すると、幻覚につながります。
class PersonInfo(BaseModel):
name: str # Always present
email: Optional[str] = Field(None, description="Email if mentioned, null otherwise")
phone: Optional[str] = Field(None, description="Phone if mentioned, null otherwise")スキーマを集中させる
1タスクにつき1スキーマ。単一の巨大なスキーマですべてを抽出しようとしないでください。50以上のフィールドが必要な場合は、複数の抽出呼び出しに分割してください。OpenAIのStrict Modeにはスキーマの複雑さに対する実用的な限界があり、機能した場合でも、非常に大きなスキーマは出力品質を低下させます。
結論:推論優先、記述的なフィールド、明示的なnull、集中したスキーマ。 この4つを正しく行えば、構造化出力の精度は測定可能なほど向上します。
ローカルLLMでの構造化出力
構造化出力のためにAPIプロバイダーは必要ありません。ローカル推論エンジンは、独自のハードウェア上で実行される同じ根本的なメカニズムである、文法ベースの制約付きデコーディングを通じてこれをサポートします。
Ollama
ローカル構造化出力への最も簡単な道。Ollama は format パラメータ経由でJSON Schemaを受け入れます。
import ollama
from pydantic import BaseModel
class Country(BaseModel):
name: str
capital: str
languages: list[str]
response = ollama.chat(
model="llama3.2",
messages=[{"role": "user", "content": "Tell me about Japan."}],
format=Country.model_json_schema(),
)
import json
country = Country(**json.loads(response.message.content))Ollamaは内部で制約付きデコーディングのために XGrammar を使用しています。APIプロバイダーと同じ保証:100%のスキーマ準拠。
vLLM と SGLang
本番グレードのローカル推論の場合、vLLM と SGLang はどちらも guided_json および guided_regex パラメータを通じて構造化出力をサポートしています。XGrammarはデフォルトのバックエンドであり、JSON生成におけるオーバーヘッドはほぼゼロで、代替の文法エンジンよりも最大3.5倍高速です。
Outlines
Outlines は、文法ベースの制約付き生成を開拓したオープンソースPythonライブラリです。任意のHugging Faceモデルで動作し、JSON Schema、正規表現、および完全な文脈自由文法(CFG/EBNF)制約をサポートします。また、文法バックエンドオプションとしてvLLMおよびSGLangにも統合されています。
APIプロバイダーとの主な違い:ローカル構造化出力には スキーマサブセットの制限がありません 。文法を完全に制御できます。ただし、モデル品質はより変動し、7Bパラメータのローカルモデルは複雑な抽出タスクでGPT-4oやClaudeに匹敵しません。スキーマは常に有効ですが、コンテンツ の品質はモデルに依存します。
結論:開発にはOllama、本番にはXGrammar搭載のvLLM/SGLang。 ローカル構造化出力はほとんどのユースケースに対応できるほど成熟していますが、小さいモデルはスキーマ内で低品質のコンテンツを生成するという注意点があります。
FAQ
LLMにおける構造化出力とは何ですか?
構造化出力は、LLMの応答が事前定義されたJSON Schemaに準拠することを保証するメカニズムです。プレーンテキストやJSON Modeとは異なり、構造化出力は制約付きデコーディングを使用して、スキーマ内のすべてのフィールド、型、制約が満たされることを保証します。「通常」ではなく、100%の時間です。
JSON Modeと構造化出力の違いは何ですか?
JSON Modeは構文的に有効なJSONを保証しますが、スキーマは強制しません。任意の有効なJSONオブジェクトが返される可能性があります。構造化出力(Strict Mode)は、制約付きデコーディングを通じて完全なスキーマ準拠を保証します。本番環境にはStrict Modeを使用してください。JSON Modeは、事前にスキーマを持っていない場合のみ関連します。
どのLLMプロバイダーがネイティブで構造化出力をサポートしていますか?
OpenAI(2024年8月以降)、Google Gemini(2024年、2026年に拡張)、Anthropic(2025年11月ベータ、2026年初頭GA)、Cohere、およびxAI(Grok)はすべてネイティブな構造化出力をサポートしています。ローカル側では、Ollama、vLLM、およびSGLangが文法ベースの制約付きデコーディングを通じてこれをサポートしています。
制約付きデコーディングはどのようにスキーマ準拠を保証しますか?
JSON Schemaは有限状態機械(FSM)にコンパイルされます。各トークン生成ステップで、FSMを通る有効なパス上に出力を留めるトークンだけが許可され、不正なトークンのロジットは負の無限大に設定されます。这意味着不正なトークンが生成される確率はゼロであり、統計的な保証ではなく数学的な保証が得られます。
構造化出力と関数呼び出しのどちらを使用すべきですか?
特定の形状のデータを望む場合、抽出と分類には構造化出力を使用してください。モデルが取るアクションを決定する必要がある場合、エージェントワークフローには関数呼び出しを使用してください。多くの本番アプリケーションでは両方を使用します。データ抽出には構造化出力、オーケストレーションには関数呼び出しです。
構造化出力をストリーミングできますか?
はい。OpenAIは parse() メソッドでストリーミングをサポートし、Instructorはフィールドごとに populate されるPydanticモデルのストリーミングのために create_partial() を提供します。個々のストリーミングチャンクは個別にはスキーマ準拠ではないことに注意してください。フィールドは漸進的に入力されます。
Instructorライブラリとは何ですか?
Instructorは最も人気のある構造化出力ライブラリです(11K以上のGitHubスター、月間300万を超えるダウンロード数)。プロバイダーSDKをPydanticベースの検証、検証フィードバック付き自動リトライ、およびストリーミングサポートでラップします。OpenAI、Anthropic、Gemini、Cohere、Ollama、および10以上の他のプロバイダーで動作します。
構造化出力はローカルLLMで動作しますか?
はい。OllamaはJSON Schema付きの format パラメータ経由で構造化出力をサポートしています。vLLMとSGLangは guided_json パラメータを通じてこれをサポートしています。3つすべてが制約付きデコーディングのためにXGrammarまたはOutlinesを使用しています。スキーマ準拠の保証はAPIプロバイダーと同じです。コンテンツ品質はモデルに依存します。
一般的なスキーマ設計の間違いは何ですか?
主な間違い:回答フィールドの後に推論フィールドを配置すること(モデルが思考前に決定)、深くネストされたスキーマ(4レベル以上はエラーを増加)、フィールド説明の欠如(モデルが意図を推測)、オプションデータのnull処理なし(幻覚を強制)、過度に大きなスキーマ(50以上のフィールドは品質を低下)。
構造化出力はレイテンシを追加しますか?
最初のリクエストにはスキーマコンパイルのオーバーヘッドがあり、通常FSMが構築される間に50-200msかかります。同じスキーマでの subsequent リクエストはキャッシュされたFSMを使用し、レイテンシはほぼゼロです。ほとんどのアプリケーションにとって、これは全体のモデル推論時間と比較して無視できます。
構造化出力を画像やマルチモーダル入力で使用できますか?
はい。構造化出力は 応答 形式に適用され、入力には適用されません。構造化出力スキーマ付きでGPT-4oやGeminiに画像を送信し、画像のスキーマ準拠分析を受け取ることができます。これは領収書、フォーム、または製品画像から構造化データを抽出する視覚的抽出ワークフローに強力です。