🦁

【最速検証記事!?】Azure OpenAIで実現するStructured Outputsの力: 具体例と実装ガイド🚀【構造化出力】

2024/09/04に公開

TL;DR

  • Azure OpenAIのStructured Outputsを活用することで、AIによるテキスト生成を事前に定義されたスキーマに従わせ、データの一貫性と精度を向上させます。
  • 特に大量の非構造化データを処理するシナリオで効果的です。コールセンターでの通話要約や採用プロセスの管理など、具体的なユースケースを紹介します。
  • 本記事では、Structured Outputsの利点を説明し、Pythonによる実装例を提供します。これにより、実際の業務に即したStructured Outputsの導入方法を理解できます。
  • 以前に書いた記事「OpenAI APIでのStructured Outputsの実装ガイド」も合わせて参照し、OpenAI APIでの実装についても確認できます。

概要

2024年9月4日、Azure OpenAIサービスにおいて、Structured Outputsの新機能が正式に発表されました。この機能は、AIモデルによる出力を指定したJSONスキーマに厳密に従わせることで、データの一貫性と精度を向上させるものです。これにより、関数の呼び出しやデータ抽出、複雑なワークフローの構築が一層容易になります。

https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/introducing-gpt-4o-2024-08-06-api-with-structured-outputs-on/ba-p/4232684

上記記事のポイントとしては以下のとおりです。

  • Structured Outputs(構造化出力)機能で出力フォーマットを指定可能
  • グローバル標準およびStandardデプロイで利用可能
    • 現時点(2024年9月4日時点)では、米国全リージョンとスウェーデン中部リージョンで利用可能
  • 開発者向けのコスト削減と効率向上
    • 価格: 入力$2.50/1Mトークン、出力$10.00/1Mトークン=現行のGPT-4o(2024-05-13)に対して入力コスト最大50%、出力コスト最大33%削減

Azure OpenAIサービスは、最新の大規模言語モデル(LLM)による高度なテキスト生成機能を提供しています。その中でも特に注目すべきは、新たに導入された Structured Outputs機能です。この機能を使うことで、AIによる出力を事前定義されたスキーマに強制的に従わせることができ、ビジネスプロセスの効率化やデータ分析の精度向上に大きく貢献します。

この記事では、Structured Outputsのメリットと、Azure OpenAIを使用した具体的な実装方法について、Pythonのコード例を交えて詳しく解説します。さらに、実用的なユースケースを紹介し、どのようにしてこの機能が日常業務に役立つかを探ります。また、以前に書いた記事「OpenAI APIでのStructured Outputsの実装ガイド」も併せて参照することで、Azure OpenAIとOpenAI APIの違いについても理解を深めることができます。

https://zenn.dev/chips0711/articles/ab49bde721375e

なぜStructured Outputsが必要なのか?

Structured Outputsとは、モデルから生成されるテキストを特定のスキーマ(データ構造)に従わせることで、LLMの出力の一貫性と信頼性を確保する技術です。通常、LLMは指示通りの出力を生成しますが、確率論的な性質を持つため、100%正確に指示通りの出力を保証することはできません。

例えば、データベースに入力するための情報を生成する場合、出力が正しい形式を満たさないと、データが正しく保存されない可能性があります。Structured Outputsを使えば、こうしたリスクを回避し、常に正確なフォーマットでの出力を確保できます。

Azure OpenAIでのStructured Outputsの概要

2024年8月にリリースされたGPT-4oモデルでは、新たに構造化出力の機能が追加されました。これにより、特定のJSONスキーマに従った出力を生成することが可能になり、関数呼び出しやデータ抽出、複雑なワークフローの構築が容易になります。

サポートされているモデル

現時点(2024年9月4日時点)では、GPT-4oバージョン2024-08-06のみがStructured Outputsをサポートしています。

APIサポート

Structured Outputsは、APIバージョン2024-08-01-previewで初めてサポートされました。

Pythonでの利用例

以下に、Pydanticを使用してオブジェクトスキーマを定義し、Structured Outputsを行う例を示します。

from pydantic import BaseModel
from openai import AzureOpenAI
import os
from dotenv import load_dotenv

load_dotenv()

