建立 Agent2Agent 代理程式

Agent Runtime 可讓您使用 Agent2Agent (A2A) 通訊協定開發及部署代理。A2A 是一項開放標準,旨在讓 AI 代理之間能順暢通訊及協作。

本文說明如何在本機開發及測試 A2A 代理,包括定義 AgentCard 和 AgentExecutor 等元件。

如要進一步瞭解如何管理已部署的代理程式,請參閱「管理已部署的代理程式」。

核心工作流程包含下列步驟:

  1. 定義重要元件
  2. 建立本機代理
  3. 測試本機代理

定義代理元件

如要建立 A2A 代理,您需要定義下列元件:AgentCard、AgentExecutor 和 ADK LlmAgent。

  • AgentCard 包含描述代理程式功能的中繼資料文件。AgentCard 就像商家名片,其他代理可透過這張名片探索您的代理功能。詳情請參閱代理資訊卡規格。
  • AgentExecutor 包含代理的核心邏輯,並定義代理處理工作的方式。您可以在這裡實作代理的行為。詳情請參閱 A2A 通訊協定規格。
  • 選用:LlmAgent 定義 ADK 代理,包括系統指令、生成模型和工具。

定義 AgentCard

下列程式碼範例定義了貨幣匯率代理的 AgentCard:

from a2a.types import AgentCard, AgentSkill
from vertexai.agent_engines.templates.a2a import create_agent_card

# Define the skill for the CurrencyAgent
currency_skill = AgentSkill(
    id='get_exchange_rate',
    name='Get Currency Exchange Rate',
    description='Retrieves the exchange rate between two currencies on a specified date.',
    tags=['Finance', 'Currency', 'Exchange Rate'],
    examples=[
        'What is the exchange rate from USD to EUR?',
        'How many Japanese Yen is 1 US dollar worth today?',
    ],
)

# Create the agent card using the utility function
agent_card = create_agent_card(
    agent_name='Currency Exchange Agent',
    description='An agent that can provide currency exchange rates',
    skills=[currency_skill]
)

定義 AgentExecutor

下列程式碼範例定義了會回覆貨幣匯率的 AgentExecutor。它會採用 CurrencyAgent 執行個體,並初始化 ADK Runner 來執行要求。

import requests
from a2a.server.agent_execution.agent_executor import AgentExecutor
from a2a.server.agent_execution.context import RequestContext
from a2a.server.events.event_queue import EventQueue
from a2a.server.tasks import TaskUpdater
from a2a import types as a2a_types
from a2a.types import Part

from google.adk import Runner
from google.adk.agents import LlmAgent
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.genai import types as genai_types

class CurrencyAgentExecutorWithRunner(AgentExecutor):
    """Executor that takes an LlmAgent instance and initializes the ADK Runner internally."""

    def __init__(self, agent: LlmAgent):
        self.agent = agent
        self.runner = None

    def _init_adk(self):
        if not self.runner:
            self.runner = Runner(
                app_name=self.agent.name,
                agent=self.agent,
                artifact_service=InMemoryArtifactService(),
                session_service=InMemorySessionService(),
                memory_service=InMemoryMemoryService(),
            )

    async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
        task_id = context.task_id
        updater = TaskUpdater(
            event_queue=event_queue,
            task_id=task_id or "",
            context_id=context.context_id or "",
        )
        await updater.cancel()

    async def execute(
        self,
        context: RequestContext,
        event_queue: EventQueue,
    ) -> None:
        self._init_adk() # Initialize on first execute call

        if not context.message:
            return

        user_id = context.message.metadata.get('user_id') if context.message and context.message.metadata else 'a2a_user'

        updater = TaskUpdater(event_queue, context.task_id, context.context_id)
        
        task = a2a_types.Task(
            id=context.task_id,
            context_id=context.context_id,
            status=a2a_types.TaskStatus(state=a2a_types.TaskState.TASK_STATE_SUBMITTED),
            history=[context.message] if context.message else [],
        )
        await event_queue.enqueue_event(task)

        await updater.start_work()

        query = context.get_user_input()
        content = genai_types.Content(role='user', parts=[genai_types.Part.from_text(text=query)])

        try:
            session = await self.runner.session_service.get_session(
                app_name=self.runner.app_name,
                user_id=user_id,
                session_id=context.context_id,
            ) or await self.runner.session_service.create_session(
                app_name=self.runner.app_name,
                user_id=user_id,
                session_id=context.context_id,
            )

            final_event = None
            async for event in self.runner.run_async(
                session_id=session.id,
                user_id=user_id,
                new_message=content
            ):
                if event.is_final_response():
                    final_event = event

            if final_event and final_event.content and final_event.content.parts:
                response_text = "".join(
                    part.text for part in final_event.content.parts if hasattr(part, 'text') and part.text
                )
                if response_text:
                    await updater.add_artifact(
                        [Part(text=response_text)],
                        name='result',
                        last_chunk=True,
                    )
                    await updater.complete()
                    return

            await updater.update_status(
                a2a_types.TaskState.TASK_STATE_FAILED,
                message=updater.new_agent_message([Part(text='Failed to generate a final response with text content.')]),
            )

        except Exception as e:
            await updater.update_status(
                a2a_types.TaskState.TASK_STATE_FAILED,
                message=updater.new_agent_message([Part(text=f"An error occurred: {str(e)}")]),
            )

