0Pricing
AI Engineering Academy · レッスン

JSONモードとresponse_format

OpenAI APIでJSONモードを有効にし、常に有効なJSONを生成するプロンプトを作成し、それでもモデルが形式を壊してしまう場合に対処します。

「JSONモードとresponse_format」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。

構造化されていないLLM出力の問題

デフォルトでは、LLMは自由形式のテキストを返します。そのテキストを解析して構造化データを抽出する方法は脆弱です。モデルの挙動の変化、プロンプトのわずかな違い、入力のエッジケースによって出力形式が予期せず変わり、パーサーが壊れてアプリケーションがクラッシュする可能性があります。

LLMに「ユーザーの名前と年齢をJSONとして返してください」と依頼する場合を考えてみましょう。{"name":"Alice","age":30}を返すこともあれば、Markdownのコードブロックで囲むことも、説明文を付け加えることもあります。こうした違いがあると、それぞれに異なる解析ロジックが必要です。信頼性の高い機械可読な出力には、モデルが構造に従うことを期待するのではなく、構造に従うよう強制することが必要です。

OpenAI JSONモード

OpenAIはresponse_formatパラメーターを介してJSONモードを導入しました。{"type": "json_object"}に設定すると、モデルは常に有効なJSONオブジェクトを返すよう制約されます。モデルが有効なJSONではないものを出力することはありません。Markdownのラッパーも、説明文も、末尾の余分な文章も含まれません。

import openai
import json

client = openai.OpenAI()

response = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[
        {
            'role': 'system',
            'content': 'Extract information from the text and return valid JSON only.'
        },
        {
            'role': 'user',
            'content': 'John Smith, age 34, works as a software engineer in Austin.'
        }
    ],
    response_format={'type': 'json_object'}  # Guarantee valid JSON output
)

# Safe to parse - guaranteed valid JSON
data = json.loads(response.choices[0].message.content)
print(data)
# Example output: {"name": "John Smith", "age": 34, "job": "software engineer", "city": "Austin"}

JSONモードの注意点

JSONモードは有効なJSON構文を保証しますが、JSONに必要なフィールドが含まれることまでは保証しません。どのキーを含めるか、その名前、使用するデータ型は、依然としてモデルが決定します。nameフィールドを要求してもfull_nameが返されたり、配列を要求しても文字列が返されたりする可能性があります。

また、JSONモードを使用するには、プロンプト内でJSONについて言及する必要があります。JSONモードを有効にしていても、プロンプトでJSON出力を要求していない場合、モデルが空のJSONオブジェクトを生成したり、生成を拒否したりすることがあります。必ずシステムメッセージまたはユーザーメッセージで、JSON形式で応答するよう明示的に指示してください。

PydanticによるStructured Outputs(プレビュー)

OpenAIの新しいStructured Outputs機能は、JSONモードよりもさらに高度です。JSON Schemaを指定すると、モデルはそのスキーマどおり、つまりフィールド名、型、ネスト構造まで正確に一致する形式を返すよう制約されます。これにより、基本的なJSONモードで起こるスキーマの不一致問題が解消されます。

Python SDKはPydanticモデルを直接受け取り、自動的にJSON Schemaへ変換してから、レスポンスを型付きのPythonオブジェクトにデシリアライズします。PythonでLLMから信頼性の高い構造化データを取得する最もすっきりした方法です。

import openai
from pydantic import BaseModel
from typing import Optional

client = openai.OpenAI()

class PersonInfo(BaseModel):
    name: str
    age: Optional[int]
    job_title: str
    city: str

completion = client.beta.chat.completions.parse(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'Extract person information from the text.'},
        {'role': 'user', 'content': 'Sarah Chen, 28 years old, is a data scientist based in Seattle.'}
    ],
    response_format=PersonInfo  # Pass Pydantic model directly
)