client = AzureOpenAI(
  azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), 
  api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
  api_version="2024-08-01-preview"
)

class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]

completion = client.beta.chat.completions.parse(
    model="MODEL_DEPLOYMENT_NAME",  # デプロイされたモデル名に置き換えてください
    messages=[
        {"role": "system", "content": "イベント情報を抽出してください。"},
        {"role": "user", "content": "アリスとボブは金曜日に科学フェアに行きます。"},
    ],
    response_format=CalendarEvent,
)

event = completion.choices[0].message.parsed

print(completion.choices[0].message)
実行結果
ParsedChatCompletionMessage[CalendarEvent](content='{"name":"科学フェア","date":"金曜日","participants":["アリス","ボブ"]}', refusal=None, role='assistant', function_call=None, tool_calls=[], parsed=CalendarEvent(name='科学フェア', date='金曜日', participants=['アリス', 'ボブ']))

このコードは、ユーザーの入力からイベント情報を抽出し、定義されたスキーマに基づいてJSON形式で出力します。

構造化出力の制約とサポートされているスキーマ

Structured Outputの利用にはいくつかの制約があります。以下に、サポートされているスキーマと制約について詳しく説明します。

サポートされているスキーマの種類

  • String: 文字列型。例えば、名前や場所など。
  • Number: 数値型。小数点を含む数値。
  • Boolean: 真偽値型。trueまたはfalse
  • Integer: 整数型。小数点を含まない数値。
  • Object: オブジェクト型。複数のプロパティを持つ構造。
  • Array: 配列型。複数の要素を持つリスト。
  • Enum: 列挙型。特定の値のセット。
  • anyOf: いずれかの型。複数の型の中からいずれか一つ。

制約事項

  1. 必須フィールド: すべてのフィールドまたは関数のパラメータは必須である必要があります。例えば、JSONスキーマでrequiredとして指定する必要があります。
  2. 追加プロパティの制限: additionalPropertiesfalseに設定する必要があります。これにより、スキーマで定義されていないキーと値のペアがオブジェクトに追加されることを防ぎます。
  3. 非サポートのキーワード: 以下のキーワードは、特定の型に対してサポートされていません。
    • String: minLength, maxLength, pattern, format
    • Number: minimum, maximum, multipleOf
    • Object: patternProperties, unevaluatedProperties, propertyNames, minProperties, maxProperties
    • Array: unevaluatedItems, contains, minContains, maxContains, minItems, maxItems, uniqueItems
  4. 並列関数呼び出しの制限: 構造化出力は並列関数呼び出しをサポートしていません。並列ツール呼び出しを行う場合、parallel_tool_callsfalseに設定する必要があります。

Azure OpenAIでのStructured Outputsの実用例

Structured Outputsの効果的な利用には、具体的なユースケースに基づく適切な設計が重要です。以下に、いくつかの典型的なユースケースと、それぞれに合わせたスキーマ設計の例を紹介します。

※以下は、あくまで想像上の例であり、実際の事例に即しているわけではありません。適宜、業務に即して応用いただければと思います。

1. 医療保険コールセンターでの要約

医療保険会社のコールセンターでは、通話内容を迅速かつ正確に要約する必要があります。これは、保険請求の処理や顧客対応の質を向上させるためです。

:

顧客からの通話内容を要約し、保険請求の進行状況を確認するスキーマ


