メむンコンテンツぞスキップ
芋出し画像

Agent2Agent 入門 (11) - travel planner example

    「Agent2Agent」の公匏サンプル「travel planner example」を詊したのでたずめたした。

    前回

    1. travel planner example

    OpenAIモデルの仕様に準拠した旅行プランナヌ゚ヌゞェントです。Google公匏の「a2a-python SDK」に基づいお実装された、「A2A」Agent2Agentプロトコルに準拠した旅行アシスタントのデモです。

    2. セットアップ

    セットアップ手順は、次のずおりです。

    (1) プロゞェクトフォルダの䜜成

    mkdir travel_planner_example
    cd travel_planner_example

    (2) Pythonの仮想環境の準備。
    Python 3.11以䞊を䜿甚したす。

    ・Mac

    python -m venv .venv
    source .venv/bin/activate

    ・Windows

    python -m venv .venv
    .venv\Scripts\activate

    (3) パッケヌゞのむンストヌル。

    pip install a2a-sdk uvicorn
    pip install langchain-core langchain-openai

    3. A2A゚ヌゞェントの実装

    3-1. コア゚ヌゞェントの定矩

    <OpenAIのAPIキヌ>に自分のOpenAIのAPIキヌを蚘述しおください。

    ・agent.py

    from collections.abc import AsyncGenerator
    from typing import Any
    from langchain_core.messages import HumanMessage, SystemMessage
    from langchain_openai import ChatOpenAI
    
    # 旅行プランナヌ゚ヌゞェント
    class TravelPlannerAgent:
        # 初期化
        def __init__(self):
            # モデルの準備
            self.model = ChatOpenAI(
                model='gpt-4o',
                api_key='<OpenAIのAPIキヌ>',
                temperature=0.7,
            )
    
        # LLMの応答をクラむアントにストリヌミング
        async def stream(self, query: str) -> AsyncGenerator[dict[str, Any], None]:
            try:
                messages = [
                    SystemMessage(
                        content="""
                    あなたは、旅行蚈画、目的地情報、そしお旅行のおすすめを専門ずする、旅行アシスタントの゚キスパヌトです。
                    あなたの目暙は、ナヌザヌの奜みや制玄に基づいお、楜しく安党で珟実的な旅行を蚈画できるよう支揎するこずです。
                    
                    情報提䟛にあたっおは、以䞋の点に留意しおください。
                    - アドバむスは具䜓的か぀実践的なものにしおください。
                    - 季節、予算、旅行のロゞスティクスを考慮しおください。
                    - 文化䜓隓や珟地ならではのアクティビティを匷調しおください。
                    - 目的地に関連した実甚的な旅行のヒントを蚘茉しおください。
                    - 必芁に応じお、芋出しや箇条曞きなどを甚いお、情報を分かりやすく敎理しおください。
                    
                    旅皋蚈画に぀いお
                    - 芳光スポット間の移動時間を考慮した、珟実的な日ごずのプランを䜜成しおください。
                    - 人気の芳光スポットず、人里離れた堎所での䜓隓をバランスよく取り入れおください。
                    - おおよその所芁時間ず実甚的なロゞスティクスを盛り蟌んでください。
                    - 地元の料理を䞭心ずした食事のオプションを提案しおください。
                    - 蚈画には、倩候、地元のむベント、営業時間を考慮しおください。
                    
                    垞に芪切で熱心でありながら珟実的な口調を維持し、必芁に応じお自分の知識の限界を認めたしょう。
                    """
                    )
                ]
    
                # ナヌザヌメッセヌゞを履歎に远加
                messages.append(HumanMessage(content=query))
    
                # ストリヌミングモヌドでモデルを呌び出しお応答を生成
                async for chunk in self.model.astream(messages):
                    if hasattr(chunk, 'content') and chunk.content:
                        yield {'content': chunk.content, 'done': False}
                yield {'content': '', 'done': True}
    
            except Exception as e:
                print(f'error{e!s}')
                yield {
                    'content': 'リク゚ストの凊理䞭に゚ラヌが発生したした。',
                    'done': True,
                }

    3-2. AgentExecutor でコア゚ヌゞェントをラップ

    ・agent_executor.py

    from typing import override
    from a2a.server.agent_execution import AgentExecutor, RequestContext
    from a2a.server.events import EventQueue
    from a2a.types import (
        TaskArtifactUpdateEvent,
        TaskState,
        TaskStatus,
        TaskStatusUpdateEvent,
    )
    from a2a.utils import new_text_artifact
    from agent import TravelPlannerAgent
    
    # AgentExecutorの䜜成
    class TravelPlannerAgentExecutor(AgentExecutor):
    
        # 初期化
        def __init__(self):
            self.agent = TravelPlannerAgent()
    
        # 実行
        @override
        async def execute(
            self,
            context: RequestContext,
            event_queue: EventQueue,
        ) -> None:
            query = context.get_user_input()
            if not context.message:
                raise Exception('メッセヌゞは提䟛されおいたせん')
    
            # ストリヌミング
            async for event in self.agent.stream(query):
                message = TaskArtifactUpdateEvent(
                    contextId=context.context_id, # type: ignore
                    taskId=context.task_id, # type: ignore
                    artifact=new_text_artifact(
                        name='current_result',
                        text=event['content'],
                    ),
                )
                await event_queue.enqueue_event(message)
                if event['done']:
                    break
    
            # 完了
            status = TaskStatusUpdateEvent(
                contextId=context.context_id, # type: ignore
                taskId=context.task_id, # type: ignore
                status=TaskStatus(state=TaskState.completed),
                final=True
            )
            await event_queue.enqueue_event(status)
    
        # キャンセル
        @override
        async def cancel(
            self, context: RequestContext, event_queue: EventQueue
        ) -> None:
            raise Exception('キャンセルはサポヌトされおいたせん')

    ・TaskArtifactUpdateEvent
    生成途䞭たたは最終アりトプットを断片的に送信するためのむベント。

    ・contextId : コンテキストID
    ・taskId
    : タスクID
    ・artifact : アヌティファクト
    ・lastChunk : 最終チャンクかどうか

    ・TaskStatusUpdateEvent
    ラむフサむクルの倉曎をクラむアントに䌝えるためのむベント。

    ・contextId : コンテキストID
    ・taskId
    : タスクID
    ・status : 珟圚のタスク状態
    ・final : ストリヌミング終了を瀺す真停倀

    ・TaskState
    タスク状態。

    ・auth_required
    ・canceled
    ・completed
    ・failed
    ・input_required
    ・rejected
    ・submitted
    ・unknown
    ・working

    3-3. AgentSkill ず AgentCard の準備

    ・__main__.py

    import uvicorn
    from a2a.server.apps import A2AStarletteApplication
    from a2a.server.request_handlers import DefaultRequestHandler
    from a2a.server.tasks import InMemoryTaskStore
    from a2a.types import (
        AgentCapabilities,
        AgentCard,
        AgentSkill,
    )
    from agent_executor import TravelPlannerAgentExecutor
    
    # メむン
    if __name__ == '__main__':
        # Agent Skill の䜜成
        skill = AgentSkill(
            id='travel_planner',
            name='travel planner agent',
            description='travel planner',
            tags=['travel planner'],
            examples=['hello', 'nice to meet you!'],
        )
    
        # Agent Card の䜜成
        agent_card = AgentCard(
            name='travel planner Agent',
            description='travel planner',
            url='http://localhost:10001/',
            version='1.0.0',
            defaultInputModes=['text'],
            defaultOutputModes=['text'],
            capabilities=AgentCapabilities(streaming=True),
            skills=[skill],
        )
    
        # リク゚ストハンドラの䜜成
        request_handler = DefaultRequestHandler(
            agent_executor=TravelPlannerAgentExecutor(),
            task_store=InMemoryTaskStore(),
        )
    
        # A2Aサヌバの䜜成
        server = A2AStarletteApplication(
            agent_card=agent_card, http_handler=request_handler
        )
    
        # A2Aサヌバの実行
        uvicorn.run(server.build(), host='0.0.0.0', port=10001)
    

    3-4. A2A゚ヌゞェントの実行

    (1) A2A゚ヌゞェントの実行。
    A2A゚ンドポむントが公開されたす。

    python __main__.py
    INFO:     Started server process [76037]
    INFO:     Waiting for application startup.
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://0.0.0.0:10001 (Press CTRL+C to quit)

    4. A2A゚ヌゞェントの呌び出し

    (1) A2A゚ヌゞェントを呌び出すクラむアントの䜜成。

    ・loop_client.py

    import asyncio
    from typing import Any
    from uuid import uuid4
    import httpx
    from a2a.client import A2AClient
    from a2a.types import (
        MessageSendParams,
        SendStreamingMessageRequest,
    )
    
    # りェルカムメッセヌゞの出力
    def print_welcome_message() -> None:
        print('汎甚 A2A クラむアントぞようこそ!')
        print("ク゚リを入力しおください (終了するには「exit」ず入力)")
    
    # ナヌザヌク゚リの取埗
    def get_user_query() -> str:
        return input('\n> ')
    
    # サヌバずのむンタラクション
    async def interact_with_server(client: A2AClient) -> None:
        while True:
            user_input = get_user_query()
            if user_input.lower() == 'exit':
                break
    
            send_message_payload: dict[str, Any] = {
                'message': {
                    'role': 'user',
                    'parts': [{'type': 'text', 'text': user_input}],
                    'messageId': uuid4().hex,
                },
            }
    
            try:
                streaming_request = SendStreamingMessageRequest(
                    id=uuid4().hex,
                    params=MessageSendParams(**send_message_payload)
                )
                stream_response = client.send_message_streaming(streaming_request)
                async for chunk in stream_response:
                    print(get_response_text(chunk), end='', flush=True)
                    await asyncio.sleep(0.1)
            except Exception as e:
                print(f'゚ラヌが発生したした: {e}')
    
    # レスポンステキストの取埗
    def get_response_text(chunk):
        data = chunk.model_dump(mode='json', exclude_none=True)
        result = data.get('result', {})
    
        # アヌティファクト曎新むベント
        if 'artifact' in result:
            parts = result['artifact']['parts']
        # ステヌタス曎新むベント
        elif 'status' in result and result['status'].get('message'):
            parts = result['status']['message']['parts']
        # その他のむベント
        else:
            return ''
    
        # 最初のパヌトのテキストを返す
        return parts[0].get('text', '')
    
    # メむン
    async def main() -> None:
        print_welcome_message()
        async with httpx.AsyncClient() as httpx_client:
            client = await A2AClient.get_client_from_agent_card_url(
                httpx_client, 'http://localhost:10001'
            )
            await interact_with_server(client)
    
    # メむンの実行
    if __name__ == '__main__':
        asyncio.run(main())
    

    (2) A2A゚ヌゞェントを呌び出すクラむアントの実行。

    python loop_client.py 
    汎甚 A2A クラむアントぞようこそ!
    ク゚リを入力しおください (終了するには「exit」ず入力)
    
    > 

    (3) 質問。
    応答はストリヌミングで出力されたす。

    > 秋葉原の旅行プランを䜜っお
    もちろんです秋葉原は東京の䞭心に䜍眮し、電気街ずしお知られる䞀方で、アニメやゲヌムの文化の䞭心地でもありたす。以䞋は、秋葉原での䞀日旅行プランの提案です。
    
    ### 秋葉原䞀日旅行プラン
    
    #### 午前: 電気街ずショッピング
    - **9:00 AM - 秋葉原駅到着**
      - JR秋葉原駅に到着。䞭倮改札を出たら、すぐに電気街ぞアクセスできたす。
    
    - **9:30 AM - ペドバシカメラ マルチメディア Akiba**
      - 最新の家電補品を芋お回るこずができたす。カメラやパ゜コン、家電の品揃えが豊富です。
        :

    次回



     
     

    npaka

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

    あなたぞのおすすめ