OpenAIのAgent SDKで遊ぶ。フライトエージェント

SDKのフライトエージェントを試して見ました。
AI エージェントの自動対応のサンプルですね。
```
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

### コンテキスト

class AirlineAgentContext(BaseModel):
    passenger_name: str | None = None
    confirmation_number: str | None = None
    seat_number: str | None = None
    flight_number: str | None = None

### ツール

@function_tool(
    name_override="faq_lookup_tool", description_override="よくある質問を検索する"
)
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 or "座席" in question:
        return (
            "機内には120席あります。"
            "そのうち22席がビジネスクラス、98席がエコノミークラスです。"
            "非常口付近の席は4列目と16列目です。"
            "5列目から8列目はエコノミープラスで、足元のスペースが広くなっています。"
        )
    elif "wifi" in question:
        return "機内では無料のWi-Fiが利用できます。『Airline-Wifi』に接続してください。"
    return "申し訳ありませんが、その質問の答えはわかりません。"

@function_tool
async def update_seat(
    context: RunContextWrapper[AirlineAgentContext], confirmation_number: str, new_seat: str
) -> str:
    """
    指定された確認番号に対して座席を更新します。

    引数:
        confirmation_number: フライトの確認番号。
        new_seat: 更新する新しい座席番号。
    """
    # お客様の入力に基づいてコンテキストを更新する
    context.context.confirmation_number = confirmation_number
    context.context.seat_number = new_seat
    # 受信したハンドオフによりフライト番号が設定されていることを確認する
    assert context.context.flight_number is not None, "フライト番号は必須です"
    return f"確認番号 {confirmation_number} の座席を {new_seat} に更新しました"

### フック

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 = Agent[AirlineAgentContext](
    name="FAQエージェント",
    handoff_description="航空会社に関する質問に答える、役立つエージェントです。",
    instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
    あなたはFAQエージェントです。お客様と話している場合、恐らくトリアージエージェントから転送されたのでしょう。
    以下の手順に従ってお客様をサポートしてください。
    # 手順
    1. お客様が最後に質問した内容を特定する。
    2. faq検索ツールを使用して質問に回答する。自身の知識に頼らないでください。
    3. 質問に回答できない場合は、トリアージエージェントに転送してください。
    """,
    tools=[faq_lookup_tool],
)

seat_booking_agent = Agent[AirlineAgentContext](
    name="座席予約エージェント",
    handoff_description="フライトの座席を更新する、役立つエージェントです。",
    instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
    あなたは座席予約エージェントです。お客様と話している場合、恐らくトリアージエージェントから転送されたのでしょう。
    以下の手順に従ってお客様をサポートしてください。
    # 手順
    1. お客様に確認番号を尋ねる。
    2. お客様に希望の座席番号を尋ねる。
    3. 座席更新ツールを使用してフライトの座席を更新する。
    お客様が手順に関係ない質問をした場合は、トリアージエージェントに転送してください。
    """,
    tools=[update_seat],
)

triage_agent = Agent[AirlineAgentContext](
    name="トリアージエージェント",
    handoff_description="お客様のリクエストを適切なエージェントに振り分けるトリアージエージェントです。",
    instructions=(
        f"{RECOMMENDED_PROMPT_PREFIX} "
        "あなたは役立つトリアージエージェントです。ツールを使用して、質問を適切なエージェントに振り分けることができます。"
    ),
    handoffs=[
        faq_agent,
        handoff(agent=seat_booking_agent, on_handoff=on_seat_booking_handoff),
    ],
)

faq_agent.handoffs.append(triage_agent)
seat_booking_agent.handoffs.append(triage_agent)

### 実行

async def main():
    current_agent: Agent[AirlineAgentContext] = triage_agent
    input_items: list[TResponseInputItem] = []
    context = AirlineAgentContext()

    # 通常、お客様からの各入力はアプリへのAPIリクエストとなり、trace()でラップされます
    # ここでは、会話IDとしてランダムなUUIDを使用します
    conversation_id = uuid.uuid4().hex[:16]

    while True:
        user_input = input("メッセージを入力してください: ")
        with trace("顧客サービス", group_id=conversation_id):
            input_items.append({"content": user_input, "role": "user"})
            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"転送: {new_item.source_agent.name} から {new_item.target_agent.name} へ"
                    )
                elif isinstance(new_item, ToolCallItem):
                    print(f"{agent_name}: ツールを呼び出し中")
                elif isinstance(new_item, ToolCallOutputItem):
                    print(f"{agent_name}: ツール呼び出しの出力: {new_item.output}")
                else:
                    print(f"{agent_name}: スキップ中: {new_item.__class__.__name__}")
            input_items = result.to_input_list()
            current_agent = result.last_agent

if __name__ == "__main__":
    asyncio.run(main())


```
動かして見ましょう。
```
メッセージを入力してください: 座席はいくつありますか？
トリアージエージェント: スキップ中: HandoffCallItem
転送: トリアージエージェント から FAQエージェント へ
FAQエージェント: ツールを呼び出し中
FAQエージェント: ツール呼び出しの出力: 機内には120席あります。そのうち22席がビジネスクラス、98席がエコノミークラスです。非常口付近の席は4列目と16列目です。5列目から8列目はエコノミープラスで、足元のスペースが広く なっています。
FAQエージェント: 機内には120席あり、22席がビジネスクラス、98席がエコノミークラスです。また、非常口付近の席は4列目と16列目で、5列目から8列目はエコノミープラスになっています。
メッセージを入力してください: wifiは使えますか？
FAQエージェント: ツールを呼び出し中
FAQエージェント: ツール呼び出しの出力: 機内では無料のWi-Fiが利用できます。『Airline-Wifi』に接続してください。
FAQエージェント: 機内では無料のWi-Fiが利用できます。「Airline-Wifi」に接続してください。
メッセージを入力してください: FLT-100,1000の席を50から100に変えてください。
FAQエージェント: スキップ中: HandoffCallItem
転送: FAQエージェント から トリアージエージェント へ
トリアージエージェント: スキップ中: HandoffCallItem
転送: トリアージエージェント から 座席予約エージェント へ
座席予約エージェント: 座席変更の手続きをお手伝いします。まず、確認番号を教えていただけますか？
メッセージを入力してください: FLT-100,1000
座席予約エージェント: ありがとうございます。新しいご希望の座席番号を教えていただけますか？
メッセージを入力してください: 座席番号100でお願いします。
座席予約エージェント: ツールを呼び出し中
座席予約エージェント: ツール呼び出しの出力: 確認番号 FLT-100,1000 の座席を 100 に更新しました
座席予約エージェント: 座席を100番に更新しました。ご利用ありがとうございます。他にご質問はございますか？   
メッセージを入力してください:

```
①まず入力はトリアージエージェントに送られます。
②FAQならFAQエージェントに送られます。
③座席予約なら座席予約エージェントに送られます。

なんかそれっぽく動いてますね。

以下の記事と同じことを書いています
https://note.com/hantani/n/n19cc2bbc9e41