{
  "type": "object",
  "description": "医療保険コールセンターでの通話要約を保持するためのスキーマ",
  "properties": {
    "call_id": { 
      "type": "string",
      "description": "通話の一意識別子" 
    },
    "customer_id": { 
      "type": "string",
      "description": "顧客の一意識別子" 
    },
    "call_date": { 
      "type": "string",
      "description": "通話日" 
    },
    "issue_type": { 
      "type": "string",
      "description": "クレームの種類" 
    },
    "policy_number": { 
      "type": "string",
      "description": "保険証書番号" 
    },
    "policy_holder_name": { 
      "type": "string",
      "description": "保険契約者の名前" 
    },
    "relationship_to_patient": { 
      "type": "string",
      "description": "患者との関係" 
    },
    "patient_name": { 
      "type": "string",
      "description": "患者の名前" 
    },
    "hospital_name": { 
      "type": "string",
      "description": "病院名" 
    },
    "date_of_treatment": { 
      "type": "string",
      "description": "治療日" 
    },
    "treatment_type": { 
      "type": "string",
      "description": "治療の種類" 
    },
    "claim_amount": { 
      "type": "integer",
      "description": "請求額" 
    },
    "claim_status": { 
      "type": "string",
      "description": "請求のステータス" 
    },
    "action_required": { 
      "type": "string",
      "description": "必要なアクション" 
    },
    "sentiment": { 
      "type": "string",
      "description": "顧客の感情" 
    },
    "call_summary": {
      "type": "object",
      "description": "通話内容の要約",
      "properties": {
        "issue_summary": { 
          "type": "string",
          "description": "問題の概要" 
        },
        "claim_details": {
          "type": "object",
          "description": "請求の詳細",
          "properties": {
            "amount": { 
              "type": "string",
              "description": "請求額" 
            },
            "status": { 
              "type": "string",
              "description": "請求のステータス" 
            },
            "required_action": { 
              "type": "string",
              "description": "必要なアクション" 
            }
          },
          "required": ["amount", "status", "required_action"],
          "additionalProperties": false
        },
        "customer_emotion": { 
          "type": "string",
          "description": "顧客の感情" 
        }
      },
      "required": ["issue_summary", "claim_details", "customer_emotion"],
      "additionalProperties": false
    }
  },
  "required": ["call_id", "customer_id", "call_date", "issue_type", "policy_number", "policy_holder_name", "relationship_to_patient", "patient_name", "hospital_name", "date_of_treatment", "treatment_type", "claim_amount", "claim_status", "action_required", "sentiment", "call_summary"],
  "additionalProperties": false
}

このスキーマは、保険請求に関する問い合わせの詳細な情報を収集し、効率的な処理をサポートします。

2. 製品不具合の顧客報告分析

家電メーカーは、製品の信頼性向上のために顧客からの不具合報告を詳細に分析する必要があります。

製品不具合報告の分析を支援するスキーマ
{
  "type": "object",
  "description": "製品不具合の顧客報告を分析するためのスキーマ",
  "properties": {
    "report_id": { 
      "type": "string",
      "description": "報告の一意識別子" 
    },
    "customer_id": { 
      "type": "string",
      "description": "顧客の一意識別子" 
    },
    "product_id": { 
      "type": "string",
      "description": "製品の一意識別子" 
    },
    "report_date": { 
      "type": "string",
      "description": "報告日" 
    },
    "issue_type": { 
      "type": "string",
      "description": "問題の種類" 
    },
    "severity": { 
      "type": "string",
      "description": "問題の重大度" 
    },
    "issue_description": { 
      "type": "string",
      "description": "問題の詳細な説明" 
    },
    "action_taken": { 
      "type": "string",
      "description": "取られたアクション" 
    },
    "customer_feedback": { 
      "type": "string",
      "description": "顧客からのフィードバック" 
    },
    "call_summary": {
      "type": "object",
      "description": "報告の要約",
      "properties": {
        "issue_summary": { 
          "type": "string",
          "description": "問題の概要" 
        },
        "severity": { 
          "type": "string",
          "description": "問題の重大度" 
        },
        "actions_taken": { 
          "type": "array",
          "items": { "type": "string" },
          "description": "取られたアクションのリスト"
        },
        "customer_emotion": { 
          "type": "string",
          "description": "顧客の感情" 
        }
      },
      "required": ["issue_summary", "severity", "actions_taken", "customer_emotion"],
      "additionalProperties": false
    }
  },
  "required": ["report_id", "customer_id", "product_id", "report_date", "issue_type", "severity", "issue_description", "action_taken", "customer_feedback", "call_summary"],
  "additionalProperties": false
}

このスキーマは、顧客のフィードバックや行動履歴を分析し、製品の改良点を特定するのに役立ちます。

3. サーバー障害対応のインシデントレポート

IT運用チームは、サーバー障害の発生時に迅速に対応し、その詳細を記録して再発防止策を検討します。

:

