型安全な AI 出力の実現
大規模言語モデル(LLM)からのレスポンスを扱う際、従来の JSON 文字列を手動で解析する手法は、型エラーやフィールド欠落のリスクを伴います。OpenAI Python SDK が提供する構造化出力機能は、Pydantic モデルを直接利用することで、この課題を解決します。これにより、自然言語による出力を、検証済みの Python オブジェクトとして即座に利用可能になります。
構造化出力の仕組み
この機能の核心は、定義した Pydantic モデルを基に、SDK 内部で厳格な JSON Schema が自動生成される点にあります。生成されたスキーマには、余分なプロパティの排除(additionalProperties: false)や必須フィールドの強制などが含まれており、モデルが预期する構造を AI に厳密に指示します。
これにより、開発者はパーシングロジックを記述する代わりに、データ構造の定義のみで済むようになります。
実装手順:3 ステップで導入
1. データモデルの定義
まず、AI から取得したいデータ構造を Pydantic の BaseModel で定義します。ここでは、為替換算の処理ログを記録する例を示します。
from pydantic import BaseModel
from typing import List
class ProcessLog(BaseModel):
description: str # 処理段階の説明
value: str # 当該段階での数値
class AnalysisResult(BaseModel):
history: List[ProcessLog] # 処理履歴
conclusion: str # 最終結論
2. 構造化リクエストの送信
OpenAI クライアントの parse メソッドを使用し、response_format に定義したモデルを指定します。これにより、返答が自動的に検証・変換されます。
from openai import OpenAI
client = OpenAI()
response_obj = client.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "You are a financial assistant."},
{"role": "user", "content": "Convert 100 USD to JPY assuming 1 USD = 150 JPY."},
],
response_format=AnalysisResult,
)
3. 型安全なオブジェクトの利用
レスポンスオブジェクトの parsed 属性を通じて、定義したクラスのインスタンスに直接アクセスできます。辞書のネストを辿る必要はありません。
msg = response_obj.choices[0].message
if msg.parsed:
for record in msg.parsed.history:
print(f"工程:{record.description}")
print(f"値:{record.value}")
print(f"結論:{msg.parsed.conclusion}")
else:
print(f"拒否理由:{msg.refusal}")
ツール呼び出しとの統合
構造化出力は、関数呼び出し(Function Calling)と組み合わせることでさらに効果を発揮します。AI が外部ツールを呼び出す際にも、パラメータの構造を厳密に定義することが可能です。
FunctionDefinition を使用してツールの仕様を明確にします。
class FunctionDefinition(BaseModel):
name: str
description: Optional[str] = None
parameters: Optional[dict] = None
strict: Optional[bool] = None
ツール連携の具体例
計算ツールを定義し、AI に適切な引数で呼び出させる例です。
def execute_formula(formula: str) -> float:
"""数式を評価して結果を返す"""
return eval(formula)
tools = [
{
"type": "function",
"function": FunctionDefinition(
name="execute_formula",
description="提供された数式を計算する",
parameters={
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "計算対象の数式(例:'10*5+2')"
}
},
"required": ["formula"],
"strict": True
}
)
}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "37 * 24 + 18 の結果は?"}],
tools=tools,
tool_choice="auto"
)
if response.choices[0].message.tool_calls:
call = response.choices[0].message.tool_calls[0]
if call.function.name == "execute_formula":
import json
args = json.loads(call.function.arguments)
result = execute_formula(args["formula"])
運用上のベストプラクティス
厳密モードの活用
スキーマ定義において strict=True を設定することを強く推奨します。これにより、AI は定義された構造から逸脱した出力を生成できなくなり、データの不整合を防げます。SDK 内部では、これに応じて additionalProperties が自動的に制御されます。
エラー処理の徹底
厳密モードを使用していても、ネットワークエラーやモデルの制約により解析が失敗する可能性があります。message.parsed が None になるケースを想定し、message.refusal を確認するフォールバックロジックを実装してください。
モデルの選定
構造化出力機能は、すべてのモデルでサポートされているわけではありません。現時点では gpt-4o や gpt-3.5-turbo-1106 以降のバージョンで利用可能です。コストと精度のバランスを考慮し、開発段階では安価なモデルを、本番環境では高精度なモデルを選定するのが効果的です。