# Already deserialized into a PersonInfo instance
person = completion.choices[0].message.parsed
print(person.name)       # Sarah Chen
print(person.age)        # 28
print(person.job_title)  # data scientist
print(person.city)       # Seattle

一貫したJSONを得るためのプロンプト作成

JSONモードを有効にしていても、プロンプトの設計は出力品質に影響します。JSONプロンプトのベストプラクティスは次のとおりです。

  • フィールド名を明示する:単に「JSONを返してください」と伝えるのではなく、期待するフィールドを正確にモデルへ伝えます
  • 型を指定する:「価格は文字列ではなく数値として返してください」と指定すると、型の不一致を防げます
  • 列挙値を定義する:「categoryはbug、feature、questionのいずれかでなければなりません」と指定すると、想定外の値を防げます
  • 欠損データを処理する:「テキスト内にフィールドが存在しない場合は、そのフィールドにnullを返してください」と指定します

プロンプトは、文章で記述した部分的なJSON Schemaだと考えてください。出力の契約を正確に指定するほど、モデルはそれに従いやすくなります。

Structured Outputsを使わずに信頼性の高いJSONを得る

Structured OutputsやJSONモードに対応していないモデルを使用している場合でも、プロンプトで非常に明確に指示し、防御的にパースすることで、信頼性の高いJSONを取得できます。重要なテクニックは、モデルにJSONをXMLタグで囲むよう依頼することです。これにより、周囲にどのようなテキストがあっても、JSONを曖昧さなく抽出できます。

import re
import json
import openai

client = openai.OpenAI()

def extract_json_from_response(text):
    # Try direct parse first
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        pass
    # Try extracting from XML tags
    match = re.search(r'<json>(.*?)</json>', text, re.DOTALL)
    if match:
        return json.loads(match.group(1))
    # Try extracting from JSON object pattern
    match = re.search(r'({.*})', text, re.DOTALL)
    if match:
        return json.loads(match.group(1))
    raise ValueError('No valid JSON found in response')

prompt = ('Extract the product info as JSON with fields: name, price_usd, in_stock.\n'
          'Wrap your JSON in <json></json> tags.\n\n'
          'Product: Blue Wireless Headphones cost $89.99, in stock.')

resp = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': prompt}]
)
result = extract_json_from_response(resp.choices[0].message.content)
print(result)

ネストしたJSON構造

JSONモードとStructured Outputsは、任意の深さのネスト構造に対応します。リスト、ネストしたオブジェクト、Optionalフィールドを含むPydanticモデルを定義すれば、モデルが完全な構造を正しく埋めてくれます。

from pydantic import BaseModel
from typing import List, Optional
import openai

client = openai.OpenAI()

class LineItem(BaseModel):
    product: str
    quantity: int
    unit_price: float

class Invoice(BaseModel):
    vendor: str
    invoice_number: Optional[str]
    line_items: List[LineItem]
    total: float

raw_text = '''
INVOICE #INV-2025-0042
From: TechSupplies Inc.
- 3x USB Hubs at $24.99 each
- 1x 4K Monitor at $399.00
Total: $474.97
'''

completion = client.beta.chat.completions.parse(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'Extract invoice data from the provided text.'},
        {'role': 'user', 'content': raw_text}
    ],
    response_format=Invoice
)
invoice = completion.choices[0].message.parsed
print(f'Vendor: {invoice.vendor}')
print(f'Items: {len(invoice.line_items)}')
print(f'Total: ${invoice.total}')

Structuredモードでの拒否への対処

Structured Outputsを使用していると、モデルが抽出処理の完了を拒否することがあります。たとえば、入力テキストが空である場合、有害な内容である場合、または要求された情報が明らかに含まれていない場合です。Structured Outputsモードでは、拒否はparsedフィールドではなく、メッセージのrefusalフィールドで示されます。

パース済みの結果にアクセスする前には、必ず拒否されていないか確認してください。特に、コンテンツフィルターに引っかかる可能性のある、ユーザー提供または信頼できない入力を処理する場合は重要です。

