人気LLMプロバイダー(OpenAI、Gemini、Anthropic、Mistral、AWS Bedrock)間の構造化出力の比較

わずかに異なるAPIには、特別なアプローチが必要です。

目次

主要なLLMプロバイダーで構造化出力(信頼性の高いJSONの取得)を並べて比較したサポート状況と、最小限のPythonの例をご紹介합니다。

colorised bars standing

Ollama上でホストされているLLMからの構造化出力の要求方法については、すでに確認済みです。 JSONがネットワーク経由で返された後、堅牢なPythonによるLLM構造化出力の検証では、サービス内でのパース、Pydanticチェック、リトライ、テストの進め方について解説しています。 ここでは、他のプロバイダーから同じものを要求する方法についてレビューします。

要点まとめマトリクス

プロバイダー ネイティブ「JSONモード」 JSON スキーマの強制 一般的な設定項目 備考
OpenAI あり あり(第一級市民扱い) response_format={"type":"json_schema", ...} Responses API または Chat Completions で動作します。関数呼び出しも可能です。
Google Gemini あり あり(第一級市民扱い) response_schema= + response_mime_type="application/json" スキーマが設定されている場合、厳密に検証されたJSONを返します。
Anthropic (Claude) 間接的 あり(JSONスキーマを持つツール使用経由) tools=[{input_schema=...}] + tool_choice モデルにスキーマで定義されたツールの「呼び出し」を強制し、スキーマ形状の引数を取得します。
Mistral あり 部分的(JSONのみ。サーバーサイドでのスキーマなし) response_format={"type":"json_object"} JSONを確保しますが、スキーマへの検証はクライアントサイドで行います。
AWS Bedrock (プラットフォーム) モデルによって異なる あり(ツール/Converseスキーマ経由) toolConfig.tools[].toolSpec.inputSchema Bedrockの Converse API は、JSONスキーマに対してツール入力を検証します。

LLM 構造化出力 - 概要情報

LLM構造化出力とは、大規模言語モデル(LLM)が、フリーフォームのテキストを生成するのではなく、事前に定義された特定の形式や構造に厳密に従った応答を生成する能力を指します。この構造化出力は、JSON、XML、テーブル、テンプレートなどの形式であり、データを機械可読にし、一貫性を保ち、ソフトウェアによる簡単なパースを可能にして、さまざまなアプリケーションでの利用を容易にします。

構造化出力は、オープンエンドな自然言語テキストを生成する従来のLLM出力と異なります。代わりに、構造化出力はスキーマや形式(例えば、定義されたキーと値タイプを持つJSONオブジェクト、または出力内の特定のクラス(正解が一つのもの、感情クラス、データベース行形式など))を強制します。このアプローチは信頼性を向上させ、エラーとハルシネーション(幻覚)を削減し、データベース、API、ワークフローなどのシステムへの統合を簡素化します。

LLMにおける構造化出力の生成には、通常、以下の技術が関与します:

  • モデルが望ましい形式の出力を生成するように導くための詳細なプロンプト指示の指定。
  • 出力がスキーマに合致することを保証するために、PythonでPydanticなどの検証およびパースツールを使用すること。
  • 形式に対するトークンレベルの遵守を確保するために、文法や有限状態機に基づくデコード制約を適用すること(場合によっては)。

LLMの構造化出力の利点には以下が含まれます:

  • 機械可読性と統合の容易さ。
  • ばらつきとエラーの減少。
  • 一貫したデータ形式が求められるタスクにおける予測可能性と検証可能性の向上。

課題としては、効果的なスキーマの設計、複雑なネストされたデータとの取り扱い、フリーフォームのテキスト生成と比較した場合の推論能力の潜在的な限界などが挙げられます。

全体的に、構造化出力により、LLMは人間が読めるテキストだけでなく、正確で形式化されたデータが必要とされるアプリケーションにおいて、より有用になることが可能になります。

構造化出力 Pythonの例

すべてのスニペットは、イベント情報をJSONとして抽出します:{title, date, location}。キーやモデルはお好みに合わせて置き換えてください。

1) OpenAI — JSONスキーマ(厳密)

from openai import OpenAI
import json

client = OpenAI()

schema = {
    "name": "Event",
    "schema": {
        "type": "object",
        "properties": {
            "title": {"type": "string"},
            "date":  {"type": "string", "description": "YYYY-MM-DD"},
            "location": {"type": "string"}
        },
        "required": ["title", "date", "location"],
        "additionalProperties": False
    },
    "strict": True
}

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "user", "content": "Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}
    ],
    response_format={"type": "json_schema", "json_schema": schema},
)