サーバー障害時の対応を記録するためのスキーマ
{
  "type": "object",
  "description": "サーバー障害対応のインシデントレポートを記録するためのスキーマ",
  "properties": {
    "incident_id": { 
      "type": "string",
      "description": "インシデントの一意識別子" 
    },
    "server_id": { 
      "type": "string",
      "description": "サーバーの一意識別子" 
    },
    "incident_date": { 
      "type": "string",
      "description": "インシデント発生日" 
    },
    "incident_type": { 
      "type": "string",
      "description": "インシデントの種類" 
    },
    "severity": { 
      "type": "string",
      "description": "インシデントの重大度" 
    },
    "issue_description": { 
      "type": "string",
      "description": "問題の詳細な説明" 
    },
    "root_cause": { 
      "type": "string",
      "description": "問題の根本原因" 
    },
    "action_taken": { 
      "type": "array",
      "items": { "type": "string" },
      "description": "取られたアクションのリスト"
    },
    "downtime_duration": { 
      "type": "string",
      "description": "ダウンタイムの期間" 
    },
    "impact_analysis": { 
      "type": "string",
      "description": "影響分析" 
    },
    "recommended_actions": { 
      "type": "string",
      "description": "推奨されるアクション" 
    },
    "incident_summary": {
      "type": "object",
      "description": "インシデントの要約",
      "properties": {
        "issue_summary": { 
          "type": "string",
          "description": "問題の概要" 
        },
        "root_cause": { 
          "type": "string",
          "description": "問題の根本原因" 
        },
        "actions_taken": { 
         

 "type": "array",
          "items": { "type": "string" },
          "description": "取られたアクションのリスト"
        },
        "customer_impact": { 
          "type": "string",
          "description": "顧客への影響" 
        },
        "recommended_next_steps": { 
          "type": "string",
          "description": "次の推奨ステップ" 
        }
      },
      "required": ["issue_summary", "root_cause", "actions_taken", "customer_impact", "recommended_next_steps"],
      "additionalProperties": false
    }
  },
  "required": ["incident_id", "server_id", "incident_date", "incident_type", "severity", "issue_description", "root_cause", "action_taken", "downtime_duration", "impact_analysis", "recommended_actions", "incident_summary"],
  "additionalProperties": false
}

このスキーマは、サーバー障害の詳細とその後の対応を記録し、同様の問題が再発しないようにするための参考になります。

4. 新卒採用プロセスの進捗管理

新卒採用プロセスを管理し、各ステージの完了状況とフィードバックを記録することで、採用活動の効率を向上させます。

:

新卒採用プロセスの進捗を管理するためのスキーマ
{
  "type": "object",
  "description": "新卒採用プロセスの進捗を管理するためのスキーマ",
  "properties": {
    "process_id": { 
      "type": "string",
      "description": "プロセスの一意識別子" 
    },
    "candidate_id": { 
      "type": "string",
      "description": "候補者の一意識別子" 
    },
    "application_date": { 
      "type": "string",
      "description": "応募日" 
    },
    "current_stage": { 
      "type": "string",
      "description": "現在のステージ" 
    },
    "next_stage": { 
      "type": "string",
      "description": "次のステージ" 
    },
    "assessment_scores": {
      "type": "object",
      "description": "評価スコア",
      "properties": {
        "aptitude_test": { 
          "type": "integer",
          "description": "適性テストのスコア" 
        },
        "group_discussion": { 
          "type": "integer",
          "description": "グループディスカッションのスコア" 
        },
        "technical_test": { 
          "type": "integer",
          "description": "技術テストのスコア" 
        },
        "initial_interview": { 
          "type": "integer",
          "description": "初期面接のスコア" 
        }
      },
      "required": ["aptitude_test", "group_discussion", "technical_test", "initial_interview"],
      "additionalProperties": false
    },
    "feedback": {
      "type": "object",
      "description": "各テストに対するフィードバック",
      "properties": {
        "aptitude_test": { 
          "type": "string",
          "description": "適性テストに関するフィードバック" 
        },
        "group_discussion": { 
          "type": "string",
          "description": "グループディスカッションに関するフィードバック" 
        },
        "technical_test": { 
          "type": "string",
          "description": "技術テストに関するフィードバック" 
        },
        "initial_interview": { 
          "type": "string",
          "description": "初期面接に関するフィードバック" 
        }
      },
      "required": ["aptitude_test", "group_discussion", "technical_test", "initial_interview"],
      "additionalProperties": false
    },
    "interviewers_comments": { 
      "type": "string",
      "description": "面接官のコメント" 
    },
    "status": { 
      "type": "string",
      "description": "採用プロセスの進捗状況" 
    },
    "candidate_summary": {
      "type": "object",
      "description": "候補者の概要",
      "properties": {
        "current_performance": { 
          "type": "string",
          "description": "現在のパフォーマンス" 
        },
        "next_steps": { 
          "type": "string",
          "description": "次のステップ" 
        }
      },
      "required": ["current_performance", "next_steps"],
      "additionalProperties": false
    }
  },
  "required": ["process_id", "candidate_id", "application_date", "current_stage", "next_stage", "assessment_scores", "feedback", "interviewers_comments", "status", "candidate_summary"],
  "additionalProperties": false
}

