メインコンテンツへスキップ
見出し画像

OpenAI Agents SDK で カスタマーサービスを実装する

    以下のサンプルコードの使い方をまとめました。

    ・openai/openai-agents-python/examples/customer_service

    前回

    1. カスタマーサービス

    「OpenAI Agents SDK」で複数のAgentを用いて航空会社の問い合わせ対応システムを実装します。Agent間の「Handoff」や「Tool Calling」を通じた顧客対応の流れを構築しています。

    次の2つのToolを定義します。

    ・faq_lookup_tool()
    FAQに関する質問の回答を提供します。

    ・update_seat()
    指定された確認番号の座席を更新します。

    次の1つのHookを定義します。

    ・on_seat_booking_handoff()
    Triage Agent から Seat Booking Agent へのHandoffの際に呼ばれるHookです。Seat Booking Agent に処理を渡す前に、ランダムなフライト番号がコンテキストに設定されます。

    次の3つのAgentを定義します。

    ・faq_agent
     FAQに特化したAgentです。顧客の質問に対してfaq_lookup_tool()を用いて回答を生成します。回答できない場合は、Triage AgentへHandoffします。

    ・seat_booking_agent
    座席の更新に関する処理を担当するAgentです。顧客から確認番号と新しい座席番号を受け取り、update_seat()を用いてコンテキスト情報を更新します。座席の更新と関係ない場合は、Triage AgentへHandoffします。

    ・triage_agent
    顧客の問い合わせ内容を判別し、適切なAgent(FAQ AgentまたはSeat Booking Agent)にHandoffする役割を持つAgentです。座席予約に関しては、Handoff時にHook関数on_seat_booking_handoffを実行してフライト番号を設定しています。

    ファイル構成は、次のとおりです。

    ・customer_service
     ・main.py

    2. コードの解説

    2-1. main.py

    from __future__ import annotations as _annotations
    import asyncio
    import random
    import uuid
    from pydantic import BaseModel
    from agents import (
        Agent,
        HandoffOutputItem,
        ItemHelpers,
        MessageOutputItem,
        RunContextWrapper,
        Runner,
        ToolCallItem,
        ToolCallOutputItem,
        TResponseInputItem,
        function_tool,
        handoff,
        trace,
    )
    from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
    
    # 航空会社Agentのコンテキスト
    class AirlineAgentContext(BaseModel):
        passenger_name: str | None = None  # 乗客名
        confirmation_number: str | None = None  # 確認番号
        seat_number: str | None = None  # 座席番号
        flight_number: str | None = None  # フライト番号
    
    # FAQツール
    @function_tool(
        name_override="faq_lookup_tool", 
        description_override="FAQに関する質問の回答を提供します。"
    )
    async def faq_lookup_tool(question: str) -> str:
        if "バック" in question or "荷物" in question:
            return (
                "飛行機にはバッグを1つ持ち込むことができます。"
                "重量は50ポンド以下、サイズは22インチ x 14インチ x 9インチである必要があります。"
            )
        elif "席" in question or "飛行機" in question:
            return (
                "飛行機には120席あります。"
                "ビジネスクラスの座席は22席、エコノミークラスの座席は98席あります。"
                "非常口は4列目と16列目です。"
                "5列目から8列目はエコノミープラスで、足元に余裕があります。"
            )
        elif "WiFi" in question or "Wi-Fi" in question:
            return "飛行機には無料Wi-Fiがあります。Airline-Wifiにご参加ください"
        return "申し訳ありませんが、その質問の答えは分かりません。"
    
    # 座席更新ツール
    @function_tool
    async def update_seat(
        context: RunContextWrapper[AirlineAgentContext], confirmation_number: str, new_seat: str
    ) -> str:
        """
        指定された確認番号の座席を更新します。
    
        Args:
            confirmation_number: フライトの確認番号
            new_seat: 更新する新しいシート
        """
        # 顧客の入力に基づいてコンテキストを更新
        context.context.confirmation_number = confirmation_number
        context.context.seat_number = new_seat
    
        # incoming handoffによってフライト番号が設定されていることを確認
        assert context.context.flight_number is not None, "フライト番号は必須です。"
        return f"確認番号 {confirmation_number} の座席を {new_seat} に更新しました。"
    
    # Triage Agent から Seat Booking Agent へのHandoffの際に呼ばれるHook
    async def on_seat_booking_handoff(context: RunContextWrapper[AirlineAgentContext]) -> None:
        flight_number = f"FLT-{random.randint(100, 999)}"
        context.context.flight_number = flight_number
    
    
    # FAQ Agentの準備
    faq_agent = Agent[AirlineAgentContext](
        name="FAQ Agent",
        handoff_description="航空会社に関する質問に答えてくれる親切なAgentです。",
        instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
        あなたは FAQ Agentです。顧客と話しているときは、Triage Agentから転送された可能性があります。
        次の手順に従って顧客をサポートします。
        # 手順
        1. 顧客が最後に尋ねた質問を特定します。
        2. faq_lookup_toolを使用して質問に答えます。自分の知識に頼らないでください。
        3. 質問に答えられない場合は、Triage Agentに転送します。""",
        tools=[faq_lookup_tool],
    )
    
    # Seat Booking Agentの準備
    seat_booking_agent = Agent[AirlineAgentContext](
        name="Seat Booking Agent",
        handoff_description="フライトの座席を更新できる便利なAgentです。",
        instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
        あなたはSeat Booking Agentです。顧客と話しているときは、Triage Agentから転送された可能性があります。
        次の手順に従って顧客をサポートします。
        # 手順
        1. 確認番号を尋ねます。
        2. お客様に希望の座席番号を尋ねます。
        3. update_seatを使用して、フライトの座席を更新します。
        お客様が手順に関係のない質問をした場合は、Triage Agentに転送します。 """,
        tools=[update_seat],
    )
    
    # Triage Agentの準備
    triage_agent = Agent[AirlineAgentContext](
        name="Triage Agent",
        handoff_description="顧客のリクエストを適切なAgentに委任できるTriage Agentです。",
        instructions=(
            f"{RECOMMENDED_PROMPT_PREFIX} "
            "あなたは役に立つTriage Agentです。質問を他の適切なAgentに委任できます。"
        ),
        handoffs=[
            faq_agent,
            handoff(agent=seat_booking_agent, on_handoff=on_seat_booking_handoff),
        ],
    )
    
    # 相互Handoff
    faq_agent.handoffs.append(triage_agent)
    seat_booking_agent.handoffs.append(triage_agent)
    
    # メインの定義
    async def main():
        current_agent: Agent[AirlineAgentContext] = triage_agent # 最初はTriage Agent
        input_items: list[TResponseInputItem] = []
        context = AirlineAgentContext()
    
        # 会話IDにランダムなUUIDを使用
        conversation_id = uuid.uuid4().hex[:16]
    
        while True:
            # メッセージの入力
            user_input = input("メッセージを入力: ")
            with trace("Customer service", group_id=conversation_id):
                # Agentの実行
                input_items.append({"role": "user", "content": user_input})
                result = await Runner.run(current_agent, input_items, context=context)
    
                # 出力
                for new_item in result.new_items:
                    agent_name = new_item.agent.name
                    if isinstance(new_item, MessageOutputItem):
                        print(f"{agent_name}: {ItemHelpers.text_message_output(new_item)}")
                    elif isinstance(new_item, HandoffOutputItem):
                        print(
                            f"Handed off from {new_item.source_agent.name} to {new_item.target_agent.name}"
                        )
                    elif isinstance(new_item, ToolCallItem):
                        print(f"{agent_name}: Calling a tool")
                    elif isinstance(new_item, ToolCallOutputItem):
                        print(f"{agent_name}: Tool call output: {new_item.output}")
                    else:
                        print(f"{agent_name}: Skipping item: {new_item.__class__.__name__}")
                input_items = result.to_input_list()
                current_agent = result.last_agent
    
    # メインの実行
    if __name__ == "__main__":
        asyncio.run(main())
    

    3. コードの実行

    (1) ローカルPCでPython仮想環境を準備。

    (2) ローカルPCの環境変数「OPENAI_API_KEY」にAPIキーを指定。

    ・Mac

    export OPENAI_API_KEY=sk-...

    ・Windows

    set OPENAI_API_KEY=sk-...

    (3) パッケージのインストール。

    pip install openai-agents

    (4) コードの実行。

    python -m customer_service.main

    メッセージを入力: 飛行機に持ち込めるバックの数は?
    Triage Agent: Skipping item: HandoffCallItem
    Handed off from Triage Agent to FAQ Agent
    FAQ Agent: Calling a tool
    FAQ Agent: Tool call output: 飛行機にはバッグを1つ持ち込むことができます。重量は50ポンド以下、サイズは22インチ x 14インチ x 9インチである必要があります。
    FAQ Agent: 飛行機にはバッグを1つ持ち込むことができます。重量は50ポンド以下で、サイズは22インチ x 14インチ x 9インチ以内である必要があります。

    メッセージを入力: 座席の更新をお願いします。
    FAQ Agent: Skipping item: HandoffCallItem
    Handed off from FAQ Agent to Triage Agent
    Triage Agent: Skipping item: HandoffCallItem
    Handed off from Triage Agent to Seat Booking Agent
    Seat Booking Agent: 座席の更新を承ります。まず、確認番号を教えていただけますか?
    メッセージを入力: 123です。
    Seat Booking Agent: 希望する新しい座席番号を教えてください。
    メッセージを入力: 3Aです。
    Seat Booking Agent: Calling a tool
    Seat Booking Agent: Tool call output: 確認番号 123 の座席を 3A に更新しました。
    Seat Booking Agent: 座席を3Aに更新しました。ご利用ありがとうございました。
    メッセージを入力:



     
     

    npaka

     
     
    プログラマー。iPhone / Android / Unity / ROS / AI / AR / VR / RasPi / ロボット / ガジェット。年2冊ペースで技術書を執筆。アニソン / カラオケ / ギター / 猫 twitter : @npaka123

    あなたへのおすすめ