data = json.loads(resp.choices[0].message.content)
print(data)

OpenAIの構造化出力機能は、サーバーサイドでこのスキーマを強制します。


2) Google Gemini — 応答スキーマ + JSON MIME

import google.generativeai as genai
from google.genai import types

# APIキーで構成
# genai.configure(api_key="your-api-key")

schema = types.Schema(
    type=types.Type.OBJECT,
    properties={
        "title": types.Schema(type=types.Type.STRING),
        "date": types.Schema(type=types.Type.STRING),
        "location": types.Schema(type=types.Type.STRING),
    },
    required=["title", "date", "location"],
    additional_properties=False,
)

resp = genai.generate_content(
    model="gemini-2.0-flash",
    contents="Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'",
    generation_config=genai.GenerationConfig(
        response_mime_type="application/json",
        response_schema=schema,
    ),
)

print(resp.text)  # スキーマに従った有効なJSON

Geminiは、response_schema に準拠する厳密なJSONを返します。


3) Anthropic (Claude) — JSONスキーマを持つツール使用

from anthropic import Anthropic
import json

client = Anthropic()

tool = {
    "name": "extract_event",
    "description": "Return event details.",
    "input_schema": {
        "type": "object",
        "properties": {
            "title": {"type": "string"},
            "date": {"type": "string"},
            "location": {"type": "string"}
        },
        "required": ["title", "date", "location"],
        "additionalProperties": False
    }
}

msg = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=256,
    tools=[tool],
    tool_choice={"type": "tool", "name": "extract_event"},  # スキーマを強制
    messages=[{"role": "user", "content":
        "Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}],
)

# Claudeはツールを「呼び出し」ます。引数(スキーマに一致します)を取得します
tool_use = next(b for b in msg.content if b.type == "tool_use")
print(json.dumps(tool_use.input, indent=2))

Claudeには一般的な「JSONモード」のスイッチはありません。代わりに、input_schema を持つツール使用により、検証済みでスキーマ形状の引数(その使用を強制することも可能)を取得できます。


4) Mistral — JSONモード(クライアントサイドでの検証)

from mistralai import Mistral
import json

client = Mistral()

resp = client.chat.complete(
    model="mistral-large-latest",
    messages=[{"role":"user","content":
        "Return JSON with keys title, date (YYYY-MM-DD), location for: "
        "'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}],
    response_format={"type": "json_object"}  # 有効なJSONを保証
)

data = json.loads(resp.choices[0].message.content)
print(data)
# ヒント: `data`をローカルでPydantic/JSONスキーマに対して検証します。

Mistralの json_object はJSONの形状(正確なスキーマではない)を強制します — クライアントサイドで検証してください。


5) AWS Bedrock — Converse API ツールスキーマ(モデル非依存)

import boto3, json

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0"

tools = [{
    "toolSpec": {
        "name": "extract_event",
        "inputSchema": {
            "json": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "date": {"type": "string"},
                    "location": {"type": "string"}
                },
                "required": ["title","date","location"],
                "additionalProperties": False
            }
        }
    }
}]

resp = bedrock.converse(
    modelId=model_id,
    toolConfig={"tools": tools},
    toolChoice={"tool": {"name": "extract_event"}},  # スキーマを強制
    messages=[{"role":"user","content":[{"text":
        "Extract the event from: 'PyData Sydney is on 2025-11-03 at Darling Harbour.'"}]}],
)

# toolUseコンテンツを取得します
tool_use = next(
    c["toolUse"] for c in resp["output"]["message"]["content"] if "toolUse" in c
)
print(json.dumps(tool_use["input"], indent=2))

BedrockはJSONスキーマに対してツール入力を検証し、多くのホスト済みモデル(例:Claude)はConverseを通じてこれをサポートしています。


実用的なガイダンスと検証

  • 最も強力なサーバーサイドの保証を望む場合: OpenAIの構造化出力またはGeminiの応答スキーマを使用してください。
  • すでにClaude/Bedrockを使っている場合: JSONスキーマを持つツールを定義し、その使用を強制してください。ツールの引数を型付きオブジェクトとして読み取ってください。
  • Mistralを使用している場合: json_object を有効にし、ローカルで検証します(例:Pydanticを使用)。

検証パターン(すべてに適用可能)

from pydantic import BaseModel, ValidationError

class Event(BaseModel):
    title: str
    date: str
    location: str

try:
    event = Event.model_validate(data)  # いずれかのプロバイダーからの `data`
except ValidationError as e:
    # e.errors() でモデルに修正を要求するなど、処理/リトライ
    print(e)

関連リンク

購読する

システム、インフラ、AIエンジニアリングの新記事をお届けします。