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

1. Introdução

b013ad6b246401eb.png

O protocolo de agente para agente (A2A) foi projetado para padronizar a comunicação entre agentes de IA, principalmente para aqueles implantados em sistemas externos. Antes, esses protocolos eram estabelecidos para ferramentas chamadas de Protocolo de contexto de modelo (MCP), um padrão emergente para conectar LLMs a dados e recursos. A2A tenta complementar o MCP quando está focado em um problema diferente. Enquanto o MCP se concentra em reduzir a complexidade para conectar agentes a 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 vendas 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 esperamos nesta demonstração.

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. Prepare seu projeto do Google Cloud e ative todas as APIs necessárias nele.
  2. Configurar o espaço de trabalho para seu ambiente de programação
  3. Prepare as variáveis de ambiente para os serviços de agente de hambúrguer e pizza e teste localmente
  4. Implantar o agente de hambúrguer e pizza no Cloud Run
  5. Inspecionar os detalhes de como o servidor A2A foi estabelecido
  6. Preparar variáveis de ambiente para o concierge de compras e testar localmente
  7. Implantar o concierge de compras no Agent Engine
  8. Conectar-se ao mecanismo de agente pela interface local
  9. Inspecione os detalhes de como o cliente A2A foi estabelecido e a modelagem de dados dele.
  10. Inspecionar o payload e a interação entre o cliente e o servidor A2A

Visão geral da arquitetura

Vamos implantar a seguinte arquitetura de serviço

9cfc4582f2d8b6f3.jpeg

Vamos 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

  • Conforto ao trabalhar com 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 do 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 faturamento ativado

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

2. Antes de começar

Selecionar projeto ativo no console do Cloud

Este codelab pressupõe que você já tenha um projeto do Google Cloud com o faturamento ativado. Se você ainda não tem uma, siga as instruções abaixo para começar.

  1. No console do Google Cloud, na página de seletor de projetos, selecione ou crie um projeto do Google Cloud.
  2. Confira se o faturamento está ativado para seu projeto do Cloud. Saiba como verificar se o faturamento está ativado em um projeto.

bc8d176ea42fbb7.png

Configurar o projeto do Cloud no terminal do Cloud Shell

  1. Você vai usar o Cloud Shell, um ambiente de linha de comando executado no Google Cloud que vem pré-carregado com bq. Clique em "Ativar o Cloud Shell" na parte de cima do console do Google Cloud. Se for preciso autorizar, clique em Autorizar.

1829c3759227c19b.png

  1. Depois de se conectar ao Cloud Shell, verifique se sua conta já está autenticada e se o projeto está configurado com seu ID do projeto usando o seguinte comando:
gcloud auth list
  1. Execute o comando a seguir no Cloud Shell para confirmar se o comando gcloud sabe sobre seu projeto.
gcloud config list project
  1. Se o projeto não estiver definido, use este comando:
gcloud config set project <YOUR_PROJECT_ID>

Também é possível conferir o ID do PROJECT_ID no console.

4032c45803813f30.jpeg

Clique nele para ver todos os seus projetos e o ID do projeto no lado direito.

8dc17eb4271de6b5.jpeg

  1. Ative as APIs necessárias usando o comando mostrado abaixo. Isso pode levar alguns minutos.
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.

A alternativa ao comando gcloud é pesquisar cada produto no console ou usar este link.

Se alguma API for esquecida, você sempre poderá ativá-la durante a implementação.

Consulte a documentação para ver o uso e os comandos gcloud.

Acessar 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.

  1. Clique no botão "Abrir editor" para abrir um editor do Cloud Shell. Podemos escrever nosso código aqui b16d56e4979ec951.png
  2. Verifique se o projeto do Cloud Code está definido no canto inferior esquerdo (barra de status) do editor do Cloud Shell, conforme destacado na imagem abaixo, e se ele está definido como o projeto ativo do Google Cloud em que o faturamento está ativado. Clique em Autorizar se for solicitado. Se você já segue o comando anterior, o botão também pode apontar diretamente para o projeto ativado em vez do botão de login.

f5003b9c38b43262.png

  1. Em seguida, vamos clonar o diretório de trabalho do modelo para este codelab do GitHub. Execute o seguinte comando: Ele vai criar o diretório de trabalho no diretório purchasing-concierge-a2a.
git clone https://github.com/alphinside/purchasing-concierge-intro-a2a-codelab-starter.git purchasing-concierge-a2a
  1. Depois disso, acesse a seção superior do editor do Cloud Shell e clique em Arquivo->Abrir pasta, encontre o diretório username 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

Configuração do ambiente

A próxima etapa é preparar o ambiente de desenvolvimento. 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.

  1. Se você ainda não abriu o terminal, clique em Terminal -> Novo terminal ou use Ctrl + Shift + C. Isso vai abrir uma janela do terminal na parte de baixo do navegador.

f8457daf0bed059e.jpeg

  1. Agora, vamos inicializar o ambiente virtual do concierge de compras usando uv (já pré-instalado no terminal da nuvem). Execute este comando:
