OpenAIのJSON modeからStructured Outputsへの実装変更について試した

OpenAIのJSON modeからStructured Outputsへの実装変更について試した

プロンプト内のJSONスキーマをpydanticを使った型アノテーションの情報で指定するように変更することでJSON modeで得ていた結果をStructured Outputsでも得られそうでした。ライブラリのAPIは異なるものを使う必要がありました。
Clock Icon2025.02.06

データ事業本部 機械学習チームの鈴木です。

昨年夏にアナウンスされたStructured Outputsは、JSON modeと比較してより厳格に、指定したスキーマにしたがって、生成される文字列を制御できるオプションです。

https://openai.com/index/introducing-structured-outputs-in-the-api/

直近ではo3-miniなどのモデルが登場してきていますが、モデルの紹介としてはStructured Outputsへの対応のみ紹介されていました。

モデル実行をツール化して運用しているような場合、モデルによってJSON modeでなくStructured Outputsに内部的に切り替えられるようにする必要がある(また、その逆もする必要がある)ユースケースがあると思います。

今回はJSON modeで行っていたJSON作成をStructured Outputsで行うのにどうすればいいか、簡単な例ですが試してみましたのでご紹介します。

やってみた

前提

今回はgpt-4o-miniにて検証します。

Structured OutputsのガイドではJSON modeおよびStructured Outputsそれぞれに対応するモデルが記載されていますが、gpt-4o-miniは双方に対応しています。

環境はGoogle Colab上で実行しました。openaiライブラリは1.59.9を利用しました。

JSON modeでの例

チームメイトの中村さんが以前書かれたブログの例を改めて試してみました。

https://dev.classmethod.jp/articles/openai-jsonmode-seikei/

この例では、JSONのキーはシステムプロンプトに入れて渡していました。

あなたはテキストを整形するアシスタントです。
与えられた文字列に対して、名前と出社ステータスと場所をJSON形式でパースしてください。
JSONのキーはname, status, locationとしてください。

nameは人名とします。
statusは出社、リモート、出張、休日の3種類から選んでください。
locationは、statusが出社または出張の場合のみパースして下さい。

関係のない文字列については空欄のJSON形式を返してください。

今回は以下のデータについて、期待するキーに対応する情報を抜き出し、JSON形式に変換します。

淀屋橋出社(鈴木)
淀屋橋出社 (鈴木)
淀屋橋 出社 (鈴木)
淀屋橋出社 (鈴木)
淀屋橋出社 (鈴木)
有休(鈴木
鈴木は今日東京出張です
(家庭の事情で)リモート予定(鈴木)

コードは通して以下のようになりました。

import json
import os
from openai import OpenAI
import polars as pl

# APIキーは環境変数に設定しておく。
client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY"),
)

system_prompt = """
あなたはテキストを整形するアシスタントです。
与えられた文字列に対して、名前と出社ステータスと場所をJSON形式でパースしてください。
JSONのキーはname, status, locationとしてください。

nameは人名とします。
statusは出社、リモート、出張、休日の3種類から選んでください。
locationは、statusが出社または出張の場合のみパースして下さい。

関係のない文字列については空欄のJSON形式を返してください。
"""

def json_parser(query: str, model: str):

    client = OpenAI()

    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": query},
        ],
        response_format={"type": "json_object"}
    )

    if response.choices[0].message.content is None:
        return {}
    else:
        return json.loads(response.choices[0].message.content)

data = """
淀屋橋出社(鈴木)
淀屋橋出社 (鈴木)
淀屋橋 出社 (鈴木)
淀屋橋出社 (鈴木)
淀屋橋出社 (鈴木)
有休(鈴木
鈴木は今日東京出張です
(家庭の事情で)リモート予定(鈴木)
""".strip("\n").split("\n")

model = "gpt-4o-mini"
df = pl.DataFrame([{"text": d, **json_parser(d, model)} for d in data])

最終的に以下のように変換できました。見やすいようにJSONからさらにデータフレームに変換しています。

JSON MODEのレスポンスを使ったパース結果

Structured Outputsの例

ここからがこの記事で試したい内容ですが、先のJSON modeの例をStructured Outputsでも実施してみました。

Structured Outputsでは、生成するJSONのスキーマをpydanticを使った型アノテーションの情報で指定します。

今回は以下のクラスとシステムプロンプトを作成しました。システムプロンプトからスキーマ情報は抜いてしまい、pydanticのBaseModelを継承したクラス内で指定しました。

from pydantic import BaseModel

class Attendance(BaseModel):
    name: str
    status: str
    location: str

system_prompt = """
あなたはテキストを整形するアシスタントです。
与えられた文字列に対して、名前と出社ステータスと場所をJSON形式でパースしてください。

statusは出社、リモート、出張、休日の3種類から選んでください。
locationは、statusが出社または出張の場合のみパースして下さい。

関係のない文字列については空欄のJSON形式を返してください。
"""

コードは通して以下のようになりました。
なお、推論実行のためのAPIはclient.beta.chat.completions.parseになっています。JSON MODEではresponse_format{"type": "json_object"}を指定していましたが、今回は定義したAttendanceクラスを指定しました。

import json
import os
from openai import OpenAI
import polars as pl
from pydantic import BaseModel

# APIキーは環境変数に設定しておく。
client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY"),
)

class Attendance(BaseModel):
    name: str
    status: str
    location: str

system_prompt = """
あなたはテキストを整形するアシスタントです。
与えられた文字列に対して、名前と出社ステータスと場所をJSON形式でパースしてください。

statusは出社、リモート、出張、休日の3種類から選んでください。
locationは、statusが出社または出張の場合のみパースして下さい。

関係のない文字列については空欄のJSON形式を返してください。
"""

def json_parser(query: str, model: str):

    client = OpenAI()

    response = client.beta.chat.completions.parse(
        model=model,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": query},
        ],
        response_format=Attendance,
    )

    return response.choices[0].message.parsed.model_dump()

model = "gpt-4o-mini"
df = pl.DataFrame([{"text": d, **json_parser(d, model)} for d in data])

JSON modeと同じ結果を得ることができました。

Structured Outputsのレスポンスを使ったパース結果

最後に

OpenAIのJSON modeからStructured Outputsへの実装変更をしたい際に、どのような変更が必要そうか簡単な例ですが試してみました。

実際のユースケースでは、本番相当のテストデータを投入してみて、JSON modeのときの結果と十分に近い結果がStructured Outputsを使った場合にも得られるかどうか確認するのがよいように思います。

参考になりましたら幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.