このスキーマは、採用プロセス全体の透明性を高め、適切な判断を行うための基盤を提供します。

Structured OutputのPythonによる実装

次に、Azure OpenAIを使用してStructured Outputを実現するための具体的なコード例を紹介します。ここでは、GPT-4oによるコールセンターのクレーム対応の事例を基にした実装を行います。

コンテキストの取得方法について

コンテキスト(例: Call ID, Customer ID, Issue Type など)は、通常、CRMシステムや通話ログデータベースから取得されます。これらの情報は、通話内容の要約を正確に行い、次のアクションを決定するために不可欠です。この情報を利用して、モデルが正しいコンテキストに基づいた出力を生成できるようになります。

今回はわかりやすくGPT-4oに関西弁クレーム風コールログ(以下の画像参照)を生成してもらい、contextに突っ込みました。

JSONスキーマによるStructured Outputの実装例

from openai import AzureOpenAI
import json
from dotenv import load_dotenv()

# 環境変数のロード
load_dotenv()

# Azure OpenAIクライアントの初期化
client = AzureOpenAI(
    azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),
    api_version="2024-08-01-preview"
)

# Structured Outputのスキーマ定義
complaint_handling_function = {
    "type": "function",
    "function": {
        "name": "analyze_complaint_handling",
        "description": "コールセンターでのクレーム対応を分析し、要約します。",
        "parameters": {
            "type": "object",
            "properties": {
                "call_id": {"type": "string", "description": "通話の一意識別子"},
                "customer_id": {"type": "string", "description": "顧客の一意識別子"},
                "call_date": {"type": "string", "description": "通話日"},
                "issue_type": {"type": "string", "description": "クレームの種類"},
                "policy_number": {"type": "string", "description": "保険証書番号"},
                "claim_amount": {"type": "integer", "description": "請求額"},
                "action_taken": {"type": "string", "description": "対応内容"},
                "operator_stress_level": {"type": "string", "description": "オペレーターのストレス度合い"},
                "urgency": {"type": "string", "description": "クレーム対応の緊急度"},
                "call_summary": {
                    "type": "object",
                    "description": "通話内容の要約",
                    "properties": {
                        "issue_summary": {"type": "string", "description": "問題の概要"},
                        "claim_details": {
                            "type": "object",
                            "description": "請求の詳細",
                            "properties": {
                                "amount": {"type": "string", "description": "請求額"},
                                "status": {"type": "string", "description": "請求のステータス"},
                                "required_action": {"type": "string", "description": "必要なアクション"}
                            },
                            "required": ["amount", "status", "required_action"],
                            "additionalProperties": false
                        },
                        "customer_emotion": {"type": "string", "description": "顧客の感情"}
                    },
                    "required": ["issue_summary", "claim_details", "customer_emotion"],
                    "additionalProperties": false
                }
            },
            "required": ["call_id", "customer_id", "call_date", "issue_type", "policy_number", "claim_amount", "action_taken", "operator_stress_level", "urgency", "call_summary"],
            "additionalProperties": false
        }
    },
    "strict": True
}

# モデルに提供するプロンプトの設定
complaint_handling_prompt = """あなたはコールセンターのクレーム対応を分析する担当者です。
通話内容に基づいて、以下のスキーマに従ってクレーム対応の要約を作成してください。
"""

