Como começar a usar o protocolo Agent2Agent (A2A): interações de um concierge de compras e um agente de vendas remoto no Cloud Run e no Agent Engine

1. 📖 Introdução

b013ad6b246401eb.png

O protocolo Agent2Agent (A2A) foi projetado para padronizar a comunicação entre agentes de IA, principalmente aqueles implantados em sistemas externos. Antes, esses protocolos eram estabelecidos para ferramentas chamadas Protocolo de contexto de modelo (MCP), um padrão emergente para conectar LLMs a dados e recursos. O A2A tenta complementar o MCP, já que o A2A se concentra em um problema diferente. Enquanto o MCP se concentra em reduzir a complexidade para conectar agentes com ferramentas e dados, o A2A se concentra em como permitir que os agentes colaborem nas modalidades naturais deles. Isso permite que os agentes se comuniquem como agentes (ou como usuários) em vez de ferramentas. Por exemplo, é possível ativar a comunicação de vai e vem quando você quer pedir algo.

O A2A complementa o MCP. Na documentação oficial, é recomendado que os aplicativos usem o MCP para ferramentas e o A2A para agentes, representados por AgentCard ( vamos falar sobre isso mais adiante). Os frameworks podem usar o A2A para se comunicar com o usuário, os agentes remotos e outros agentes.

83b1a03588b90b68.png

Nesta demonstração, vamos começar com a implementação do A2A usando o SDK do Python. Vamos analisar um caso de uso em que temos um concierge de compras pessoal que pode nos ajudar a conversar com agentes de vendedores de hambúrgueres e pizzas para cuidar do nosso pedido.

O A2A usa o princípio cliente-servidor. Este é o fluxo típico de A2A que você vai encontrar neste tutorial.

aa6c8bc5b5df73f1.jpeg

  1. Primeiro, o cliente A2A faz a descoberta em todos os cards de agente do servidor A2A acessíveis e usa as informações para criar um cliente de conexão.
  2. Quando necessário, o cliente A2A envia uma mensagem ao servidor A2A, que avalia isso como uma tarefa a ser concluída. Se o URL do receptor de notificações push estiver configurado no cliente A2A e for compatível com o servidor A2A, o servidor também poderá publicar o estado da progressão da tarefa no endpoint de recebimento do cliente.
  3. Depois que a tarefa for concluída, o servidor A2A vai enviar o artefato de resposta ao cliente A2A.

Durante o codelab, você vai usar uma abordagem gradual da seguinte forma:

  1. Preparar o projeto do Google Cloud
  2. Configurar o diretório de trabalho para o ambiente de programação
  3. Implantar o agente de hambúrguer no Cloud Run
  4. Implantar o agente de pizza no Cloud Run
  5. Implantar o concierge de compras no Agent Engine
  6. Interagir com o concierge de compras pela interface local

Visão geral da arquitetura

Você vai implantar a seguinte arquitetura de serviço

9cfc4582f2d8b6f3.jpeg

Você vai implantar dois serviços que vão atuar como servidor A2A: o agente Burger ( com suporte da estrutura de agente CrewAI) e o agente Pizza ( com suporte da estrutura de agente Langgraph). O usuário vai interagir diretamente apenas com o concierge de compras, que será executado usando a estrutura do Kit de desenvolvimento de agentes (ADK, na sigla em inglês), que vai atuar como cliente A2A.

Cada um desses agentes terá um ambiente e uma implantação próprios.

Pré-requisitos

  • Conhecimento de Python
  • Conhecimento básico da arquitetura full-stack usando o serviço HTTP

O que você vai aprender

  • Estrutura principal do servidor A2A
  • Estrutura principal do cliente A2A
  • Como implantar o serviço de agente no Cloud Run
  • Como implantar o serviço de agente no Agent Engine
  • Como o cliente A2A se conecta ao servidor A2A
  • Estrutura de solicitação e resposta em uma conexão sem streaming

O que é necessário

  • Navegador da Web Google Chrome
  • Uma conta do Gmail
  • Um projeto do Cloud com uma conta de faturamento ativada

Este codelab, criado para desenvolvedores de todos os níveis (incluindo iniciantes), usa Python no aplicativo de exemplo. No entanto, não é necessário ter conhecimento de Python para entender os conceitos apresentados.

2. 🚀 Preparando a configuração de desenvolvimento do workshop

Etapa 1: selecionar "Projeto ativo" no Console do Cloud

No console do Google Cloud, na página de seletor de projetos, selecione ou crie um projeto do Google Cloud (consulte a seção no canto superior esquerdo do console).

78c981437f90248.png

Clique nele para ver uma lista de todos os seus projetos, como neste exemplo:

2f5247dd825b808c.png

O valor indicado pela caixa vermelha é o ID DO PROJETO, que será usado em todo o tutorial.

Verifique se o faturamento está ativado para seu projeto do Cloud. Para verificar, clique no ícone de hambúrguer ☰ na barra superior esquerda, que mostra o menu de navegação, e encontre o menu "Faturamento".

db49b5267c00cc33.png

Se você vir a mensagem "A conta de faturamento de teste do Google Cloud Platform está vinculada", seu projeto estará pronto para ser usado neste tutorial. Caso contrário, volte ao início deste tutorial e resgate a conta de faturamento.

e44b767990aa6aab.png

Etapa 2: conhecer o Cloud Shell

Você vai usar o Cloud Shell na maior parte dos tutoriais. Clique em "Ativar o Cloud Shell" na parte de cima do console do Google Cloud. Se for preciso autorizar, clique em Autorizar.

