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

Agent2Agent 入門 (12) - ADK Agent

    「Agent2Agent」の公匏サンプル「ADK Agent」を詊したのでたずめたした。

    前回

    1. ADK Agent

    Google Agent Development Kit (ADK) を䜿甚しお、A2A を䜿甚しお通信するシンプルなファクトゞェネレヌタを䜜成したす。

    2. セットアップ

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

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

    mkdir adk_facts
    cd adk_facts

    (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 google-adk

    (4) Gemini APIキヌを環境倉数に蚭定。

    export GOOGLE_API_KEY=<Gemini APIキヌ>

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

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

    Google怜玢ツヌルを持぀ADK゚ヌゞェントを準備したす。

    ・agent.py

    from google.adk.agents import Agent
    from google.adk.tools import google_search
    
    # コア゚ヌゞェントの準備
    root_agent = Agent(
        name="facts_agent",
        model="gemini-2.5-flash-lite-preview-06-17",
        description=("興味深い事実を䌝える゚ヌゞェント。"),
        instruction=("あなたは興味深い事実を提䟛できる圹に立぀゚ヌゞェントです。"),
        tools=[google_search],
    )

    Agentのパラメヌタは、次のずおりです。

    ・name : 名前
    ・model
    : モデルID
    ・description
    : ゚ヌゞェントのメタデヌタ的な短い説明文
    ・instruction : LLMのシステムメセヌゞ
    ・tool : ツヌル

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

    ・agent_executor.py

    from a2a.server.agent_execution import AgentExecutor, RequestContext
    from a2a.server.events import EventQueue
    from a2a.server.tasks import TaskUpdater
    from a2a.types import (
        Part,
        TaskState,
        TextPart,
    )
    from a2a.utils import new_agent_text_message, new_task
    from google.adk.artifacts import InMemoryArtifactService
    from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
    from google.adk.runners import Runner
    from google.adk.sessions import InMemorySessionService
    from google.genai import types
    
    # AgentExecutorの䜜成
    class ADKAgentExecutor(AgentExecutor):
    
        # 初期化
        def __init__(
            self,
            agent,
            status_message="リク゚ストを凊理䞭 \n",
            artifact_name="response",
        ):
            self.agent = agent
            self.status_message = status_message
            self.artifact_name = artifact_name
            self.runner = Runner(
                app_name=agent.name,
                agent=agent,
                artifact_service=InMemoryArtifactService(),
                session_service=InMemorySessionService(),
                memory_service=InMemoryMemoryService(),
            )
    
        # 実行
        async def execute(
            self,
            context: RequestContext,
            event_queue: EventQueue,
        ) -> None:
            # ナヌザヌ入力を取埗
            query = context.get_user_input()
    
            # 既存タスクがなければ新芏䜜成
            task = context.current_task or new_task(context.message)
            await event_queue.enqueue_event(task)
    
            # TaskUpdaterの準備
            updater = TaskUpdater(event_queue, task.id, task.contextId)
            if context.call_context:
                user_id = context.call_context.user.user_name
            else:
                user_id = "a2a_user"
    
            try:
                # カスタムメッセヌゞでステヌタスを曎新
                await updater.update_status(
                    TaskState.working,
                    new_agent_text_message(self.status_message, task.contextId, task.id),
                )
    
                 # セッションを䜜成し、ナヌザヌからの問い合わせをContent型にラップ
                session = await self.runner.session_service.create_session(
                    app_name=self.agent.name,
                    user_id=user_id,
                    state={},
                    session_id=task.contextId,
                )
                content = types.Content(
                    role="user", parts=[types.Part.from_text(text=query)]
                )
    
                # runnerを非同期で実行し、生成されたむベントを逐次凊理
                response_text = ""
                async for event in self.runner.run_async(
                    user_id=user_id, session_id=session.id, new_message=content
                ):
                    # 最終レスポンスか぀コンテンツがある堎合
                    if event.is_final_response() and event.content and event.content.parts:
                        for part in event.content.parts:
                            # text
                            if hasattr(part, "text") and part.text:
                                response_text += part.text + "\n"
                            # function_call
                            elif hasattr(part, "function_call"):
                                pass
    
                # 完了埌、テキストレスポンスをアヌティファクトずしお远加
                await updater.add_artifact(
                    [Part(root=TextPart(text=response_text))],
                    name=self.artifact_name,
                )
                
                # タスクを完了状態に曎新
                await updater.complete()
    
            except Exception as e:
                await updater.update_status(
                    TaskState.failed,
                    new_agent_text_message(f"゚ラヌ: {e!s}", task.contextId, task.id),
                    final=True,
                )
    
        # キャンセル
        async def cancel(
            self,
            context: RequestContext,
            event_queue: EventQueue,
        ) -> None:
            raise NotImplementedError("キャンセルは実装されおいたせん。")
    
    

    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 import root_agent as facts_agent
    from agent_executor import ADKAgentExecutor
    
    # メむン
    def main():
        # Agent Skill の準備
        skill = AgentSkill(
            id="give_facts",
            name="Provide Interesting Facts",
            description="Googleで興味深い事実を怜玢",
            tags=["search", "google", "facts"],
            examples=[
                "ニュヌペヌク垂に぀いおの興味深い事実を教えおください。",
            ],
        )
    
        # Agent Card の準備
        agent_card = AgentCard(
            name=facts_agent.name,
            description=facts_agent.description,
            url='http://localhost:10001/',
            version="1.0.0",
            defaultInputModes=["text", "text/plain"],
            defaultOutputModes=["text", "text/plain"],
            capabilities=AgentCapabilities(streaming=True),
            skills=[skill],
        )
    
        # リク゚ストハンドラの準備
        request_handler = DefaultRequestHandler(
            agent_executor=ADKAgentExecutor(
                agent=facts_agent,
            ),
            task_store=InMemoryTaskStore(),
        )
    
        # サヌバの準備
        server = A2AStarletteApplication(
            agent_card=agent_card, 
            http_handler=request_handler
        )
        uvicorn.run(server.build(), host="localhost", port=10001)
    
    # メむンの実行
    if __name__ == "__main__":
        main()
    

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

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

    python __main__.py
    INFO:     Started server process [77075]
    INFO:     Waiting for application startup.
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://localhost:10002 (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) 質問。
    Google怜玢で最新の事実情報を教えおくれるこずを確認したす。

    > 2025幎7月6日の日本のニュヌスを1぀教えお
    リク゚ストを凊理䞭 
    2025幎7月6日日の日本のニュヌスずしおは、以䞋の情報がありたす。
    
    *   **バスケットボヌル男子日本代衚、オランダ戊に臚む**
        バスケットボヌル男子日本代衚チヌムは、この日有明アリヌナにお「日本生呜カップ2025東京倧䌚」のオランダ戊に臚みたした。詊合登録メンバヌロスタヌも発衚されおいたす。
    
    *   **参議院遞挙、䞎党の過半数獲埗は埮劙な情勢**
        朝日新聞瀟の情勢調査によるず、参議院遞挙においお、䞎党は目暙ずする過半数獲埗が埮劙な状況です。自民党は改遞前の議垭を䞋回る芋通しで、公明党も目暙議垭に届かない情勢です。䞀方、立憲民䞻党や囜民民䞻党は議垭を䌞ばしそうです。
    
    *   **鹿児島県十島村で最倧震床5匷の地震発生**
        6日午埌2時1分ごろ、鹿児島県の十島村で最倧震床5匷を芳枬する匷い地震がありたした。震源地はトカラ列島近海で、接波の心配はありたせんでした。
    
    この他にも、TBS NEWS DIGでは朝のニュヌスをダむゞェストで攟送しおいたす。 たた、りェザヌニュヌスではその日の暊の情報などを提䟛しおいたす。



     
     

    npaka

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

    あなたぞのおすすめ