# 例としての通話内容をテスト
example_input = {
    "user_input": """
オペレーター: こんにちは、保険のXYZカスタマーサポートです。本日はどのようなご用件でしょうか?
顧客: ちょっとええ加減にしてや!先月やで、先月に事故に遭うて、保険金の支払い待っとるんやけど、まだなんの連絡もないやんか!どうなっとんねん!
オペレーター: 大変申し訳ございません。すぐに確認させていただきます。まず、保険証書番号を教えていただけますか?
顧客: そんなん、保険証書番号はXXXXXXXXや。急いでや!
オペレーター: 確認いたしました。お客様の事故について、もう一度詳細を伺えますでしょうか?
顧客: もう何回も同じこと言うとるやろ!信号待ちしとったら、後ろから車が突っ込んできて、相手が居眠り運転しとったんや。警察も来て、事故証明書ももらうた。すぐに保険会社に連絡して、修理の見積もりも提出したけど、まだ何も進んどらんやんか!
オペレーター: ご不便をおかけしており、申し訳ございません。確認したところ、修理見積書はすでに受領しており、現在支払い手続き中でございます。金額は70万円で、代車の手配も含まれております。通常、このプロセスには2週間ほどかかりますが、現在進行中ですので、もう少しだけお待ちいただけますでしょうか?
顧客: 2週間!?待たれへんわ!代車もまだ手配されてへんのか?ほんま、使えんサービスやな!
オペレーター: お気持ちは十分に理解しております。代車の手配が遅れていることについて、担当部署に優先対応をお願いするよう手配いたします。また、支払い手続きの迅速化も依頼いたします。
顧客: 分かったけど、ほんまに早うせなあかんで。待つのはもううんざりや!
オペレーター: かしこまりました。今後は私が直接フォローアップさせていただきますので、何か進展がありましたらすぐにご連絡いたします。
顧客: それやったら安心やけど、ちゃんと頼むで。これ以上待たされたらほんまにキレるで!
オペレーター: はい、迅速に対応いたします。他にご質問やご不明な点はございますか?
顧客: 今はええわ。ほんま、頼むで!
オペレーター: ご迷惑をおかけして申し訳ございませんでした。失礼いたします。
顧客: 失礼するわ。ほんまに頼むで。
""",
    "context": "Call ID: C67890, Customer ID: 〇〇〇〇, Call Date: 2024-09-01, Issue Type: クレーム対応, Policy Number: XXXXXXXX"
}

def get_response(user_input, context):
    response = client.chat.completions.create(
        model=GPT4o_DEPLOYMENT_NAME,
        temperature=0,
        messages=[
            {"role": "system", "content": complaint_handling_prompt},
            {"role": "user", "content": f"CONTEXT: {context}\n USER INPUT: {user_input}"}
        ],
        tools=[complaint_handling_function]
    )
    return response.choices[0].message.tool_calls

result = get_response(example_input["user_input"], example_input["context"])
tool_call = result[0].function.arguments
json_data = json.loads(tool_call)
print(json.dumps(json_data, indent=4, ensure_ascii=False))

出力例

出力例
{
    "call_id": "C67890",
    "customer_id": "〇〇〇〇",
    "call_date": "2024-09-01",
    "issue_type": "クレーム対応",
    "policy_number": "XXXXXXXX",
    "claim_amount": 700000,
    "action_taken": "代車の手配と支払い手続きの迅速化を担当部署に依頼。オペレーターが直接フォローアップを約束。",
    "operator_stress_level": "高",
    "urgency": "高",
    "call_summary": {
        "issue_summary": "事故後の保険金支払いが遅延。代車の手配も未完了。",
        "claim_details": {
            "amount": "70万円",
            "status": "支払い手続き中",
            "required_action": "代車の手配と支払い手続きの迅速化"
        },
        "customer_emotion": "怒りと不満"
    }
}

PydanticモデルによるStructured Outputの実装例

次に、Pydanticモデルを使ったStructured Outputの実装例を示します。この例では、PythonのデータクラスのようにPydanticモデルを定義し、Azure OpenAI APIに対してそのモデルを使用して出力の形式を指定します。