定義 LlmAgent

首先,定義 LlmAgent 要使用的貨幣換算工具:

def get_exchange_rate(
    currency_from: str = "USD",
    currency_to: str = "EUR",
    currency_date: str = "latest",
):
    """Retrieves the exchange rate between two currencies on a specified date.
    Uses the Frankfurter API (https://api.frankfurter.app/) to obtain
    exchange rate data.
    """
    try:
        response = requests.get(
            f"https://api.frankfurter.app/{currency_date}",
            params={"from": currency_from, "to": currency_to},
        )
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        return {"error": str(e)}

接著,定義使用該工具的 ADK LlmAgent。

my_llm_agent = LlmAgent(
    model='gemini-2.0-flash',
    name='currency_exchange_agent',
    description='An agent that can provide currency exchange rates.',
    instruction="""You are a helpful currency exchange assistant.
                   Use the get_exchange_rate tool to answer user questions.
                   If the tool returns an error, inform the user about the error.""",
    tools=[get_exchange_rate],
)

建立本機代理

定義代理的元件後,請建立使用 AgentCard、AgentExecutor 和 LlmAgent 的 A2aAgent 類別執行個體,開始本機測試。

from vertexai.agent_engines.templates.a2a import A2aAgent

a2a_agent = A2aAgent(
    agent_card=agent_card, # Assuming agent_card is defined
    agent_executor_builder=lambda: CurrencyAgentExecutorWithRunner(
        agent=my_llm_agent,
    )
)
a2a_agent.set_up()

A2A 代理範本可協助您建立符合 A2A 規範的服務。這項服務會做為包裝函式,為您抽象化轉換層。

測試本機代理

貨幣匯率代理支援下列三種方法:

  • handle_authenticated_agent_card
  • on_message_send
  • on_get_task

測試「handle_authenticated_agent_card」

下列程式碼會擷取代理的已驗證資訊卡,其中說明代理的功能。

# Test the `authenticated_agent_card` endpoint.
response_get_card = await a2a_agent.handle_authenticated_agent_card(request=None, context=None)
print(response_get_card)

測試「on_message_send」

下列程式碼會模擬用戶端傳送新訊息給代理。A2aAgent 會建立新工作,並傳回工作 ID。

from a2a.types import SendMessageRequest, Message, Part
from a2a.server.context import ServerCallContext

# 1. Define the message
message = Message(
    role="ROLE_USER",
    message_id="local-test-message-id",
    parts=[Part(text="What is the exchange rate from USD to EUR today?")]
)

# 2. Construct the request
request = SendMessageRequest(message=message)

# 3. Construct context
context = ServerCallContext()

# 4. Call the agent
send_message_response = await a2a_agent.on_message_send(request=request, context=context)

print(send_message_response)

測試「on_get_task」

下列程式碼會擷取工作的狀態和結果。輸出內容會顯示工作已完成,並包含「Hello World」回應構件。

from a2a.types import GetTaskRequest

# 1. Provide the task_id from the previous step.
# In a real application, you would store and retrieve this ID.
task_id_to_get = send_message_response.id

# 2. Construct the request
request = GetTaskRequest(id=task_id_to_get)

# 3. Call the agent's handler to get the task status.
# Reusing the context constructed in the previous step
task_status_response = await a2a_agent.on_get_task(request=request, context=context)

print(f"Successfully retrieved status for Task ID: {task_id_to_get}")
print("\nFull task status response:")
print(task_status_response)

後續步驟

指南

瞭解如何根據開發需求,在 Agent Platform Runtime 部署代理。

指南

搭配使用 Agent2Agent 代理和 Agent Platform Runtime。

指南

建立及部署基本代理,並使用 Gen AI Evaluation Service 評估代理

疑難排解

瞭解如何解決建立自訂代理時的常見錯誤。

資源

尋找 Google Agent Platform 的資源和支援。

資源

在 GitHub 探索 Python 版 Agent2Agent 範例。