ブログに戻る
チュートリアル

構造化出力(JSON)で確実にパースする:response_formatの使い方

FastMetal

LLMの出力をプログラムで使いたいとき、プロンプトで「JSONで返して」とお願いするのは最後の手段です。 response_format にJSON Schemaを渡せば、スキーマに沿ったJSONが返ってきます。

違いは信頼性です。プロンプト頼みだと、9割は正しいJSONが返り、残り1割で ```json のコードブロックに包まれたり、前置きの一文が付いたり、末尾のカンマが混ざったりします。その1割を潰すために書くパース処理が、いちばん壊れやすいコードになります。

response_formatを使う

response_formatjson_schema を指定します。

from openai import OpenAI

client = OpenAI(
    api_key="<FASTMETAL_API_KEY>",
    base_url="https://api.fastmetal.ai/v1",
)

resp = client.chat.completions.create(
    model="gemini-flash-lite-free",
    max_tokens=300,
    messages=[
        {"role": "user", "content": "「新宿の田中太郎さん(42歳)」から情報を抽出して"},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "person",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "age": {"type": "integer"},
                    "city": {"type": "string"},
                },
                "required": ["name", "age", "city"],
                "additionalProperties": False,
            },
        },
    },
)
print(resp.choices[0].message.content)

返ってきた content は、そのまま json.loads() に通せます(2026年8月29日実測)。

{
  "name": "田中太郎",
  "age": 42,
  "city": "新宿"
}

コードブロックの記号も、前置きの文章もありません。

curlの場合

curl https://api.fastmetal.ai/v1/chat/completions \
  -H "Authorization: Bearer $FASTMETAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-flash-lite-free",
    "max_tokens": 300,
    "messages": [
      {"role": "user", "content": "「新宿の田中太郎さん(42歳)」から情報を抽出して"}
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "person",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "name": {"type": "string"},
            "age": {"type": "integer"},
            "city": {"type": "string"}
          },
          "required": ["name", "age", "city"],
          "additionalProperties": false
        }
      }
    }
  }'

スキーマの書き方で押さえる3点

required に全項目を入れる 「任意項目」にすると、モデルはその項目を省略できてしまいます。省略を許したいなら、required に入れたうえで型を ["string", "null"] にして、値としてのnullを返させるほうが扱いやすくなります。キーの有無を毎回チェックするコードを書かずに済みます。

additionalProperties: false を付ける 付けないと、スキーマにない項目を勝手に足されることがあります。

strict: true を付ける スキーマへの準拠を厳密に扱わせる指定です。

description を各プロパティに書けるのも重要です。{"type": "string", "description": "都道府県名。市区町村は含めない"} のように書くと、抽出の粒度が安定します。スキーマはバリデーションであると同時に指示でもあると考えてください。

実用的な例:レビューの分類

配列とenumを使うと、分類タスクがそのまま構造化されます。

schema = {
    "type": "object",
    "properties": {
        "sentiment": {
            "type": "string",
            "enum": ["positive", "negative", "neutral"],
        },
        "topics": {
            "type": "array",
            "items": {"type": "string"},
            "description": "言及されている話題。最大3つ",
        },
        "summary": {"type": "string", "description": "20字以内の要約"},
    },
    "required": ["sentiment", "topics", "summary"],
    "additionalProperties": False,
}

resp = client.chat.completions.create(
    model="gemini-flash-lite-free",
    max_tokens=300,
    messages=[
        {"role": "system", "content": "レビューを分類するアシスタントです。"},
        {"role": "user", "content": "配送は早かったけど、箱が潰れていた。商品自体は満足。"},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {"name": "review", "strict": True, "schema": schema},
    },
)

import json
data = json.loads(resp.choices[0].message.content)
print(data["sentiment"], data["topics"])

enum を使えば、分類ラベルの表記ゆれ(positive / Positive / ポジティブ)が起きません。分岐する側のコードが単純になります。

それでもパースが失敗する場合

構造化出力でも、失敗しうるパターンが2つあります。

1. max_tokens で途中で切れる

出力の途中で上限に達すると、JSONが閉じないまま返ります。これはスキーマの問題ではないので、構造化出力でも防げません。

finish_reason を必ず確認してください。

choice = resp.choices[0]
if choice.finish_reason == "length":
    raise RuntimeError("max_tokens で切れました。上限を上げてください")
data = json.loads(choice.message.content)

finish_reasonstop なら正常終了、length なら切れています。この見分け方はChat Completions APIとはでも触れています。

配列を返させるスキーマは特に危険です。件数の上限をスキーマの description に書き、max_tokens に余裕を持たせてください。

2. モデルが対応していない

すべてのモデルが json_schema に対応しているわけではありません。非対応の場合、指定が無視されて普通の文章が返ることがあります。まず1回叩いて確かめてください。

保険としてのパース処理を1枚挟んでおくと安全です。

import json, re

def parse_json(text: str):
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        # ```json ... ``` に包まれている場合の救済
        m = re.search(r"```(?:json)?\s*(.+?)\s*```", text, re.S)
        if m:
            return json.loads(m.group(1))
        raise

これは非対応モデルへのフォールバックであって、通常の経路ではありません。毎回ここを通っているなら、モデルか指定を見直すサインです。

json_object との違い

response_format にはもう一つ {"type": "json_object"} という指定があります。これは「JSONであること」だけを保証し、中身の構造は保証しません。キー名が毎回変わる可能性があります。

構造まで決めたいなら json_schema を使ってください。json_object を使う場合は、期待するキーをプロンプト側に書く必要があります。

構造化出力とツール呼び出しの使い分け

似て見えますが、目的が違います。

構造化出力ツール呼び出し
目的出力のを固定する外部の処理を実行する
往復1回2回以上
使う場面抽出、分類、要約の構造化検索、DB参照、副作用のある操作

「JSONが欲しいだけ」なら構造化出力です。 ツール呼び出しでJSONを取り出す実装を見かけますが、往復が増えるぶん遅く、高くつきます。

つまずきやすい点

required を絞ってしまう 任意項目はキーごと消えます。nullを返させるほうが扱いやすくなります。

finish_reason を見ていない length で切れたJSONは必ずパースに失敗します。エラーの原因を探す前にここを見てください。

additionalProperties を省略する スキーマにないキーが増えることがあります。

スキーマが深すぎる 入れ子が深いと精度が落ち、出力トークンも増えます。2〜3階層に収め、必要なら複数回に分けてください。

プロンプト側の指示と矛盾している 「JSONで返して」とプロンプトに書きつつスキーマも渡すと、指示が二重になります。スキーマに任せて、プロンプトには何を抽出するかだけを書いてください。

次のステップ

画像から情報を抽出して構造化する、という組み合わせは実用的です。画像入力の使い方と併せて使うと、レシートや帳票の読み取りがそのまま実装できます。

複数モデルの出力品質を比べたい場合は、構造化出力にしておくと自動評価が書きやすくなります。Evalsの入門記事を参照してください。

よくある質問

Q. プロンプトで「JSONで返して」と書くのとどう違いますか? プロンプト頼みだと、コードブロックに包まれたり前置きが付いたりして、一定の確率でパースに失敗します。response_format にスキーマを渡せば、スキーマに沿ったJSONが直接返ります。

Q. 構造化出力は無料モデルでも使えますか? 使えます。この記事のコードは gemini-flash-lite-free で動作を確認しています。ただしすべてのモデルが対応しているわけではないので、使うモデルで一度確かめてください。

Q. JSONが途中で切れてパースに失敗します。 max_tokens の上限で切れている可能性が高いです。finish_reasonlength になっていないか確認し、上限を上げてください。構造化出力でもこれは防げません。

Q. json_objectjson_schema はどちらを使うべきですか? 構造まで固定したいなら json_schema です。json_object はJSONであることしか保証せず、キー名は変わりえます。

まとめ

response_formatjson_schema を渡せば、パース処理を書かずに済みます。required は絞らずnullを許す、additionalProperties: false を付ける、finish_reason を必ず見る——この3つで実務上の失敗はほぼ潰せます。

無料モデルで試せるので、APIキーを取得して手元のスキーマで1回叩いてみてください。対応モデルはモデルカタログにあります。

最新のAIモデルを今すぐ試す

最新のAIモデルはFastMetalのAPIキー1つで利用できます。ブラウザですぐに試す、またはOpenAI SDKからそのまま呼び出せます。