サイオステクノロジーのひろです。今回はAzure OpenAIでスキーマに沿ったJSON形式データを出力することをゴールに、以下の目次で解説していきます。
前回の記事はこちら
LLMは流暢で自然な文章を出力することが可能です。学習した膨大なテキスト情報を使い、ユーザの入力(プロンプト)に沿った文章を出力してくれます。
例えばAzure OpenAIモデルに「こんにちは」というプロンプトを渡すと以下のように返してくれました。
「こんにちは!どのようにお手伝いできますか?」
自然な文章だと思います。
しかしLLMはこのような自然な文章を生成するだけでなく、JSON形式でテキストを出力することができます。
JSON形式で出力させることで、アプリケーションのシステムの一部として組み込むことが容易になります。
試しにプロンプトのみでJSON形式で出力してもらいました。
messageは以下の通りです。
messages=[
{
"role": "system",
"content": "あなたは優秀なアシスタントAIです。JSON形式で出力してください。",
},
{"role": "user", "content": "こんにちは"},
],実行結果(返答)はこちらです。
{
"message": "こんにちは!何かお手伝いできることがありますか?"
}JSON形式でレスポンスをくれました。
この例のようにJSON形式で返してくれる場合もありますが、プロンプトエンジニアリングだけでは確実性に欠けるもので、崩れたJSON形式であったり、JSONの前に「JSON形式で回答します。」という文章が出力されることがあります。
そのためアプリケーションに組み込むにはリスクがあります。
そこで、この次の章ではAzure OpenAIモデルでJSON形式でレスポンスを取得できる2種類の方法をご紹介します。
Azure OpenAIにはJSONモードが存在します。
JSONモードを使用することで、JSON形式で返答されることが保証されます。
JSONモードを使用するには、AzureOpenAIへのリクエストの中に以下のパラメータを追加し、プロンプトでJSON形式で出力してください等という文章を追加します。
response_format: { type: "json_object" }これによりJSONモードを使用できます。
以下はサンプルコードと実行結果です。今回はpythonで用意しています。
また、スキーマを用意し、プロンプトに含めることでスキーマに沿った内容を出力させようとしています。
サンプルコード
import os
import json
from openai import AzureOpenAI
from dotenv import load_dotenv
from test_schemas import TestSchemas
# .envファイルから環境変数を読み込み
load_dotenv()
api_key = os.getenv("AZURE_OPENAI_API_KEY")
endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
api_version = os.getenv("AZURE_OPENAI_API_VERSION")
deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
# Azure OpenAIクライアントの作成
client = AzureOpenAI(
api_version=api_version,
azure_endpoint=endpoint,
api_key=api_key,
)
# スキーマのJSON表現を取得
schema_string = json.dumps(
TestSchemas.model_json_schema(), indent=2, ensure_ascii=False
)
prompt = f"""
以下の文章を分析し、指定されたスキーマに従ってJSON形式で出力してください。
文章: '''私は今週末のテストに備えて早く寝ることにしました。'''
スキーマ: '''{schema_string}'''
"""
response = client.chat.completions.create(
messages=[
{
"role": "system",
"content": "あなたは優秀なアシスタントAIです。日本語でJSON形式に合わせて回答してください。",
},
{"role": "user", "content": prompt},
],
response_format={"type": "json_object"},
max_tokens=1000,
temperature=0.7,
model=deployment,
)
print(response.choices[0].message.content)TestSchemasクラス
from pydantic import BaseModel, Field
class TestSchemas(BaseModel):
title: str = Field(description="文章のタイトル")
content: str = Field(description="文章の内容")
importance: int = Field(
ge=1, le=5, description="文章の重要度を1から5の範囲で評価(5が最も重要)"
)
実行結果
{
"title": "テスト準備",
"content": "私は今週末のテストに備えて早く寝ることにしました。",
"importance": 4
}スキーマに沿ったJSON形式のレスポンスが返ってきていることが確認できました。
ただ、これは成功例で、スキーマに沿ったキーでレスポンスをくれない場合もあります。
このJSONモードはJSON形式であることは保証してくれますが、どんなキーが含まれるかまでは保証してくれません。
プロンプトのみでJSONを指定した場合に比べると形式が保証されているという良い点がありますが、キーが変わる場合があるとなるとアプリケーションに組み込むにはリスクがあります。
この問題を解決するのがもう一つの方法である構造化出力(Structured Outputs)です。
構造化出力はJSONモードより後に追加された機能で、JSONモードを強化したバージョンといえると思います。
JSONモードと何が違うかというと、JSONモードではどんなキーが含まれるかという点は保証してくれませんでした。しかし、この構造化出力は指定したスキーマに準拠した回答をさせることが可能です。
JSONモードではプロンプトにスキーマを入力していましたが、response_formatにスキーマを指定することができます。
以下はサンプルコードです。
スキーマはJSONモードのサンプルコードで使用したTestSchemasを使用しています。
import os
import json
from openai import AzureOpenAI
from dotenv import load_dotenv
from test_schemas import TestSchemas
# .envファイルから環境変数を読み込み
load_dotenv()
api_key = os.getenv("AZURE_OPENAI_API_KEY")
endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
api_version = os.getenv("AZURE_OPENAI_API_VERSION")
deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
client = AzureOpenAI(
api_version=api_version,
azure_endpoint=endpoint,
api_key=api_key,
)
schema_string = json.dumps(
TestSchemas.model_json_schema(), indent=2, ensure_ascii=False
)
prompt = """
以下の文章を分析し、指定されたスキーマに従ってJSON形式で出力してください。
文章: '''私は今週末のテストに備えて早く寝ることにしました。'''
"""
response = client.chat.completions.parse(
messages=[
{
"role": "system",
"content": "あなたは優秀なアシスタントAIです。日本語でJSON形式に合わせて回答してください。",
},
{"role": "user", "content": prompt},
],
response_format=TestSchemas,#スキーマを指定
max_tokens=1000,
temperature=0.7,
model=deployment,
)
print(response.choices[0].message.content)
実行結果
{"title":"テストに備えるための早寝","content":"私は今週末のテストに備えて早く寝ることにしました。","importance":3}スキーマに沿ったJSON形式で出力されていることが確認できました。
なぜ構造化出力でキーを保証できるんだろうという点に疑問を持ったので調べてみました。
どうやら制約付きデコードと呼ばれる技術が用いられているようです。
ここでLLMが文章を生成する方法についておさらいしましょう。
LLMは学習したパターンに基づいて、プロンプトに沿った内容の文章を生成してくれます。
まず、文脈に沿った次に来る確率が高い言葉をリストアップし、その中から確率で次の言葉を決定し、これを繰り返すことで文章を生成しています。
以下の図の例で見ますと、「今日は」に続いて、「いい」、「天気」、「寒い」という言葉がリストアップされており、それぞれに確率が存在します。次の単語が「いい」の確率は60%、「天気」の確率は30%、「寒い」の確率は10%という具合です。例では「いい」が選ばれていますね。この選択を繰り返すことで文章を生成しています。

制約つきデコードとは何かというと、リストアップされた言葉のうち、指定された種類の言葉以外が選ばれる確率を制限するというものです。
以下の例では、response_formatとして、titleとcontentというfieldのみ存在するスキーマを渡したとします。
制約付きデコードがない場合は。「{」に続いて、「title」が60%、「key」が30%、「name」が10%という具合で、title以外のキーが選ばれる可能性があります。

制約付きデコードがあれば、「title」以外の言葉の確率が0になるようマスキングされ、titleが選ばれるようになります。

このように制約付きデコーディングが使用されることで、構造化出力で指定されたスキーマのキーが保証されるようになります。
今回はAzureOpenAIを使用してJSON形式のデータを出力させる方法についてまとめました。
方法は2種類あり、JSONモードではJSON形式の出力は保証されますが、どんなキーが含まれるかについては保証されません。
対して構造化出力(Structured Outputs)を使用することでキーについても保証されるようになります。
JSON形式で出力させることでアプリケーションに生成AIを組み込みやすくなります。是非活用してみてはいかがでしょうか。
次回はAzure OpenAIのFunctionCallingについて解説します。
https://openai.com/ja-JP/index/introducing-structured-outputs-in-the-api