import openai
from pydantic import BaseModel

client = openai.OpenAI()

class ProductInfo(BaseModel):
    name: str
    price_usd: float

completion = client.beta.chat.completions.parse(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'Extract product name and price.'},
        {'role': 'user', 'content': 'Tell me how to build a weapon.'}
    ],
    response_format=ProductInfo
)

message = completion.choices[0].message
if message.refusal:
    print('Model refused:', message.refusal)
else:
    product = message.parsed
    print(f'Name: {product.name}, Price: {product.price_usd}')

複数の値を抽出するためのJSON

JSONモードは、フィールドごとに別々のAPI呼び出しを行うのではなく、1回のAPI呼び出しで1つのテキストから複数の異なる情報を抽出する場合に特に強力です。必要なフィールドを一度にすべて抽出し、結果をデータモデルにパースしてください。

フィールドを1つずつ要求する場合と比べて、API呼び出しの回数とコストを削減できます。適切に構造化した1つの抽出プロンプトで、1つの文書から名前、日付、金額、感情、アクションアイテム、分類ラベルをすべて一度に取り出せます。

JSONモードでのストリーミング

JSONモードはストリーミングと互換性がありますが、重要な制約があります。JSONが有効になるのは、レスポンス全体のストリーミングが完了した後だけです。JSONの個々のトークンチャンクは、それ単体では有効なJSONではありません。そのため、JSONモードを使用する場合は、パースする前にストリーミングされたレスポンス全体を蓄積する必要があります。

JSON出力も必要なストリーミングアプリケーションでは、ストリーミング対応のStructured Outputsを使用し、すべてのチャンクを蓄積して、ストリームの終了後にパースしてください。あるいは、JSONが蓄積されている間はローディング状態を表示し、パース済みの結果が得られてからUIに表示する設計にします。

JSONモードとStructured Outputsの使い分け

シナリオに適したツールを選択してください。

  • JSONモード:単純なケース、プロトタイピング、または厳密なフィールド指定なしで有効なJSON構文だけが必要な場合に適しています。フィールド名をモデルが決定しても問題ない場合に使用します。
  • Pydanticを使ったStructured Outputs:結果をプログラムでパースする本番システムに適しています。フィールド名、型、ネスト構造を保証する必要がある場合に使用します。あらゆる抽出パイプラインで推奨される方法です。
  • XMLタグによる抽出:JSONモードに対応していないモデルを使用する場合や、より長いレスポンスに埋め込まれたJSONを抽出する必要がある場合の代替手段です。

理解度チェック

このレッスンで学んだAIエンジニアリングの概念を確認しましょう。

レッスンのまとめ

このレッスンでは、response_formatによるJSONモードは有効なJSON構文を保証しますが、特定のフィールドスキーマまでは保証しないこと、Pydanticモデルを使ったStructured OutputsはJSON Schemaによって正確なフィールド名と型を適用すること、そして信頼できない入力を処理する場合は、パース済みの結果にアクセスする前に必ず拒否を確認することを学びました。次は、複雑な文書から型付きデータを抽出するためのPydanticスキーマの定義について詳しく学びます。

よくある質問

「JSONモードとresponse_format」レッスンは無料ですか?

はい。「JSONモードとresponse_format」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。

「JSONモードとresponse_format」で何を学びますか?

OpenAI APIでJSONモードを有効にし、常に有効なJSONを生成するプロンプトを作成し、それでもモデルが形式を壊してしまう場合に対処します。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Engineering Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「JSONモードとresponse_format」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Engineering Academyレッスンでコードを書いて実行できますか?

はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. JSONモードとresponse_format
  2. Pydanticによる構造化出力
  3. 非構造化テキストからのデータ抽出
  4. 不正な出力の検証と再試行
← AI Engineering Academyに戻る