1829c3759227c19b.png

b8fe7df5c3c2b919.png

Depois de se conectar ao Cloud Shell, precisamos verificar se o shell ( ou terminal) já está autenticado com nossa conta.

gcloud auth list

Se você vir seu Gmail pessoal como no exemplo de saída abaixo, tudo está certo.

Credentialed Accounts

ACTIVE: *
ACCOUNT: alvinprayuda@gmail.com

To set the active account, run:
    $ gcloud config set account `ACCOUNT`

Se não, tente atualizar o navegador e clique em Autorizar quando solicitado. A autorização pode ser interrompida devido a um problema de conexão.

Em seguida, também precisamos verificar se o shell já está configurado para o ID DO PROJETO correto. Se você vir um valor entre parênteses antes do ícone $ no terminal (na captura de tela abaixo, o valor é "a2a-agent-engine"), esse valor mostra o projeto configurado para sua sessão de shell ativa.

fadd80f0da3b906.png

Se o valor mostrado já estiver correto, pule o próximo comando. No entanto, se não estiver correto ou estiver faltando, execute o seguinte comando:

gcloud config set project <YOUR_PROJECT_ID>

Em seguida, clone o diretório de trabalho do modelo para este codelab do GitHub executando o seguinte comando: Ele vai criar o diretório de trabalho em purchasing-concierge-a2a.

git clone https://github.com/alphinside/purchasing-concierge-intro-a2a-codelab-starter.git purchasing-concierge-a2a

Etapa 3: conhecer o editor do Cloud Shell e configurar o diretório de trabalho do aplicativo

Agora, podemos configurar nosso editor de código para fazer algumas coisas de programação. Vamos usar o editor do Cloud Shell para isso.

Clique no botão Abrir editor para abrir um editor do Cloud Shell b16d56e4979ec951.png.

Depois disso, acesse a seção superior do editor do Cloud Shell e clique em Arquivo->Abrir pasta, encontre o diretório nome de usuário e o diretório purchasing-concierge-a2a. Em seguida, clique no botão "OK". Isso vai definir o diretório escolhido como o principal de trabalho. Neste exemplo, o nome de usuário é alvinprayuda. Portanto, o caminho do diretório é mostrado abaixo.

2c53696f81d805cc.png

253b472fa1bd752e.png

Agora, o editor do Cloud Shell vai ficar assim:

aedd0725db87717e.png

Agora, abra o terminal do editor. Para fazer isso, clique em Terminal -> Novo terminal na barra de menus ou use Ctrl + Shift + C. Isso vai abrir uma janela de terminal na parte de baixo do navegador.

f8457daf0bed059e.jpeg

O terminal ativo atual precisa estar no diretório de trabalho purchasing-concierge-a2a. Vamos usar o Python 3.12 neste codelab e o gerenciador de projetos Python uv para simplificar a necessidade de criar e gerenciar a versão do Python e o ambiente virtual. O pacote uv já está pré-instalado no Cloud Shell.

Execute este comando para instalar as dependências necessárias no ambiente virtual no diretório .venv.

uv sync --frozen

Confira o arquivo pyproject.toml para ver as dependências declaradas para este tutorial, que são a2a-sdk, google-adk, and gradio.

Agora, vamos ativar as APIs necessárias usando o comando mostrado abaixo. Isso pode levar algum tempo.

gcloud services enable aiplatform.googleapis.com \
                       run.googleapis.com \
                       cloudbuild.googleapis.com \
                       cloudresourcemanager.googleapis.com

Após a execução do comando, você vai ver uma mensagem semelhante a esta:

Operation "operations/..." finished successfully.

3. 🚀 Como implantar agentes de vendas remotas do servidor A2A no Cloud Run

Nesta etapa, vamos implantar esses dois agentes de vendedores remotos marcados pela caixa vermelha. O agente de hambúrguer será alimentado pelo framework de agentes da CrewAI, e o agente de pizza será alimentado pelo agente do Langgraph.

e91777eecfbae4f7.png

4. 🚀 Como implantar o agente de vendas de hambúrgueres: servidor A2A

O código-fonte do agente de hambúrguer está no diretório remote_seller_agents/burger_agent.

Todos os arquivos no diretório remote_seller_agents/burger_agent já são suficientes para implantar nosso agente no Cloud Run e torná-lo acessível como um serviço. Execute o comando a seguir para implantá-lo:

gcloud run deploy burger-agent \
    --source remote_seller_agents/burger_agent \
    --port=8080 \
    --allow-unauthenticated \
    --min 1 \
    --region us-central1 \
    --update-env-vars GOOGLE_CLOUD_LOCATION=us-central1 \
    --update-env-vars GOOGLE_CLOUD_PROJECT={your-project-id}

Se você receber uma mensagem informando que um repositório de contêiner será criado para implantação da origem, responda Y. Após a implantação, um registro como este será exibido.

