主要なLLMプロバイダー間における構造化出力の比較 - OpenAI、Gemini、Anthropic、Mistral、およびAWS Bedrock

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

目次

主要なLLMプロバイダにおける構造化出力(信頼性の高いJSONの取得)のサポート状況を並べて比較し、最小限のPythonコード例を掲載します。

colorised bars standing

以前、OllamaでホストされたLLMからの構造化出力のリクエスト方法について見てみました。 JSONが返ってきた後、Pythonでの堅牢なLLM構造化出力の検証では、パース、Pydanticによるチェック、リトライ、およびサービス内のテストについて解説しています。 ここでは、他のプロバイダから同様の出力をリクエストする方法を解説します。

TL;DR 比較表

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

LLM構造化出力 - 概要

LLM構造化出力とは、大規模言語モデル(LLMs)が、自由形式のテキストを生成するのではなく、事前に定義された特定の形式または構造に厳密に従ってレスポンスを生成する機能のことです。この構造化出力はJSON、XML、テーブル、テンプレートなどの形式を取り得るため、データは機械可読性が高まり、一貫性があり、各種アプリケーションでの使用のためにソフトウェアによって容易にパースできます。

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

LLMにおける構造化出力の生成には、一般的に以下の技術が含まれます:

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

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

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

課題としては、効果的なスキーマの設計、複雑なネストされたデータの処理、および自由形式のテキスト生成と比較した場合の推論能力の潜在的な限界が挙げられます。

総じて、構造化出力は、人間が読めるテキストだけでなく、正確でフォーマットされたデータを必要とするアプリケーションにおいて、LLMをより有用なものにします。

構造化出力のPython例

以下のすべてのスニペットは、イベント情報をJSONとして抽出します:{title, date, location}。キーやモデルは適宜変更してください。

1) OpenAI — JSON Schema(厳格)

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スキーマを用いたTool Use

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 を伴うTool Useを使用することで、検証済みでスキーマ形状の引数を得ることができます(使用を強制も可能です)。


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_objectJSON形状(正確なスキーマではありません)を強制します — クライアント側で検証してください。


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スキーマを持つToolを定義し、その使用を強制してください。ツールの引数を型付きオブジェクトとして読み取ります。
  • 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エンジニアリングの新記事をお届けします。