from pydantic import BaseModel, Field
from openai import AzureOpenAI
from dotenv import load_dotenv
import os
import json

# 環境変数のロード
load_dotenv()

# Azure OpenAIクライアントの初期化
client = AzureOpenAI(
    azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),
    api_version="2024-08-01-preview"
)

# Pydanticモデルの定義
class ClaimDetails(BaseModel):
    """
    クレームの詳細情報を保持するモデル。
    """
    amount: str = Field(..., description="請求額を表します。例: '70万円'")
    status: str = Field(..., description="請求のステータス。例: '支払い手続き中'")
    required_action: str = Field(..., description="必要なアクション。例: '代車手配の優先対応'")

    class Config:
        extra = "forbid"  # additionalProperties: false を設定

class CallSummary(BaseModel):
    """
    通話の要約を保持するモデル。
    """
    issue_summary: str = Field(..., description="問題の概要を表します。例: '事故に遭い、保険金の支払いが遅れている。'")
    claim_details: ClaimDetails = Field(..., description="クレームの詳細情報を含むオブジェクト。")
    customer_emotion: str = Field(..., description="顧客の感情。例: '怒り、不満'")

    class Config:
        extra = "forbid"  # additionalProperties: false を設定

class ComplaintHandling(BaseModel):
    """
    クレーム対応の全体的な情報を保持するモデル。
    """
    call_id: str = Field(..., description="通話の一意識別子。")
    customer_id: str = Field(..., description="顧客の一意識別子。")
    call_date: str = Field(..., description="通話日。")
    issue_type: str = Field(..., description="クレームの種類。例: 'クレーム対応'")
    policy_number: str = Field(..., description="保険証書番号。")
    claim_amount: int = Field(..., description="請求額。整数で表現されます。")
    action_taken: str = Field(..., description="取られた対応。")
    operator_stress_level: str = Field(..., description="オペレーターのストレス度合い。")
    urgency: str = Field(..., description="クレーム対応の緊急度。")
    call_summary: CallSummary = Field(..., description="通話の要約情報を含むオブジェクト。")

    class Config:
        extra = "forbid"  # additionalProperties: false を設定

# モデルに提供するプロンプトの設定
complaint_handling_prompt = """あなたはコールセンターのクレーム対応を分析する担当者です。
通話内容に基づいて、以下のスキーマに従ってクレーム対応の要約を作成してください。
"""

# 例としての通話内容をテスト
example_input = {
    "user_input": """
オペレーター: こんにちは、保険のXYZカスタマーサポートです。本日はどのようなご用件でしょうか?
顧客: ちょっとええ加減にしてや!先月やで、先月に事故に遭うて、保険金の支払い待っとるんやけど、まだなんの連絡もないやんか!どうなっとんねん!
オペレーター: 大変申し訳ございません。すぐに確認させていただきます。まず、保険証書番号を教えていただけますか?
顧客: そんなん、保険証書番号はXXXXXXXXや。急いでや!
オペレーター: 確認いたしました。お客様の事故について、もう一度詳細を伺えますでしょうか?
顧客: もう何回も同じこと言うとるやろ!信号待ちしとったら、後ろから車が突っ込んできて、相手が居眠り運転しとったんや。警察も来て、事故証明書ももらうた。すぐに保険会社に連絡して、修理の見積もりも提出したけど、まだ何も進んどらんやんか!
オペレーター: ご不便をおかけしており、申し訳ございません。確認したところ、修理見積書はすでに受領しており、現在支払い手続き中でございます。金額は70万円で、代車の手配も含まれております。通常、このプロセスには2週間ほどかかりますが、現在進行中ですので、もう少しだけお待ちいただけますでしょうか?
顧客: 2週間!?待たれへんわ!代車もまだ手配されてへんのか?ほんま、使えんサービスやな!
オペレーター: お気持ちは十分に理解しております。代車の手配が遅れていることについて、担当部署に優先対応をお願いするよう手配いたします。また、支払い手続きの迅速化も依頼いたします。
顧客: 分かったけど、ほんまに早うせなあかんで。待つのはもううんざりや!
オペレーター: かしこまりました。今後は私が直接フォローアップさせていただきますので、何か進展がありましたらすぐにご連絡いたします。
顧客: それやったら安心やけど、ちゃんと頼むで。これ以上待たされたらほんまにキレるで!
オペレーター: はい、迅速に対応いたします。他にご質問やご不明な点はございますか?
顧客: 今はええわ。ほんま、頼むで!
オペレーター: ご迷惑をおかけして申し訳ございませんでした。失礼いたします。
顧客: 失礼するわ。ほんまに頼むで。
""",
    "context": "Call ID: C67890, Customer ID: 〇〇〇〇, Call Date: 2024-09-01, Issue Type: クレーム対応, Policy Number: XXXXXXXX"
}