Service [burger-agent] revision [burger-agent-xxxxx-xxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://burger-agent-xxxxxxxxx.us-central1.run.app

A parte xxxx será um identificador exclusivo quando implantarmos o serviço.

Abra uma nova guia do navegador e acesse a rota https://burger-agent-xxxxxxxxx.us-central1.run.app/.well-known/agent.json dos serviços de agente de hambúrguer implantados pelo navegador. Este é o URL para acessar o card do agente do servidor A2A implantado.

Se a implantação for bem-sucedida, você verá a resposta mostrada abaixo no navegador ao acessar o card do agente.

72fdf3f52b5e8313.png

São as informações do card do agente de hambúrguer que precisam estar acessíveis para fins de descoberta.

Observe que o valor url ainda está definido como http://0.0.0.0:8080/ aqui. Esse valor url precisa ser a principal informação para o cliente A2A enviar mensagens de fora, mas não está configurado corretamente.

Precisamos atualizar esse valor para o URL do nosso serviço de agente de hambúrgueres adicionando uma variável de ambiente HOST_OVERRIDE.

Atualizar o valor do URL do Burger Agent no card do agente usando uma variável de ambiente

Para adicionar HOST_OVERRIDE ao serviço de agente de hambúrguer, siga estas etapas:

  1. Pesquise "Cloud Run" na barra de pesquisa na parte de cima do console do Cloud.

1adde569bb345b48.png

  1. Clique no serviço do Cloud Run burger-agent implantado anteriormente.

9091c12526fb7f41.png

  1. Copie o URL do burger-service e clique em Editar e implantar nova revisão.

2701da8b124793b9.png

  1. Em seguida, clique na seção Variáveis e secrets.

31ea00e12134d74d.png

  1. Depois disso, clique em Adicionar variável e defina o HOST_OVERRIDE como o valor do URL do serviço ( aquele com o padrão https://burger-agent-xxxxxxxxx.us-central1.run.app).

52b382da7cf33cd5.png

  1. Por fim, clique no botão implantar para reimplantar o serviço.

11464f4a51ffe54.png

Quando você acessar o card do agente burger-agent novamente no navegador https://burger-agent-xxxxxxxxx.us-central1.run.app/.well-known/agent.json , o valor url já estará configurado corretamente.

2ed7ebcb530f070a.png

5. 🚀 Implantando o agente vendedor de pizza: servidor A2A

Da mesma forma, o código-fonte do agente de pizza está no diretório remote_seller_agents/pizza_agent.

Semelhante à etapa anterior de implantação do agente de hambúrguer, todos os arquivos que existem no diretório remote_seller_agents/pizza_agent já são suficientes para implantar nosso agente no Cloud Run para que ele possa ser acessado como um serviço. Execute o comando a seguir para implantá-lo:

gcloud run deploy pizza-agent \
    --source remote_seller_agents/pizza_agent \
    --port=8080 \
    --allow-unauthenticated \
    --min 1 \
    --region us-central1 \
    --update-env-vars GOOGLE_CLOUD_LOCATION=us-central1 \
    --update-env-vars GOOGLE_CLOUD_PROJECT={your-project-id}

Após a implantação, um registro como este será exibido.

Service [pizza-agent] revision [pizza-agent-xxxxx-xxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://pizza-agent-xxxxxxxxx.us-central1.run.app

A parte xxxx será um identificador exclusivo quando implantarmos o serviço.

O mesmo acontece com o agente de hambúrguer. Quando você tenta acessar a rota https://pizza-agent-xxxxxxxxx.us-central1.run.app/.well-known/agent.json dos serviços implantados do agente de pizza pelo navegador para acessar o card do agente do servidor A2A, o valor url do agente de pizza no card ainda não está configurado corretamente. Também precisamos adicionar HOST_OVERRIDE à variável de ambiente

Atualizar o valor do URL do agente de pizza no card do agente usando uma variável de ambiente

Para adicionar HOST_OVERRIDE ao serviço do agente de pizza, siga estas etapas:

  1. Pesquise "Cloud Run" na barra de pesquisa na parte de cima do console do Cloud.

1adde569bb345b48.png

  1. Clique no serviço do Cloud Run pizza-agent implantado anteriormente.

5743b0aa0555741f.png

  1. Clique em Editar e implantar nova revisão.

d60ba267410183be.png

  1. Copie o URL do pizza-service e clique na seção Variáveis e secrets.

618e9da2f94ed415.png

  1. Depois disso, clique em Adicionar variável e defina o HOST_OVERRIDE como o valor do URL do serviço ( aquele com o padrão https://pizza-agent-xxxxxxxxx.us-central1.run.app).

214a6eb98f877e65.png

  1. Por fim, clique no botão implantar para reimplantar o serviço.

11464f4a51ffe54.png

Agora, quando você acessar o card do agente pizza-agent novamente no navegador https://pizza-agent-xxxxxxxxx.us-central1.run.app/.well-known/agent.json, o valor url já estará configurado corretamente.

c37b26ec80c821b6.png

Neste ponto, já implantamos com sucesso os serviços de hambúrguer e pizza no Cloud Run.

6. 🚀 Como implantar o Purchasing Concierge: cliente A2A no Agent Engine

Nesta etapa, vamos implantar o agente de concierge de compras. É com ele que vamos interagir.

c4a8e7a3d18b1ef.png

O código-fonte do nosso agente de concierge de compras está no diretório purchasing_concierge. A inicialização do agente pode ser inspecionada no script purchasing_concierge/purchasing_agent.py.

Siga estas etapas para implantar :

  1. Primeiro, precisamos criar nosso armazenamento de preparação no Cloud Storage.
gcloud storage buckets create gs://purchasing-concierge-{your-project-id} --location=us-central1
  1. Agora, precisamos preparar a variável .env. Copie o arquivo .env.example para .env.
cp .env.example .env
  1. Agora, abra o arquivo .env e confira o seguinte conteúdo:
GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT={your-project-id}
GOOGLE_CLOUD_LOCATION=us-central1
STAGING_BUCKET=gs://purchasing-concierge-{your-project-id}
PIZZA_SELLER_AGENT_URL={your-pizza-agent-url}
BURGER_SELLER_AGENT_URL={your-burger-agent-url}
AGENT_ENGINE_RESOURCE_NAME={your-agent-engine-resource-name}

Esse agente vai se comunicar com o agente de hambúrguer e pizza. Por isso, precisamos fornecer as credenciais adequadas para os dois. Vamos precisar atualizar PIZZA_SELLER_AGENT_URL e BURGER_SELLER_AGENT_URL com o URL do Cloud Run das etapas anteriores.

Se você se esquecer disso, acesse o console do Cloud Run. Digite "Cloud Run" na barra de pesquisa na parte de cima do console e clique com o botão direito do mouse no ícone do Cloud Run para abrir em uma nova guia.

1adde569bb345b48.png

Você vai ver os serviços de agente de vendedor remoto implantados anteriormente, como mostrado abaixo.

179e55cc095723a8.png

Para conferir o URL público desses serviços, clique em um deles e você será redirecionado para a página "Detalhes do serviço". O URL aparece na parte de cima, ao lado das informações da região.

64c01403a92b1107.png

A variável de ambiente final vai ficar parecida com esta:

GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT={your-project-id}
GOOGLE_CLOUD_LOCATION=us-central1
STAGING_BUCKET=gs://purchasing-concierge-{your-project-id}
PIZZA_SELLER_AGENT_URL=https://pizza-agent-xxxxx.us-central1.run.app
BURGER_SELLER_AGENT_URL=https://burger-agent-xxxxx.us-central1.run.app
AGENT_ENGINE_RESOURCE_NAME={your-agent-engine-resource-name}
  1. Agora, estamos prontos para implantar nosso agente de concierge de compras. Vamos implantá-lo no mecanismo do agente, e o código de implantação está dentro do script deploy_to_agent_engine.py.

Podemos implantá-lo executando o script:

uv run deploy_to_agent_engine.py

Após a implantação, um registro como este será exibido. Ele vai mostrar o nome do recurso do Agent Engine como "projects/xxxx/locations/us-central1/reasoningEngines/yyyy"

AgentEngine created. Resource name: projects/xxxx/locations/us-central1/reasoningEngines/yyyy
To use this AgentEngine in another session:
agent_engine = vertexai.agent_engines.get('projects/xxxx/locations/us-central1/reasoningEngines/yyyy)
Deployed remote app resource: projects/xxxx/locations/us-central1/reasoningEngines/xxxx

E quando inspecionamos no painel do Agent Engine (pesquise "Agent Engine" na barra de pesquisa), a implantação anterior é mostrada.

e80f1c00ec9fbb38.png

Você também pode inspecionar se o nome do recurso do Agent Engine está sendo exibido. Em seguida, podemos usar esse nome para testar.

Depois disso, atualize o AGENT_ENGINE_RESOURCE_NAME no arquivo .env com esse valor. Verifique se você informou o nome correto do recurso do mecanismo do agente. Seu arquivo .env vai ficar assim:

GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT={your-project-id}
GOOGLE_CLOUD_LOCATION=us-central1
STAGING_BUCKET=gs://purchasing-concierge-{your-project-id}
PIZZA_SELLER_AGENT_URL=https://pizza-agent-xxxxx.us-central1.run.app
BURGER_SELLER_AGENT_URL=https://burger-agent-xxxxx.us-central1.run.app
AGENT_ENGINE_RESOURCE_NAME=projects/xxxx/locations/us-central1/reasoningEngines/yyyy

Como testar o agente implantado no Agent Engine

A interação com o Agent Engine pode ser feita usando o comando curl e o SDK. Por exemplo, execute o comando a seguir para tentar interagir com o agente implantado.

Envie esta consulta para verificar se o agente foi implantado corretamente. Execute o script test_agent_engine.sh a seguir:

bash test_agent_engine.sh

Você pode inspecionar o script e ver que tentamos pedir "List available burger menu please" (Liste o menu de hambúrguer disponível, por favor) ao agente.

Se a operação for bem-sucedida, vários eventos de resposta serão transmitidos no console, como este:

{
  "content": {
    "parts": [
      {
        "text": "Here is our burger menu:\n- Classic Cheeseburger: IDR 85K\n- Double Cheeseburger: IDR 110K\n- Spicy Chicken Burger: IDR 80K\n- Spicy Cajun Burger: IDR 85K"
      }
    ],
    "role": "model"
  },
  "usage_metadata": {
    "candidates_token_count": 51,
    "candidates_tokens_details": [
      {
        "modality": "TEXT",
        "token_count": 51
      }
    ],
    "prompt_token_count": 907,
    "prompt_tokens_details": [
      {
        "modality": "TEXT",
        "token_count": 907
      }
    ],
    "total_token_count": 958,
    "traffic_type": "ON_DEMAND"
  },
  "invocation_id": "e-14679918-af68-45f1-b942-cf014368a733",
  "author": "purchasing_agent",
  "actions": {
    "state_delta": {},
    "artifact_delta": {},
    "requested_auth_configs": {}
  },
  "id": "dbe7fc43-b82a-4f3e-82aa-dd97afa8f15b",
  "timestamp": 1754287348.941454
}

Vamos tentar usar a interface na próxima etapa, mas primeiro vamos discutir quais são os componentes principais e o fluxo típico dos clientes A2A.

7. 🚀 Teste de integração e inspeção de payload

Agora vamos inspecionar nosso concierge de compras com a interação do agente remoto usando uma interface da Web. Execute o comando a seguir para implantar um app do Gradio. Para executar esse app, você precisa ter preenchido o arquivo .env corretamente.

uv run purchasing_concierge_ui.py

Se a operação for bem-sucedida, a seguinte saída será exibida:

* Running on local URL:  http://0.0.0.0:8080
* To create a public link, set `share=True` in `launch()`.

Em seguida, Ctrl + clique no URL http://0.0.0.0:8080 no terminal ou clique no botão de visualização da Web para abrir a interface da Web.

b38b428d9e4582bc.png

Tente ter uma conversa assim :

  • Mostre o cardápio de hambúrgueres e pizzas
  • Quero pedir uma pizza de frango com churrasco e um hambúrguer cajun picante

e continue a conversa até concluir o pedido. Analise como está a interação e qual é a chamada e a resposta da ferramenta. A imagem a seguir é um exemplo do resultado da interação.

ff5f752965816b2b.png

6f65155c7a289964.png

b390f4b15f1c5a8c.png

ff44c54b50c36e1a.png

Podemos ver que a comunicação com dois agentes diferentes gera dois comportamentos diferentes, e o A2A lida bem com isso. O agente de vendas de pizza aceita diretamente nossa solicitação de agente de compras, enquanto o agente de hambúrguer precisa da nossa confirmação antes de prosseguir com a solicitação. Depois que confirmamos, o agente pode confiar na confirmação para o agente de hambúrguer.

Agora que concluímos os conceitos básicos de A2A, vamos ver como ele é implementado como uma arquitetura de cliente e servidor.

8. 💡 [Explicação do código] Conceito e implementação do servidor A2A

A inicialização do agente de vendedor remoto pode ser inspecionada no script remote_seller_agents/*/agent.py. Confira o snippet de código dos agentes vendedores.

Agente de hambúrguer

from crewai import Agent, Crew, LLM, Task, Process
from crewai.tools import tool

...

       model = LLM(
            model="vertex_ai/gemini-2.5-flash-lite",  # Use base model name without provider prefix
        )
        burger_agent = Agent(
            role="Burger Seller Agent",
            goal=(
                "Help user to understand what is available on burger menu and price also handle order creation."
            ),
            backstory=("You are an expert and helpful burger seller agent."),
            verbose=False,
            allow_delegation=False,
            tools=[create_burger_order],
            llm=model,
        )

        agent_task = Task(
            description=self.TaskInstruction,
            agent=burger_agent,
            expected_output="Response to the user in friendly and helpful manner",
        )

        crew = Crew(
            tasks=[agent_task],
            agents=[burger_agent],
            verbose=False,
            process=Process.sequential,
        )

        inputs = {"user_prompt": query, "session_id": sessionId}
        response = crew.kickoff(inputs)
        return response

...

Agente de pizza

from langchain_google_vertexai import ChatVertexAI
from langgraph.prebuilt import create_react_agent

...

self.model = ChatVertexAI(
    model="gemini-2.5-flash-lite",
    location=os.getenv("GOOGLE_CLOUD_LOCATION"),
    project=os.getenv("GOOGLE_CLOUD_PROJECT"),
)
self.tools = [create_pizza_order]
self.graph = create_react_agent(
    self.model,
    tools=self.tools,
    checkpointer=memory,
    prompt=self.SYSTEM_INSTRUCTION,
)

...

Como você pode ver, esses dois agentes são criados com frameworks completamente diferentes ( CrewAI e Langgraph) em comparação com o agente cliente ( ADK). Com o A2A, isso não é um problema. Não é necessário que eles compartilhem o código interno para se comunicar. Não importa quais frameworks estão sendo usados, qual linguagem é utilizada ou onde eles são implantados.

Componentes principais do servidor A2A

Agora vamos discutir o conceito e os componentes principais do servidor A2A.

Card do agente

Cada servidor A2A precisa ter um card de agente acessível no recurso /.well-known/agent.json. Isso é para apoiar a fase de descoberta no cliente A2A, que deve fornecer informações e contextos completos sobre como acessar o agente e conhecer todas as suas capacidades. É um pouco semelhante à documentação de API bem documentada usando Swagger ou Postman.

Este é o conteúdo do card do agente de hambúrguer implantado.

{
  "capabilities": {
    "streaming": true
  },
  "defaultInputModes": [
    "text",
    "text/plain"
  ],
  "defaultOutputModes": [
    "text",
    "text/plain"
  ],
  "description": "Helps with creating burger orders",
  "name": "burger_seller_agent",
  "protocolVersion": "0.2.6",
  "skills": [
    {
      "description": "Helps with creating burger orders",
      "examples": [
        "I want to order 2 classic cheeseburgers"
      ],
      "id": "create_burger_order",
      "name": "Burger Order Creation Tool",
      "tags": [
        "burger order creation"
      ]
    }
  ],
  "url": "https://burger-agent-109790610330.us-central1.run.app",
  "version": "1.0.0"
}

Esses cards destacam muitos componentes importantes, como habilidades do agente, recursos de streaming, modalidades compatíveis, versão do protocolo e muito mais.

Todas essas informações podem ser usadas para desenvolver um mecanismo de comunicação adequado para que o cliente A2A possa se comunicar corretamente. A modalidade e o mecanismo de autenticação compatíveis garantem que a comunicação seja estabelecida corretamente, e as informações do agente skills podem ser incorporadas ao comando do sistema do cliente A2A para dar ao agente do cliente contexto sobre as capacidades e habilidades do agente remoto a serem invocadas. Campos mais detalhados para esse card do agente podem ser encontrados nesta documentação.

No nosso código, a implementação do card do agente é estabelecida usando o SDK Python A2A. Confira o snippet remote_seller_agents/burger_agent/main.py abaixo para a implementação.

...

        capabilities = AgentCapabilities(streaming=True)
        skill = AgentSkill(
            id="create_burger_order",
            name="Burger Order Creation Tool",
            description="Helps with creating burger orders",
            tags=["burger order creation"],
            examples=["I want to order 2 classic cheeseburgers"],
        )
        agent_host_url = (
            os.getenv("HOST_OVERRIDE")
            if os.getenv("HOST_OVERRIDE")
            else f"http://{host}:{port}/"
        )
        agent_card = AgentCard(
            name="burger_seller_agent",
            description="Helps with creating burger orders",
            url=agent_host_url,
            version="1.0.0",
            defaultInputModes=BurgerSellerAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=BurgerSellerAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=[skill],
        )

...

Podemos ver vários campos, como:

  1. AgentCapabilities :declaração de outras funções opcionais compatíveis com o serviço de agente,como capacidade de streaming e/ou suporte a notificações push.
  2. AgentSkill :ferramentas ou funções compatíveis com o agente
  3. Input/OutputModes :modalidade de tipo de entrada/saída compatível.
  4. Url :endereço para se comunicar com o agente.

Nessa configuração, fornecemos uma criação dinâmica de URL do host do agente para facilitar a alternância entre testes locais e implantação na nuvem. Por isso, precisamos adicionar a variável HOST_OVERRIDE na etapa anterior.

Fila de tarefas e executor de agente

Um servidor A2A pode processar solicitações de diferentes agentes ou usuários e isolar cada tarefa perfeitamente. Para visualizar melhor os contextos, inspecione a imagem abaixo

b9eb6b4025db4642.jpeg

Assim, cada servidor A2A precisa rastrear as tarefas recebidas e armazenar informações adequadas sobre elas. O SDK A2A oferece módulos para resolver esse desafio no servidor A2A. Primeiro, podemos instanciar a lógica de como queremos processar a solicitação recebida. Ao herdar a classe abstrata AgentExecutor, podemos controlar como queremos gerenciar a execução e o cancelamento de tarefas. Essa implementação de exemplo pode ser inspecionada no módulo remote_seller_agents/burger_agent/agent_executor.py ( caminho semelhante para o caso do vendedor de pizza).

...

class BurgerSellerAgentExecutor(AgentExecutor):
    """Burger Seller AgentExecutor."""

    def __init__(self):
        self.agent = BurgerSellerAgent()

    async def execute(
        self,
        context: RequestContext,
        event_queue: EventQueue,
    ) -> None:
        query = context.get_user_input()
        try:
            result = self.agent.invoke(query, context.context_id)
            print(f"Final Result ===> {result}")

            parts = [Part(root=TextPart(text=str(result)))]
            await event_queue.enqueue_event(
                completed_task(
                    context.task_id,
                    context.context_id,
                    [new_artifact(parts, f"burger_{context.task_id}")],
                    [context.message],
                )
            )
        except Exception as e:
            print("Error invoking agent: %s", e)
            raise ServerError(error=ValueError(f"Error invoking agent: {e}")) from e

    async def cancel(
        self, request: RequestContext, event_queue: EventQueue
    ) -> Task | None:
        raise ServerError(error=UnsupportedOperationError())

...

No código acima, implementamos um esquema de processamento básico em que o agente é invocado diretamente quando a solicitação chega e envia eventos de tarefa concluída após terminar a invocação. No entanto, não implementamos o método de cancelamento aqui porque ele foi considerado uma operação de curta duração.

Depois de criar o executor, podemos usar diretamente o DefaultRequestHandler, InMemoryTaskStore e A2AStarletteApplication integrados para ativar o servidor HTTP. Essa implementação pode ser inspecionada em remote_seller_agents/burger_agent/__main__.py

...

        request_handler = DefaultRequestHandler(
            agent_executor=BurgerSellerAgentExecutor(),
            task_store=InMemoryTaskStore(),
        )
        server = A2AStarletteApplication(
            agent_card=agent_card, http_handler=request_handler
        )

        uvicorn.run(server.build(), host=host, port=port)

...

Este módulo vai fornecer a implementação da rota /.well-known/agent.json para acessar o card do agente e também o endpoint POST para oferecer suporte ao protocolo A2A.

Resumo

Em resumo, até agora, nosso servidor A2A implantado usa o SDK Python, que é compatível com as duas funcionalidades abaixo:

  1. Publicar o card do agente na rota /.well-known/agent.json
  2. Processar solicitação JSON-RPC com filas de tarefas na memória

O ponto de entrada ao iniciar essas funcionalidades pode ser inspecionado no script __main__.py ( em remote_seller_agents/burger_agent ou remote_seller_agents/pizza_agent) .

9. 💡 [Explicação do código] Implantação do Agent Engine

Confira o snippet de código do agente de concierge de compras em purchasing_concierge/purchasing_agent.py::

from google.adk import Agent

...

def create_agent(self) -> Agent:
        return Agent(
            model="gemini-2.5-flash-lite",
            name="purchasing_agent",
            instruction=self.root_instruction,
            before_model_callback=self.before_model_callback,
            before_agent_callback=self.before_agent_callback,
            description=(
                "This purchasing agent orchestrates the decomposition of the user purchase request into"
                " tasks that can be performed by the seller agents."
            ),
            tools=[
                self.send_task,
            ],
        )

...

Esse agente é criado usando o ADK e implantado no Agent Engine.

O Vertex AI Agent Engine é um conjunto de serviços que permite aos desenvolvedores implantar, gerenciar e escalonar agentes de IA em produção. Ele cuida da infraestrutura para escalonar agentes em produção, para que possamos nos concentrar na criação de aplicativos. Leia mais sobre isso neste documento . Antes, era necessário preparar os arquivos necessários para implantar o serviço de agente, como o script do servidor main e o Dockerfile. Agora, é possível implantar o agente diretamente do script Python sem precisar desenvolver seu próprio serviço de back-end usando uma combinação do ADK e do Agent Engine.

Neste tutorial, vamos fazer a implantação usando o script deploy_to_agent_engine.py, cujo conteúdo é mostrado abaixo.

import vertexai
from vertexai.preview import reasoning_engines
from vertexai import agent_engines
from dotenv import load_dotenv
import os
from purchasing_concierge.agent import root_agent

load_dotenv()

PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT")
LOCATION = os.getenv("GOOGLE_CLOUD_LOCATION")
STAGING_BUCKET = os.getenv("STAGING_BUCKET")

vertexai.init(
    project=PROJECT_ID,
    location=LOCATION,
    staging_bucket=STAGING_BUCKET,
)

adk_app = reasoning_engines.AdkApp(
    agent=root_agent,
)

remote_app = agent_engines.create(
    agent_engine=adk_app,
    display_name="purchasing-concierge",
    requirements=[
        "google-cloud-aiplatform[adk,agent_engines]",
        "a2a-sdk==0.2.16",
    ],
    extra_packages=[
        "./purchasing_concierge",
    ],
    env_vars={
        "GOOGLE_GENAI_USE_VERTEXAI": os.environ["GOOGLE_GENAI_USE_VERTEXAI"],
        "PIZZA_SELLER_AGENT_URL": os.environ["PIZZA_SELLER_AGENT_URL"],
        "BURGER_SELLER_AGENT_URL": os.environ["BURGER_SELLER_AGENT_URL"],
    },
)

print(f"Deployed remote app resource: {remote_app.resource_name}")

Estas são as etapas necessárias para implantar nosso agente do ADK no Agent Engine. Primeiro, precisamos criar um objeto AdkApp com base no nosso root_agent do ADK. Em seguida, podemos executar o método agent_engines.create fornecendo o objeto adk_app, especificando os requisitos no campo requirements, especificando o caminho do diretório do agente em extra_packages ( também é possível fornecer outros diretórios e arquivos, se necessário) e fornecendo as variáveis de ambiente necessárias.

10. 💡 [Explicação do código] Conceito e implementação do cliente A2A

aa6c8bc5b5df73f1.jpeg

A imagem acima mostra o fluxo típico de interações A2A:

  1. O cliente vai tentar encontrar um card de agente publicado no URL do agente remoto fornecido na rota /.well-known/agent.json.
  2. Em seguida, quando necessário, ele enviará uma mensagem para esse agente com a mensagem e os parâmetros de metadados necessários ( por exemplo, ID da sessão, contexto histórico etc.). O servidor vai perceber essa mensagem como uma tarefa a ser concluída.
  3. O servidor A2A processa a solicitação. Se ele for compatível com notificações push, também poderá publicar algumas notificações durante o processamento da tarefa. Essa funcionalidade está fora do escopo deste codelab.
  4. Depois de concluído, o servidor A2A envia o artefato de resposta de volta ao cliente.

Alguns dos objetos principais para as interações acima são estes itens (mais detalhes podem ser lidos aqui) :

  • Mensagem:uma troca de comunicação entre um cliente e um agente remoto.
  • Tarefa: a unidade fundamental de trabalho gerenciada pelo A2A, identificada por um ID exclusivo.
  • Artefato:uma saída (por exemplo, um documento, uma imagem, dados estruturados) gerada pelo agente como resultado de uma tarefa, composta de partes.
  • Parte:a menor unidade de conteúdo em uma mensagem ou um artefato. A parte pode ser um texto, uma imagem, um vídeo, um arquivo etc.

Descoberta de cards

Quando o serviço do cliente A2A está sendo ativado, o processo típico é tentar obter as informações do cartão do agente e armazená-las para facilitar o acesso quando necessário. Neste codelab, vamos implementar isso em before_agent_callback. Veja a implementação em purchasing_concierge/purchasing_agent.py no snippet de código abaixo.

...

async def before_agent_callback(self, callback_context: CallbackContext):
        if not self.a2a_client_init_status:
            httpx_client = httpx.AsyncClient(timeout=httpx.Timeout(timeout=30))
            for address in self.remote_agent_addresses:
                card_resolver = A2ACardResolver(
                    base_url=address, httpx_client=httpx_client
                )
                try:
                    card = await card_resolver.get_agent_card()
                    remote_connection = RemoteAgentConnections(
                        agent_card=card, agent_url=card.url
                    )
                    self.remote_agent_connections[card.name] = remote_connection
                    self.cards[card.name] = card
                except httpx.ConnectError:
                    print(f"ERROR: Failed to get agent card from : {address}")
            agent_info = []
            for ra in self.list_remote_agents():
                agent_info.append(json.dumps(ra))
            self.agents = "\n".join(agent_info)

...

Aqui, tentamos acessar todos os cards de agente disponíveis usando o módulo A2ACardResolver do cliente A2A integrado. Em seguida, reunimos a conexão necessária para enviar mensagens ao agente. Depois disso, também precisamos listar todos os agentes disponíveis e suas especificações no comando para que nosso agente saiba que pode se comunicar com eles.

Ferramenta de solicitação e envio de tarefas

Este é o comando e a ferramenta que fornecemos ao nosso agente do ADK aqui

...

def root_instruction(self, context: ReadonlyContext) -> str:
    current_agent = self.check_active_agent(context)
    return f"""You are an expert purchasing delegator that can delegate the user product inquiry and purchase request to the
appropriate seller remote agents.

Execution:
- For actionable tasks, you can use `send_task` to assign tasks to remote agents to perform.
- When the remote agent is repeatedly asking for user confirmation, assume that the remote agent doesn't have access to user's conversation context. 
So improve the task description to include all the necessary information related to that agent
- Never ask user permission when you want to connect with remote agents. If you need to make connection with multiple remote agents, directly
connect with them without asking user permission or asking user preference
- Always show the detailed response information from the seller agent and propagate it properly to the user. 
- If the remote seller is asking for confirmation, rely the confirmation question to the user if the user haven't do so. 
- If the user already confirmed the related order in the past conversation history, you can confirm on behalf of the user
- Do not give irrelevant context to remote seller agent. For example, ordered pizza item is not relevant for the burger seller agent
- Never ask order confirmation to the remote seller agent 

Please rely on tools to address the request, and don't make up the response. If you are not sure, please ask the user for more details.
Focus on the most recent parts of the conversation primarily.

If there is an active agent, send the request to that agent with the update task tool.

Agents:
{self.agents}

Current active seller agent: {current_agent["active_agent"]}
"""

...

async def send_task(self, agent_name: str, task: str, tool_context: ToolContext):
        """Sends a task to remote seller agent

        This will send a message to the remote agent named agent_name.

        Args:
            agent_name: The name of the agent to send the task to.
            task: The comprehensive conversation context summary
                and goal to be achieved regarding user inquiry and purchase request.
            tool_context: The tool context this method runs in.

        Yields:
            A dictionary of JSON data.
        """
        if agent_name not in self.remote_agent_connections:
            raise ValueError(f"Agent {agent_name} not found")
        state = tool_context.state
        state["active_agent"] = agent_name
        client = self.remote_agent_connections[agent_name]
        if not client:
            raise ValueError(f"Client not available for {agent_name}")
        session_id = state["session_id"]
        task: Task
        message_id = ""
        metadata = {}
        if "input_message_metadata" in state:
            metadata.update(**state["input_message_metadata"])
            if "message_id" in state["input_message_metadata"]:
                message_id = state["input_message_metadata"]["message_id"]
        if not message_id:
            message_id = str(uuid.uuid4())

        payload = {
            "message": {
                "role": "user",
                "parts": [
                    {"type": "text", "text": task}
                ],  # Use the 'task' argument here
                "messageId": message_id,
                "contextId": session_id,
            },
        }

        message_request = SendMessageRequest(
            id=message_id, params=MessageSendParams.model_validate(payload)
        )
        send_response: SendMessageResponse = await client.send_message(
            message_request=message_request
        )
        print(
            "send_response",
            send_response.model_dump_json(exclude_none=True, indent=2),
        )

        if not isinstance(send_response.root, SendMessageSuccessResponse):
            print("received non-success response. Aborting get task ")
            return None

        if not isinstance(send_response.root.result, Task):
            print("received non-task response. Aborting get task ")
            return None

        return send_response.root.result

...

Na solicitação, fornecemos ao nosso agente de compras todas as informações disponíveis sobre os agentes remotos, como nome e descrição. Na ferramenta self.send_task, oferecemos um mecanismo para recuperar o cliente adequado, conectar-se ao agente e enviar os metadados necessários usando o objeto SendMessageRequest.

Os protocolos de comunicação

A definição de Task é um domínio de propriedade do servidor A2A. No entanto, da perspectiva do cliente A2A, ele vê isso como uma mensagem enviada ao servidor. Cabe ao servidor definir como as mensagens recebidas do cliente são definidas como qual tarefa e se a conclusão da tarefa precisa da interação do cliente. Leia mais detalhes sobre o ciclo de vida da tarefa nesta documentação. O conceito de nível mais alto pode ser visualizado abaixo:

65b8878a4854fd93.jpeg

9ddfae690d40cbbf.jpeg

Essa troca de mensagem -> tarefa é implementada usando o formato de payload sobre o padrão JSON-RPC, como mostrado no exemplo abaixo do protocolo message/send :

{
  # identifier for this request
  "id": "abc123",
  # version of JSON-RPC protocol
  "jsonrpc": "2.0",
  # method name
  "method": "message/send",
  # parameters/arguments of the method
  "params": {
    "message": "hi, what can you help me with?"
  }  
}

Há vários métodos disponíveis, por exemplo, para oferecer suporte a diferentes tipos de comunicação (síncrona, streaming, assíncrona) ou para configurar notificações sobre o status da tarefa. Um servidor A2A pode ser configurado de maneira flexível para processar esses padrões de definição de tarefas. Leia os detalhes desses métodos neste documento.

11. 🎯 Desafio

Agora, você pode preparar o arquivo necessário e implantar o app do Gradio no Cloud Run por conta própria? É hora de aceitar o desafio!

12. 🧹 Limpeza

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste codelab, siga estas etapas:

  1. No console do Google Cloud, acesse a página Gerenciar recursos.
  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir.
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.
  4. Outra opção é acessar Cloud Run e Agent Engine no console, selecionar o serviço que você acabou de implantar e excluir.