uv sync --frozen

Isso vai criar o diretório .venv e instalar as dependências. Uma rápida olhada no pyproject.toml vai mostrar informações sobre as dependências, assim:

dependencies = [
    "a2a-sdk>=0.2.16",
    "google-adk>=1.8.0",
    "gradio>=5.38.2",
]
  1. Para testar o ambiente virtual, crie um novo arquivo main.py e copie o seguinte código:
def main():
   print("Hello from purchasing-concierge-a2a!")

if __name__ == "__main__":
   main()
  1. Em seguida, execute o comando abaixo.
uv run main.py

Você vai receber uma saída como a mostrada abaixo

Using CPython 3.12
Creating virtual environment at: .venv
Hello from purchasing-concierge-a2a!

Isso mostra que o projeto Python está sendo configurado corretamente.

Agora podemos passar para a próxima etapa, que é configurar e implantar nosso agente de vendedor remoto.

3. Como implantar o agente de vendedor remoto (servidor A2A) no Cloud Run

Nesta etapa, vamos implantar esses dois agentes de vendedores remotos marcados pela caixa vermelha. O agente de hambúrguer vai usar a estrutura de agente CrewAI, e o agente de pizza vai usar o agente Langgraph. Ambos são alimentados pelo modelo Gemini Flash 2.0.

e91777eecfbae4f7.png

Como implantar o agente do Remote Burger

O código-fonte do agente de hambúrguer está no diretório remote_seller_agents/burger_agent. A inicialização do agente pode ser inspecionada no script agent.py. Confira o snippet de código do agente inicializado

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

...

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. Vamos falar sobre isso mais adiante. 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. Isso só acontecia se você nunca tivesse feito uma implantação em um Cloud Run de uma origem antes. 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. Agora, vamos tentar acessar a rota https://burger-agent-xxxxxxxxx.us-central1.run.app/.well-known/agent.json desses 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 https://burger-agent-xxxxxxxxx.us-central1.run.app/.well-known/agent.json :

72fdf3f52b5e8313.png

São as informações do card do agente de hambúrguer que precisam estar acessíveis para fins de descoberta. Vamos falar sobre isso mais adiante. Observe que o valor url ainda está definido como http://0.0.0.0:8080/. Esse valor url precisa ser a principal informação para o cliente A2A enviar mensagens de fora, mas não está configurado corretamente. Para esta demonstração, vamos precisar 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 agente de hambúrguer 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

Agora, 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

Como implantar o agente remoto de pizza

Da mesma forma, o código-fonte do agente de pizza está no diretório remote_seller_agents/pizza_agent. A inicialização do agente pode ser inspecionada no script agent.py. Confira o snippet de código do agente inicializado

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,
)

...

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 dele 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. Agora vamos falar sobre os principais componentes do servidor A2A.

4. 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 funcionalidades dele. É 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 ver 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) .

5. 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_agent.py. Confira o snippet de código do agente inicializado.

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,
            ],
        )

...

Vamos implantar esse agente 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. 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 .env.example para o arquivo .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 encontrar os serviços de agente de vendas remotas 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. Nesta demonstração, vamos fazer a implantação usando o script deploy_to_agent_engine.py, cujo conteúdo é mostrado abaixo.
"""
Copyright 2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

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 e fornecendo as variáveis de ambiente necessárias.

Podemos implantá-lo executando o script:

uv run deploy_to_agent_engine.py

Após a implantação, um registro como este será exibido. Observações: xxxx é o ID do projeto e yyyy é o ID do recurso do mecanismo do agente.

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 mecanismo de agente (pesquise "mecanismo de agente" na barra de pesquisa), a implantação anterior é mostrada.

29738fbf7e5f5ecc.png

Como testar o agente implantado no Agent Engine

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

Você pode enviar esta consulta para verificar se o agente foi implantado corretamente

curl \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
https://us-central1-aiplatform.googleapis.com/v1/projects/{YOUR_PROJECT_ID}/locations/us-central1/reasoningEngines/{YOUR_AGENT_ENGINE_RESOURCE_ID}:streamQuery?alt=sse -d '{
  "class_method": "stream_query",
  "input": {
    "user_id": "user_123",
    "message": "List available burger menu please",
  }
}'

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.

6. Componentes principais 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. Depois, quando necessário, ele vai 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. Confira 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, do ponto de vista do cliente A2A, ele vê isso como uma Mensagem enviada ao servidor. Cabe ao servidor definir como as mensagens recebidas do cliente são consideradas 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.

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.

Primeiro, precisamos atualizar o AGENT_ENGINE_RESOURCE_NAME no .arquivo env. Verifique se você informou o nome correto do recurso do mecanismo do agente. O arquivo .env 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

Depois disso, execute o comando a seguir para implantar um app do Gradio

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 venda 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. 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!

9. Limpar

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. Ou acesse Cloud Run no console, selecione o serviço que você acabou de implantar e exclua.