# PydanticモデルによるStructured Outputの例
completion = client.beta.chat.completions.parse(
    model="gpt-4o",  # デプロイされたモデル名に置き換えてください
    messages=[
        {"role": "system", "content": complaint_handling_prompt},
        {"role": "user", "content": f"CONTEXT: {example_input['context']}\n USER INPUT: {example_input['user_input']}"}
    ],
    response_format=ComplaintHandling
)

complaint_data = completion.choices[0].message.parsed
json_data = json.dumps(complaint_data.model_dump(), indent=4, ensure_ascii=False)
print(json_data)

出力例

出力例
{
    "call_id": "C67890",
    "customer_id": "〇〇〇〇",
    "call_date": "2024-09-01",
    "issue_type": "クレーム対応",
    "policy_number": "XXXXXXXX",
    "claim_amount": 700000,
    "action_taken": "代車の優先手配と支払い手続きの迅速化を依頼。オペレーターが今後フォローアップを担当。",
    "operator_stress_level": "中",
    "urgency": "高",
    "call_summary": {
        "issue_summary": "事故に遭い、保険金の支払いが遅れている。",
        "claim_details": {
            "amount": "70万円",
            "status": "支払い手続き中",
            "required_action": "代車手配の優先対応"
        },
        "customer_emotion": "怒り、不満"
    }
}

このPydanticモデルによる実装でも、前述のJSONスキーマと同じ入力を使用し、結果として同じ内容の出力を生成することができます。これにより、JSONスキーマによるStructured OutputとPydanticモデルによるStructured Outputが同等の結果を生むことを確認できます。

結論と次のステップ

Azure OpenAIを使ったStructured Outputは、企業のデータ処理と意思決定プロセスを大幅に効率化する強力なツールです。本記事では、具体的なユースケースとPythonによる実装方法を通して、その利点を明確にしました。また、コンテキストの取得方法についても言及し、CRMシステムやデータベースからの情報取得の重要性を強調しました。

次に、あなたのプロジェクトにこの技術をどのように適用できるかを考え、実際にコードを試してみてください。また、以前に書いた記事「OpenAI APIでのStructured Outputの実装ガイド」も参照し、OpenAI APIとAzure OpenAIのStructured Outputの違いを理解することをお勧めします。

この記事が役立ったと感じた場合は、ぜひシェアやいいねをお願いいたします。また、他のAzure OpenAI機能についても知りたい場合は、引き続き関連する記事をチェックしてみてください。

参考文献

【免責事項】

本記事の情報は執筆時点(2024年9月4日)のものです。本記事は、公開されている情報に基づいて作成されていますが、誤りが含まれている可能性もあります。内容の正確性については、読者ご自身の責任で判断をお願いいたします。AI技術は急速に進化しており、製品の仕様、価格、可用性などが予告なく変更される可能性があります。最新かつ正確な情報については、常にOpenAIの公式ドキュメントおよび関連するサービスプロバイダーの最新情報をご確認ください。また、本記事の内容は一般的な情報提供を目的としており、専門的なアドバイスとしては意図していません。具体的な導入や利用に関しては、適切な専門家にご相談ください。

また、この記事に記載されている例(顧客コード、保険証書番号、その他の情報)は、全て仮想のものであり、実在する個人や企業の情報とは一切関係ありません。これらの例は、Azure OpenAIのStructured Output機能を説明するための参考として用いられているものであり、実際のデータや機密情報を使用しているわけではありませんので、ご安心ください。

Discussion