1. Introdução

Aprenda a criar um mecanismo multimodal multiagente que usa um resumo de tema bruto e organiza a criação de recursos sociais altamente envolventes. Usando o Kit de Desenvolvimento de Agente (ADK, na sigla em inglês) do Google e o protocolo Agent2Agent (A2A, na sigla em inglês), você vai criar microrrobôs autônomos, empacotar regras de marca como uma habilidade de agente reutilizável, implantar um serviço de agente no Cloud Run e classificar os recursos visuais principais gerados para conformidade da marca com as funções de IA do BigQuery.
Atividades deste laboratório
Neste codelab, você vai aprender a:
- Orquestre vários agentes do ADK com um fluxo de trabalho de gráfico determinístico.
- Empacote as diretrizes de marca como uma habilidade do agente que é carregada apenas quando necessário.
- Equipar um agente com uma ferramenta de geração de imagens que salva a saída como um artefato.
- Expor um agente como um serviço A2A e chamá-lo de outro agente como um nó remoto.
- Implante o Visual Director no Cloud Run e chame o fluxo de trabalho pelo A2A.
- Avalie os recursos visuais principais gerados para adequação à marca no BigQuery com funções de IA.
O que é necessário
- Um navegador da Web, como o Chrome.
- Uma conta do Google Cloud e um projeto do Google Cloud com o faturamento ativado.
2. Antes de começar
Criar um projeto do Google Cloud
- No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto na nuvem.
- Verifique se o faturamento está ativado para seu projeto do Cloud. Saiba como verificar se o faturamento está ativado em um projeto.
Iniciar o Cloud Shell
Você vai usar o Google Cloud Shell para baixar o código, executar scripts de configuração e implantar o aplicativo.
- Abra o Google Cloud Shell no navegador ou clique em Ativar o Cloud Shell na parte de cima do console do Google Cloud:
Clique em Continuar se receber outras notificações.Aguarde até que o terminal e o editor estejam totalmente carregados. Isso pode levar um ou dois minutos.https://shell.cloud.google.com/ - Configure o ID do projeto e a região do Google Cloud (por exemplo,
us-central1) no seu ambiente colando este no terminal (substituaepelos seus valores):export PROJECT_ID="<YOUR_PROJECT_ID>" export REGION="<YOUR_REGION>" gcloud config set project $PROJECT_ID
Você vai receber uma mensagem semelhante a esta:Your active configuration is: [cloudshell-####] Updated property [core/project] - Ative as APIs do Google Cloud necessárias para seu projeto:
gcloud services enable \ compute.googleapis.com \ cloudresourcemanager.googleapis.com \ aiplatform.googleapis.com \ storage.googleapis.com \ storage-component.googleapis.com \ bigqueryconnection.googleapis.com \ bigquery.googleapis.com \ run.googleapis.com \ secretmanager.googleapis.com \ cloudbuild.googleapis.com \ artifactregistry.googleapis.com \ iap.googleapis.com \ iam.googleapis.com
Clone o repositório
Temos um modelo de código para você começar o codelab. Ele é relativamente pequeno, mas o repositório do GitHub em que ele está é bem grande. Para acelerar o clone e economizar espaço em disco, normalmente usamos git clone com a opção --filter=blob:none, git sparse-checkout e mais alguns comandos.
Isso parece muito complexo, mas o Antigravity pode fazer tudo isso por nós.
Primeiro, vamos configurar a CLI do Antigravity para que ela possa resolver nosso problema e ser iniciada.
No Cloud Shell, execute este comando:
source <(curl -fsSL https://github.com/GoogleCloudPlatform/devrel-demos/raw/refs/heads/vladkol/cloud-shell-configure-agy/codelabs/labs-scripts/bwg-multi-agent-pitch-generator/cloud_shell_configure_agy.sh)
O comando configura e inicia a CLI do Antigravity no mesmo terminal. Ao sair da CLI do Antigravity, você volta ao prompt do Cloud Shell.

Precisamos apenas da pasta codelabs/bwg-multi-agent-pitch-generator no repositório. Vamos pedir ao Antigravity para baixar apenas essa pasta e armazená-la localmente em ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/.
Dê esta tarefa à CLI do Antigravity:
Download files from under `https://github.com/GoogleCloudPlatform/devrel-demos/tree/main/codelabs/bwg-multi-agent-pitch-generator`
to folder `~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/`.
If `devrel-demos` folder already exists, rename it with a `.backup` suffix.

O Antigravity vai executar a tarefa. Ele vai pedir permissões para executar alguns comandos. Depois que o código for baixado, saia da CLI do Antigravity executando o comando /exit:
/exit

Abra o espaço de trabalho do repositório
Agora, vamos abrir o espaço de trabalho de código no Cloud Shell. Execute este comando no terminal do Cloud Shell:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
cloudshell workspace .
Executar o script de configuração
Para facilitar essa experiência, execute o script de configuração do repositório. Ele configura variáveis de ambiente, instala o agents-cli, cria o bucket do Cloud Storage para artefatos, cria a conexão a recursos do Cloud do BigQuery e concede as permissões necessárias do IAM à conta de serviço com nova tentativa automática.
O script de configuração também chama setenv.sh, um pequeno auxiliar que deriva as variáveis necessárias para os dois agentes (GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_REGION, GOOGLE_CLOUD_LOCATION e GOOGLE_GENAI_USE_VERTEXAI) dos PROJECT_ID e REGION exportados acima. Ele também salva esses dois valores em um arquivo .env no seu diretório inicial para que você possa recuperá-los mais tarde.
- No Cloud Shell, torne o script de configuração executável e execute-o:
chmod +x codelab-setup.sh setenv.sh ./codelab-setup.sh - Esse processo leva cerca de um minuto. Você vai ver esta mensagem indicando sucesso: "🎉 🦄 Script execution complete."

- O script de configuração foi executado em um shell próprio, então as variáveis não chegaram ao seu terminal. Carregue-os na sua sessão:
Seu projeto, região e local serão repetidos.source ./setenv.sh
3. Orquestrar agentes com um fluxo de trabalho de gráfico
Nesta tarefa, você vai criar o projeto do gerador de sugestões e um fluxo de trabalho de gráfico que executa dois agentes e combina a saída deles.
Um pitch precisa de um conceito, ou seja, a ideia e os recursos que vendem esse conceito. Neste caso de uso, vamos criar agentes com responsabilidades diferentes no escopo do trabalho, mostrando como desacoplar tarefas complexas e delegá-las a agentes diferentes com responsabilidades distintas, facilitando o gerenciamento.
Dividir esse tipo de trabalho entre especialistas exige algo para coordenar quando cada agente é executado. Você pode deixar o agente decidir quando chamar cada subagente ou aplicar uma abordagem mais determinista. Neste codelab, você vai usar uma abordagem determinista com um fluxo de trabalho de gráfico.
Estruturar o primeiro projeto de agente
- No Cloud Shell, crie o projeto colando este comando no terminal:
Isso grava um projeto completo e executável: um agente, umcd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator agents-cli create pitch-generator \ --agent adk \ --agent-directory pitch_generator \ --deployment-target cloud_run \ --session-type in_memory \ --cicd-runner skip \ --region <YOUR_REGION> \ --auto-approveDockerfile, testes, Terraform para mais tarde e um arquivo.envcom o modelo.env.example. Duas partes são importantes para este codelab:pitch_generator/agent.pyé o agente e o arquivo que você reescreve.pitch_generator/é o nome do pacote, definido por--agent-directory.
setenv.sh. Portanto, não é necessário configurar nada aqui. - Instale as dependências:
Observação:o scaffolding também conecta o projeto para disponibilizar o A2A, o protocolo usado mais tarde neste codelab. Por enquanto, não mexa nesses arquivos.cd pitch-generator agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
Entender o gráfico
O ADK v2.x é compatível com fluxos de trabalho de gráficos. Um gráfico é criado com nós, que são as unidades de trabalho, e arestas, que os conectam. Um nó geralmente é um agente, mas uma função Python simples também funciona. Uma aresta indica que, quando um nó termina, outro é executado em seguida.
As arestas são escritas como uma lista, e o ADK calcula a ordem:
edges=[
("START", creative_director), ## the graph begins here
(creative_director, copywriter), ## then this
]
O aninhamento dá um significado à borda:
Edge | Significado |
| Uma cadeia: a, depois b, depois c, um após o outro |
| Uma distribuição de dados. a, depois b e c ao mesmo tempo |
| Um fan-in. c aguarda a e b |
Para um fan-in, use um JoinNode, que aguarda todos os predecessores e transmite as saídas deles juntos.
Nesta tarefa, vamos criar o seguinte fluxo de trabalho:

Criar o pipeline
- Cole o seguinte no terminal para abrir o arquivo do agente:
Ocd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator cloudshell edit pitch_generator/agent.pyagent.pyestruturado vai aparecer no editor do Cloud Shell.💡 Dica: a qualquer momento, selecione o código e use a lâmpada para pedir que o Gemini explique. Você também pode perguntar à CLI do Antigravity.
- Substitua todo o arquivo em
pitch_generator/agent.pyno editor pelo seguinte:import mimetypes import os from dotenv import load_dotenv from google.adk.agents import Agent from google.adk.agents.context import Context from google.adk.agents.remote_a2a_agent import ( AGENT_CARD_WELL_KNOWN_PATH, RemoteA2aAgent, ) from google.adk.apps import App from google.adk.events.event import Event from google.adk.models import Gemini from google.adk.workflow import JoinNode, Workflow from google.genai import types load_dotenv() MODEL = "gemini-3.8-flash" def _model() -> Gemini: return Gemini( model=MODEL, client_kwargs={"location": "global"}, retry_options=types.HttpRetryOptions(attempts=3), ) """ ⬇️ Configure visual-director RemoteA2aAgent agent below this comment """ creative_director = Agent( name="creative_director", model=_model(), description="Turns a raw idea into a campaign concept.", instruction=( "You are the Creative Director. Turn the idea you are given into ONE " "punchy campaign concept line with the rationale explanation behind it." ), output_key="creative_director", ) copywriter = Agent( name="copywriter", model=Gemini(model=MODEL, retry_options=types.HttpRetryOptions(attempts=3)), description="Writes social copy for a campaign concept.", instruction=( "You are the Copywriter. Write ONE short social caption for the " "campaign concept you are given. Under 25 words." ), ) """ Waits for every node wired into it, then hands their outputs on together.""" assemble = JoinNode(name="assemble") def package(node_input: dict): """Render the joined branches as the finished pitch. """ pitch = "\n\n".join( [ f"CONCEPT\n{node_input['creative_director']}", f"COPY\n{node_input['copywriter']}", ] ) """ The first event is what the user sees. The second is the node's output, which any downstream node would receive.""" yield Event(content=types.Content(role="model", parts=[types.Part(text=pitch)])) yield Event(output=pitch) """ we put creative_director before `assemble` so that we can gather the `Concept` output at assemble""" root_agent = Workflow( name="pitch_generator", edges=[ ("START", creative_director), (creative_director, copywriter), ((creative_director, copywriter), assemble), (assemble, package), ], ) app = App( root_agent=root_agent, name="pitch_generator", ) - O
agents-cliinclui uma interface de desenvolvimento da Web integrada chamadaplaygroundpara inspeção, semelhante ao ADK. Para iniciar o playground: Você vai receber uma pergunta sobre se quer ativar a telemetria. Digiteagents-cli playground --port 8080ne pressione Enter. - Clique em Visualização na Web, o ícone de olho no canto superior direito do terminal, e escolha Visualizar na porta 8080.

- Você vai ver a seguinte interface para conversar com o agente. Tente enviar o comando "Skates voadores para gatos". Você vai ver uma resposta como a mostrada abaixo, em que também é possível inspecionar o gráfico completo do fluxo de trabalho.
Inspecione como essa resposta foi produzida. O playground mostra cada etapa que o gráfico seguiu, em ordem: creative_director,copywriter,assembleepackage. Essa sequência é o gráfico que você conectou, e assim você pode confirmar rapidamente o gráfico que acabou de criar. - Volte ao terminal e interrompa o processo
agents-cli playgroundem execução pressionando Ctrl+C duas vezes.
4. Atribuir uma habilidade a um agente
Nesta tarefa, você vai criar um segundo agente e dar a ele um conjunto de diretrizes de marca empacotadas como uma habilidade.
O pipeline grava palavras. Uma imagem precisa de um estilo próprio: uma paleta, uma maneira de iluminar as coisas, composições que você usa e outras que nunca usa. Essa é uma página de regras, e uma página de regras precisa de um mecanismo melhor para ser gerenciada. Uma maneira de fazer isso é empacotar como uma "habilidade do agente".
Uma habilidade é uma pasta que contém um arquivo SKILL.md. A estrutura do diretório de habilidades é semelhante ao exemplo a seguir (usando visual_director, o próximo agente que você vai criar):
visual_director/
...
agent.py
skills/
brand-guidelines/
SKILL.md ## required: frontmatter + instructions
references/ ## optional: longer supporting documents
assets/ ## optional: templates, images, data
scripts/ ## optional: executable helpers
...
O ponto de entrada da habilidade do agente é o arquivo SKILL.md, que inclui uma seção de frontmatter YAML como a mostrada abaixo:
---
name: brand-guidelines
description: The house brand rules for a key visual. Load this before writing art direction, so the visual looks like it came from this brand.
---
## Brand guidelines
Deep indigo and slate as the ground. One warm accent, amber or terracotta...
O frontmatter é o rótulo. É tudo o que o agente vê de início, por isso a descrição precisa dizer quando vale a pena carregar a habilidade. O corpo é o conteúdo, e ele fica fora do contexto até que o agente chame load_skill.
Essa divisão é o ponto principal. O comando permanece curto, o agente extrai as regras apenas quando precisa delas, e mudar o estilo significa editar um arquivo em vez de procurar em uma string de instrução.
Vamos começar a criar o segundo agente, visual-director, usando as habilidades do agente, conforme mostrado no diagrama abaixo:

Estruturar o diretor visual
Você vai criar um agente especializado para lidar com o componente visual, visual-director. É um projeto separado porque mais tarde ele se torna um serviço próprio para demonstrar como vários agentes se comunicam usando o protocolo A2A. Você vai terminar com dois projetos lado a lado:
bwg-multi-agent-pitch-generator/
├── pitch-generator/ ## built in the last task
│ ├── pitch_generator/
│ │ ├── agent.py ## the graph you wrote
│ │ ├── app_utils/ ## generated, A2A wiring
│ │ └── fast_api_app.py ## generated, serves the agent
│ └── pyproject.toml
└── visual-director/ ## created below
└── ... ## the same shape again
- Crie o projeto e instale as dependências dele colando este no terminal:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator agents-cli create visual-director \ --agent adk \ --agent-directory visual_director \ --deployment-target cloud_run \ --session-type in_memory \ --cicd-runner skip \ --region <YOUR_REGION> \ --auto-approve cd visual-director agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
Escrever as diretrizes da marca
- Crie a pasta de habilidade e abra o arquivo:
mkdir -p visual_director/skills/brand-guidelines cloudshell edit visual_director/skills/brand-guidelines/SKILL.md - Cole o conteúdo a seguir no arquivo
SKILL.mdaberto no Editor e salve-o:
--- name: brand-guidelines description: The house brand rules for a key visual — palette, light, composition, subject treatment, and what never appears. Load this before writing art direction or rendering an image, so the visual looks like it came from this brand rather than from a generic image model. --- ## Brand guidelines House style for campaign key visuals. Every rule here beats a preference in the brief. If the brief asks for something forbidden below, follow the brief for the subject and the guidelines for the treatment. ## Palette Deep indigo and slate as the ground. One warm accent — amber or terracotta — carrying the eye to the subject. Nothing neon, nothing fluorescent. At most three colors with any weight. A fourth reads as clutter. ## Light One dominant source, low and raking. Long shadows are wanted. Golden hour or a single hard studio key, never flat overhead light and never a ring light. Rim light is allowed on the subject only, and only to separate it from the ground. ## Composition Subject off-center, roughly a third in. Generous negative space on the opposite side — copy lands there later, so leave it genuinely empty. Eye level or slightly below. No top-down flat lays. No fisheye, no Dutch tilt. ## Subject One subject. If the idea implies a crowd, pick the one that carries it. Photographic realism with a shallow depth of field. Not illustration, not 3D render, not clay, not pixel art. ## Never - Text, logos, watermarks, or signage anywhere in the frame - Collage or split-screen layouts - Chrome, lens flare, bokeh sparkle - Cool-toned neon cyan or magenta ## Rendering the prompt When you turn art direction into an image prompt, state the palette, the light, and the composition explicitly. An image model will not infer them from the subject alone. Put the "Never" items in as plain description of what the frame does contain, not as negatives — image models handle a positive description far better than a list of prohibitions.
Atualizar o visual_director/agent.py
Atualize o arquivo agent.py e configure-o para que ele tenha a habilidade definida anteriormente.
- Abra o arquivo do agente:
cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py - Substitua todo o conteúdo do arquivo
visual_director/agent.pypelo seguinte código: O agenteimport mimetypes from pathlib import Path from google import genai from google.adk.agents import Agent from google.adk.apps import App from google.adk.models import Gemini from google.adk.skills import load_skill_from_dir from google.adk.tools import ToolContext from google.adk.tools.skill_toolset import SkillToolset from google.genai import types MODEL = "gemini-3.8-flash" ## Configure the `brand-guidelines` skill for the agent BRAND_SKILL = load_skill_from_dir(Path(__file__).parent / "skills" / "brand-guidelines") ## Add definition of `generate_key_visual` tool below this comment root_agent = Agent( name="visual_director", model=Gemini( model=MODEL, client_kwargs={"location": "global"}, retry_options=types.HttpRetryOptions(attempts=3), ), description="Turns a campaign concept into art direction and a key visual.", instruction="""You are the Visual Director on a campaign pitch team. Call `load_skill` for `brand-guidelines` before you write anything. The house style is not optional and it is not in this prompt. You are given a campaign concept. Then, in order: 1. Write art direction for ONE key visual that sells it, obeying the brand guidelines: subject, composition, lighting, color, mood. Three or four sentences. 2. Call `generate_key_visual` with exactly that art direction. Your final reply is the art direction itself and nothing else. No preamble, no alternatives, no questions back. Do not mention the skill, the tool, or the image file — the image travels on its own.""", tools=[ SkillToolset(skills=[BRAND_SKILL]), ## Supply the `generate_key_visual` tool below this comment ], ) app = App( root_agent=root_agent, name="visual_director", )visual-directoragora está configurado corretamente com uma habilidade de agente. Antes de testar, você vai equipar esse agente com uma ferramenta para gerar recursos de imagem como uma das principais funcionalidades.
5. Equipar um agente com uma ferramenta de geração de imagens
Nesta tarefa, você vai dar ao diretor visual uma ferramenta que renderiza a direção de arte como uma imagem e a salva como um artefato. Um artefato é um conjunto nomeado e com controle de versões de dados binários ou conteúdo de arquivo que um agente de IA cria, modifica ou usa durante uma tarefa.

Implementar e adicionar a ferramenta ao agente
- Abra o arquivo do agente:
cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py - Copie a seguinte ferramenta
generate_key_visualantes da definiçãoroot_agent:async def generate_key_visual(art_direction: str, tool_context: ToolContext) -> dict: """Render the art direction as the campaign's key visual. Args: art_direction: The art direction to render, in full. Returns: The saved artifact's filename, version, size and media type. """ IMAGE_MODEL = "gemini-3.1-flash-lite-image" response = await genai.Client(location="global").aio.models.generate_content( model=IMAGE_MODEL, contents=art_direction ) ## The image arrives as inline bytes on one of the response parts. image = next( ( part.inline_data for part in response.candidates[0].content.parts or [] if part.inline_data ), None, ) if image is None: raise ValueError(f"{IMAGE_MODEL} returned no image for: {art_direction[:120]}") filename = "key_visual" + ( mimetypes.guess_extension(image.mime_type or "") or ".bin" ) version = await tool_context.save_artifact(filename, types.Part(inline_data=image)) ## Return the artifact filename and version return { "filename": filename, "version": version, "bytes": len(image.data), "mime_type": image.mime_type, } - Adicione
generate_key_visualabaixo deSkillToolsetpara equipar o agente com a ferramenta:tools=[ SkillToolset(skills=[BRAND_SKILL]), ## Supply the `generate_key_visual` tool below this comment generate_key_visual, ], - O
agents-cliinclui uma interface de desenvolvimento da Web integrada chamadaplaygroundpara inspeção, semelhante ao ADK. Para iniciar o playground:agents-cli playground --port 8080 - Clique em Visualização na Web, o ícone de olho no canto superior direito do terminal, e escolha Visualizar na porta 8080.

- Você vai ver a seguinte interface para conversar com o agente. Tente enviar o comando "Nove vidas. Gravidade zero."
Você pode inspecionar nas interações como o agente visual_directorexecutaload_skill("brand-guidelines")para ler a habilidade de diretrizes da marca e, em seguida, executagenerate_key_visualpara começar a gerar a imagem. A imagem resultante é armazenada como um artefato, que também é exibido na interface e pode ser facilmente inspecionado. - Volte ao terminal e interrompa o processo
agents-cli playgroundem execução pressionando Ctrl+C duas vezes.
6. Arquitetura multiagente com o protocolo A2A
Nesta tarefa, você vai expor o diretor visual como um serviço que outros agentes podem chamar e configurá-lo para responder com a imagem gerada.
O Agent2Agent (A2A) é um protocolo aberto para um agente chamar outro em uma rede. A peça central é o card do agente, um documento JSON que o agente publica sobre si mesmo em um URL conhecido, informando nome, descrição e recursos. Um usuário busca o card e sabe como falar com o agente por trás dele, sem código-fonte, dependências ou linguagem compartilhados.

O agents-cli já faz isso por padrão. O scaffold gerou visual_director/app_utils/a2a.py, que cria o card do seu agente e monta as rotas, e visual_director/fast_api_app.py, que as disponibiliza. Basta uma pequena modificação para que ele também envie os artefatos de volta ao cliente.
A arquitetura vai ficar assim: O agente ainda não está integrado ao nosso fluxo de trabalho inicial:

O scaffold do servidor A2A agents-cli
O código e o módulo a seguir já são fornecidos quando inicializamos o agente visual-director usando agents-cli. Abra esses arquivos colando os seguintes comandos no terminal:
cloudshell edit visual_director/fast_api_app.py
cloudshell edit visual_director/app_utils/a2a.py
O visual_director/fast_api_app.py inicia o agente e monta as rotas A2A em um caminho criado com base no nome do app:
await attach_a2a_routes(
app,
agent=root_agent,
runner=runner,
rpc_path=f"/a2a/{adk_app.name}", ## becomes /a2a/visual_director
)
O visual_director/app_utils/a2a.py faz o resto em três movimentos.
Primeiro, ele cria o card do agente com base no próprio agente. O nome, a descrição e cada ferramenta se tornam entradas que um caller pode ler:
agent_card = await AgentCardBuilder(
agent=agent,
rpc_url=f"{resolved_app_url}{rpc_path}",
).build()
Em segundo lugar, ele coloca um executor entre o protocolo e seu agente. As solicitações A2A recebidas são traduzidas em execuções do ADK, e os resultados são traduzidos de volta:
request_handler = DefaultRequestHandler(
agent_executor=A2aAgentExecutor(runner=runner, force_new_version=True),
task_store=task_store,
agent_card=agent_card,
)
Em terceiro lugar, ele monta duas rotas: uma que veicula o cartão e outra que aceita chamadas.
Trajeto | Finalidade |
| O card. O usuário lê isso primeiro para saber o que o agente pode fazer. |
| O endpoint JSON-RPC. Para onde as solicitações reais vão |
Permitir que artefatos cruzem o limite
Por padrão, uma resposta de A2A tem texto. A imagem é um artefato, e um artefato permanece no repositório do agente de exibição, a menos que você o configure para ser incluído.
- Abra o arquivo A2A gerado:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director cloudshell edit visual_director/app_utils/a2a.py - Encontre a importação
AgentCardBuildere adicione as importaçõesA2aAgentExecutorConfigeinclude_artifacts_in_a2a_event_interceptorabaixo dela:## ... ## from google.adk.a2a.utils.agent_card_builder import AgentCardBuilder ## Add the following two below `AgentCardBuilder` import from google.adk.a2a.executor.config import A2aAgentExecutorConfig from google.adk.a2a.executor.interceptors.include_artifacts_in_a2a_event import ( include_artifacts_in_a2a_event_interceptor, ) - Encontre a linha
agent_executor=A2aAgentExecutor(runner=runner),e substitua pelo seguinte (adicionando oA2aAgentExecutorConfig):agent_executor=A2aAgentExecutor( runner=runner, force_new_version=True, config=A2aAgentExecutorConfig( execute_interceptors=[include_artifacts_in_a2a_event_interceptor] ), ),
Inicie o serviço e leia o card A2A publicado
- Inicie o servidor em segundo plano. É necessário declarar
APP_URL, já que esse é o endereço que o cartão anuncia como próprio. Cole isso no terminal:APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 & - Aguarde alguns segundos para que ele seja iniciado e confirme se está veiculando:
Você vai ver:sleep 10 tail -2 /tmp/visual-director.logINFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit) - Leia o card que o agente publica:
O card lista o nome, a descrição, o URL e as habilidades do agente, incluindocurl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.toolgenerate_key_visual. Ele é gerado diretamente da definição do agente no código. Agora você pode incorporar o agente{ "name": "visual_director", "description": "Turns a campaign concept into art direction and a key visual.", "skills": [ { "id": "visual_director", "name": "model", "tags": [ "llm" ] }, { "id": "visual_director-generate_key_visual", "name": "generate_key_visual", "description": "Render the art direction as the campaign's key visual.\n\nArgs:\n art_direction: The art direction to render, in full.\n\nReturns:\n The saved artifact's filename, version, size and media type.", "tags": [ "llm", "tools" ] } ], "preferredTransport": "JSONRPC", "protocolVersion": "0.3", "url": "http://localhost:8801/a2a/visual_director" }visual-directorao fluxo de trabalho principalpitch-generator.
7. Incorporar o agente remoto A2A no fluxo de trabalho
Nesta tarefa, você vai adicionar o agente remoto ao gráfico como uma terceira ramificação.
Um agente remoto é envolvido em RemoteA2aAgent e colocado no gráfico como qualquer outro nó. As bordas mantêm o mesmo formato, e o coordenador não sabe quais dos nós são locais.
Aponte o coordenador para o serviço e atualize o fluxo de trabalho do gráfico
- Abra o arquivo do agente:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator cloudshell edit pitch_generator/agent.py - Adicione estas importações na parte de cima do arquivo, junto com as atuais:
import asyncio import subprocess import google.auth.transport.requests import google.oauth2.id_token import httpx from google.adk.a2a.converters.part_converter import convert_genai_part_to_a2a_part - Adicione a seguinte definição de
RemoteA2aAgentacima da declaraçãocreative_director:VISUAL_DIRECTOR_URL = os.getenv("VISUAL_DIRECTOR_URL", "http://localhost:8801") ## For deployment on Cloud Run def _cloud_run_client() -> httpx.AsyncClient | None: """An httpx client that signs each request with a Cloud Run ID token. Cloud Run deploys private, so both the agent-card fetch and the A2A calls need one. """ if not VISUAL_DIRECTOR_URL.startswith("https://"): return None async def sign(request: httpx.Request) -> None: token = None try: token = await asyncio.to_thread( google.oauth2.id_token.fetch_id_token, google.auth.transport.requests.Request(), VISUAL_DIRECTOR_URL, ) except Exception: token = subprocess.check_output( ["gcloud", "auth", "print-identity-token", "-q"], stderr=subprocess.PIPE ).decode().strip() if not token: raise Exception("Failed to fetch ID token") request.headers["Authorization"] = f"Bearer {token}" return httpx.AsyncClient(event_hooks={"request": [sign]}, timeout=600) ## Keep the approval's tool traffic out of the A2A request def _pitch_parts_only(part: types.Part): """Sends the Visual Director text and media only. RemoteA2aAgent replays the session on every call, and the Visual Director runs downstream of the human approval, so the approval's function_response would travel alongside the concept text. An ADK server reads a function response as "resume the paused invocation" and rejects a message that also carries text, so drop the tool parts here. """ if part.function_call or part.function_response: return None return convert_genai_part_to_a2a_part(part) visual_director = RemoteA2aAgent( name="visual_director", description="Turns a campaign concept into art direction for one key visual.", agent_card=( f"{VISUAL_DIRECTOR_URL}/a2a/visual_director{AGENT_CARD_WELL_KNOWN_PATH}" ), httpx_client=_cloud_run_client(), genai_part_converter=_pitch_parts_only, ) - Substitua ou substitua a função Python do nó
packagepela seguinte definição:async def package(ctx: Context, node_input: dict): """Render the joined branches as the finished pitch.""" empty = [name for name, value in node_input.items() if not value] if empty: raise ValueError(f"nothing reached the join from: {', '.join(empty)}") def _key_visual(ctx: Context) -> types.Blob | None: """The image the Visual Director sent back, or None if it sent none.""" for event in reversed(ctx.session.events): if event.author != "visual_director" or not event.content: continue for part in event.content.parts or []: if part.inline_data and part.inline_data.data: return part.inline_data return None image = _key_visual(ctx) if image is None: raise ValueError("the Visual Director returned art direction but no image") suffix = mimetypes.guess_extension(image.mime_type or "") or ".bin" filename = f"key_visual{suffix}" await ctx.save_artifact(filename, types.Part(inline_data=image)) pitch = "\n\n".join( [ f"CONCEPT\n{node_input['creative_director']}", f"COPY\n{node_input['copywriter']}", f"ART DIRECTION\n{node_input['visual_director']}", f"KEY VISUAL\n{filename}, {len(image.data)} bytes, {image.mime_type}", ] ) yield Event( content=types.Content( role="model", parts=[types.Part(text=pitch), types.Part(inline_data=image)], ) ) yield Event(output=pitch) - Adicione um controle de aprovação humana antes de prosseguir com a geração downstream. Em
pitch_generator/agent.py, defina os nós de aprovação acima deroot_agent:@node(rerun_on_resume=False) async def approve_concept(ctx: Context): yield RequestInput( message="Please approve the campaign concept (yes/no).", response_schema=str ) @node(rerun_on_resume=True) async def user_approval(ctx: Context): user_response = await ctx.run_node(approve_concept) if str(user_response).lower() in ("yes", "y"): approved_concept = f"## Approved Concept\n\n{ctx.session.state['creative_director']}" yield Event( content=types.Content( role="model", parts=[types.Part(text=approved_concept)] ) ) else: raise ValueError("User rejected the concept") - Por fim, atualize as importações na parte de cima para incluir
RequestInputenodedegoogle.adk.eventsegoogle.adk.workflow: e modifique o fluxo de trabalhofrom google.adk.events import Event, RequestInput from google.adk.workflow import JoinNode, Workflow, noderoot_agentedgespara rotear poruser_approval: Alguns detalhes importantes:edges=[ ("START", creative_director), (creative_director, user_approval, (copywriter, visual_director)), ((creative_director, copywriter, visual_director), assemble), (assemble, package), ],- Aprovação human-in-the-loop (HITL):
approve_conceptgera umRequestInput. Quando o fluxo de trabalho atinge esse nó, a execução é pausada e emite uma interrupção para o autor da chamada. As ramificações downstream (copywriterevisual_director) não serão executadas até que a aprovação do usuário seja confirmada. visual_directoraparece nas bordas exatamente comocopywriter, embora um esteja a uma chamada de função de distância e o outro esteja em uma rede.- A imagem retornada é lida dos eventos da sessão, e não de
node_input, porque atualmente apenas o texto é promovido para a saída de um nó. A imagem cruzou o limite pelo interceptor A2A, mas chegou anexada aos eventos do agente remoto. Portanto, a função auxiliar_key_visuala recupera de lá.
- Aprovação human-in-the-loop (HITL):
Testar a integração
Agora você vai testar se o Visual Director (executado em segundo plano) e o Pitch Generator podem se comunicar pelo protocolo A2A.
- Confirme se o Diretor visual ainda está em execução em segundo plano:
Você vai receber um resultadocurl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.jsonvisual director: 200aqui.Se você receber qualquer outro status, execute o seguinte comando para reiniciar:fuser -k 8801/tcp 2>/dev/null || true cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 & cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator sleep 10 curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json - Inicie a prévia:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator agents-cli playground --port 8080 - Clique em Visualização na Web, o ícone de olho no canto superior direito do terminal, e escolha Visualizar na porta 8080.

- A interface para conversar com o agente vai aparecer. Envie o comando "Skates voadores para gatos".

Você vai ver a interação completa em que o fluxo de trabalho começa e cada agente é executado. Depois que o creative_directorcria o conceito, o fluxo de trabalho é pausado no portão de aprovação e pede que você confirme: "Aprove o conceito da campanha (sim/não)." Insira "yes" no campo de entrada para continuar. Depois da aprovação, o gráfico se abre: é possível ver quando o agentevisual-directorcomeça a carregar a habilidade e, no meio da geração de imagens, o agentecopywritertermina de gerar a legenda para redes sociais. Isso mostra que os dois agentes são executados em paralelo sem esperar um pelo outro. Depois que ambos terminarem, o nóassemblevai juntar os resultados com o conceito aprovado gerado pelocreative_director, finalizando a entrega do conteúdo da sugestão na funçãopackage.
8. Persistir os recursos visuais principais no Cloud Storage
Até agora, todos os recursos visuais principais estavam na memória. No momento em que o processo para, a imagem desaparece, o que é bom para um playground e inútil para uma campanha. Nesta tarefa, você vai atribuir um bucket ao fluxo de trabalho para gravação e criar um conjunto de dados do BigQuery que será usado na última tarefa.
O ADK decide onde os artefatos vão na inicialização. O app_utils/services.py gerado lê uma única variável de ambiente:
def get_artifact_service():
"""Process-wide artifact service: GCS when a bucket is set, else in-memory."""
if bucket := os.environ.get("LOGS_BUCKET_NAME"):
return GcsArtifactService(bucket_name=bucket)
return InMemoryArtifactService()
Se você definir LOGS_BUCKET_NAME, todas as chamadas save_artifact serão direcionadas ao Cloud Storage.

Crie o conjunto de dados do BigQuery
- No Cloud Shell, execute o comando a seguir para criar um conjunto de dados do BigQuery:
bq mk --dataset --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> bwg - Aponte o repositório de artefatos do coordenador para o bucket:
export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"GcsArtifactServiceusa um nome de bucket simples, sem o prefixogs://. Assim como as outras variáveis, essa fica no shell, e a tarefa de implantação a transmite para o Cloud Run. - Verifique o conteúdo do bucket. Em uma nova guia do navegador, abra o seguinte URL:
Clique no bucket. Dependendo dos seus testes anteriores, ela pode conter imagens geradas anteriormente.https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
9. Implantar o agente do Visual Director no Cloud Run
Até agora, tudo foi executado em uma máquina, com o Visual Director acessível em localhost:8801. Nesta tarefa, você vai implantar o agente Visual Director como um serviço do Cloud Run e permitir que o gerador de sugestões se comunique com ele pela rede usando o protocolo Agent2Agent (A2A).
O diretor visual recebe um URL https:// real, então o _cloud_run_client que você escreveu antes começa a assinar solicitações em vez de retornar None. Além disso, o Cloud Run faz implantações particulares por padrão.

Implantar o Visual Director
- Interrompa o serviço em segundo plano local e o playground se eles ainda estiverem em execução:
fuser -k 8801/tcp 2>/dev/null || true fuser -k 8000/tcp 2>/dev/null || true - Implante o agente. Isso cria e envia um contêiner.⏰ O processo leva alguns minutos.
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director source .env agents-cli deploy \ --service-name visual-director \ --project <YOUR_PROJECT_ID> \ --region <YOUR_REGION> \ --no-confirm-project \ --labels dev-tutorial=bwg001-devlab-1 \ --update-env-vars "LOGS_BUCKET_NAME=$LOGS_BUCKET_NAME,GOOGLE_GENAI_USE_VERTEXAI=true,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION,GOOGLE_CLOUD_REGION=$GOOGLE_CLOUD_REGION"
Executar o gerador de sugestões que usa o diretor visual implantado no Cloud Run
- Leia o URL do diretor visual e execute o gerador de sugestões que o usa:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator source .env export VISUAL_DIRECTOR_URL=$(gcloud run services list \ --project <YOUR_PROJECT_ID> \ --region <YOUR_REGION> \ --flatten='spec.template.spec.containers[0].env' \ --filter='metadata.name=visual-director AND spec.template.spec.containers[0].env.name=APP_URL' \ --format='value(spec.template.spec.containers[0].env.value)') echo "Visual Director is at $VISUAL_DIRECTOR_URL" APP_URL=http://localhost:8000 nohup uv run uvicorn pitch_generator.fast_api_app:app --port 8000 > /tmp/visual-pitch_generator.log 2>&1 &
Chamar o fluxo de trabalho implantado
Os dois agentes são compatíveis com o protocolo A2A. O repositório envia um pequeno cliente para chamar o Gerador de sugestões por A2A.
Ele precisa que PITCH_GENERATOR_URL seja definido como o URL do gerador de ideias que está sendo executado localmente. Enquanto isso, o diretor visual é executado no Cloud Run, e o gerador de sugestões se comunica com ele por A2A.
- Copie o cliente para o projeto e execute-o:
Como o fluxo de trabalho contém um portal de aprovação humana, o script é pausado após gerar o conceito de rascunho:cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator cp ../solution/pitch-generator/call_agent.py . PITCH_GENERATOR_URL=http://localhost:8000 uv run python call_agent.py "Flying skateboards for cats" --save-image ./key_visual.png Digite=== HUMAN APPROVAL REQUIRED === [DRAFT CONCEPT] Concept Line: "Gravity is a Suggestion." ... Please approve the campaign concept (yes/no):yese pressione Enter para aprovar e continuar o fluxo de trabalho. Também é possível transmitir-you--auto-approvepara aprovar automaticamente. - Abra
call_agent.pye examine como ele interage com o agente usandoa2a-sdk:- O
ClientFactorycria umClientA2A oficial parahttps://SERVICE_URL/a2a/pitch_generator. - O
generate_pitch_asyncenvia umSendMessageRequestinicial. Quando a resposta retorna comTaskState.TASK_STATE_INPUT_REQUIRED, ela detecta a parteadk_request_input, mostra o conceito do rascunho, coleta sua aprovação e envia um pedido de resumo com sua resposta de volta para a tarefa. - O loop de análise lê as seções
CONCEPT,COPYeART DIRECTIONdos artefatos da tarefa concluída e salva a imagem binária emkey_visual.png.
key_visual.png. - O
- Faça upload da imagem principal para seu bucket, em um prefixo que o BigQuery possa apontar:
gcloud storage cp ./key_visual.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png - Gere um segundo pitch para ter algo com que comparar e faça upload dele também:
PITCH_GENERATOR_URL=http://localhost:8000 uv run python call_agent.py "A commuter bike built for rainy cities" --save-image ./bike.png gcloud storage cp ./bike.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/bike.png
10. Pontuar os recursos visuais principais para adequação à marca no BigQuery
Agora você tem imagens em um bucket e as diretrizes da marca que deveriam moldá-las. Nesta tarefa, você vai juntar os dois: referenciar as imagens do BigQuery e pedir a um modelo do Gemini para classificar cada uma de acordo com as diretrizes, sem mover um único byte do Cloud Storage.
Dois recursos do BigQuery fazem o trabalho:
ObjectRefé um valor que aponta para um objeto do Cloud Storage. OOBJ.MAKE_REFcria um URI e uma conexão que concede ao BigQuery acesso delegado ao bucket. Opitch-connectioncriado pelo script de configuração é essa conexão e já tem o leitor de objetos do Storage e o usuário da Agent Platform.- O
AI.SCORErecebe um comando e retorna umFLOAT64. O comando pode misturar strings com valoresObjectRefpara que o modelo veja a rubrica e a imagem juntas.

Criar uma tabela de recursos visuais principais
- Abra o BigQuery Studio em uma nova guia do navegador:
https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID> - Clique em Criar nova consulta (ícone +), cole e Execute o seguinte (substituindo
epelo ID do projeto e pela região). Ele cria uma linha por recurso visual principal, combinando o conceito da campanha com uma referência à imagem:SET @@location = '<YOUR_REGION>'; CREATE OR REPLACE TABLE bwg.key_visuals AS SELECT campaign, concept, OBJ.FETCH_METADATA( OBJ.MAKE_REF(uri, '<YOUR_REGION>.pitch-connection') ) AS key_visual FROM UNNEST([ STRUCT( 'cats' AS campaign, 'Flying skateboards for cats' AS concept, 'gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png' AS uri ), STRUCT( 'bike', 'A commuter bike built for rainy cities', 'gs://<YOUR_PROJECT_ID>-bwg/key-visuals/bike.png' ) ]);
OBJ.MAKE_REFcria a referência do URI e da conexão. Em seguida,OBJ.FETCH_METADATAentra em contato com o Cloud Storage e preenche o tamanho, o tipo de conteúdo e a geração do objeto. Essa segunda chamada também é a maneira mais rápida de provar que sua conexão funciona: se as permissões estiverem erradas, o campodetailsvai retornar o erro em vez dos metadados. - Inspecione o que você armazenou. Substitua a consulta anterior por esta e execute:
A tabela contém um ponteiro e os metadados dele, não a imagem. Os bytes nunca saíram do bucket.SELECT campaign, concept, key_visual.uri, key_visual.details FROM bwg.key_visuals;
Classifique cada imagem de acordo com as diretrizes da marca
O diretor visual recebeu instruções para obedecer a uma paleta, uma regra de iluminação e uma regra de composição. Se isso realmente aconteceu é uma decisão que você precisa tomar, e é para isso que serve AI.SCORE.
- Execute a consulta a seguir, substituindo
pela sua região. O comando é uma tupla: sua rubrica, o conceito da campanha e oObjectRefda imagem:SELECT campaign, concept, AI.SCORE( ('''You are the brand guardian reviewing a campaign key visual. Score the attached image from 1 to 10 on how closely it follows these house rules: - Palette: deep indigo and slate, with one warm accent of amber or terracotta. Nothing neon. - Light: one dominant low, raking source with long shadows. No flat overhead light. - Composition: subject off-center with generous empty negative space on the opposite side. - Subject: one subject only, photographic realism, shallow depth of field. - Never: text, logos, or watermarks anywhere in the frame. A 10 obeys every rule. A 1 ignores them. The campaign concept is: ''', concept, key_visual), connection_id => '<YOUR_REGION>.pitch-connection', endpoint => 'gemini-3.5-flash' ) AS brand_fit FROM bwg.key_visuals ORDER BY brand_fit DESC; - Transforme a pontuação em um veredicto que sua equipe possa usar:
CREATE OR REPLACE TABLE bwg.brand_review AS SELECT campaign, concept, brand_fit, IF(brand_fit >= 7, 'on brand', 'needs another pass') AS verdict FROM ( SELECT campaign, concept, AI.SCORE( ('''Score this campaign key visual from 1 to 10 on how closely it follows the house brand rules: deep indigo and slate palette with a single warm accent and nothing neon; one low raking light source with long shadows; the subject off-center with empty negative space opposite; a single photographic subject; and no text, logos, or watermarks in the frame. The campaign concept is: ''', concept, key_visual), connection_id => '<YOUR_REGION>.pitch-connection', endpoint => 'gemini-3.5-flash' ) AS brand_fit FROM bwg.key_visuals ); SELECT * FROM bwg.brand_review ORDER BY brand_fit DESC;AI.SCOREretorna umFLOAT64e nada mais. Portanto, o limite fica em SQL simples, onde você pode ajustar sem tocar no comando. A pontuação é executada por linha, o que significa que a mesma rubrica é aplicada a todos os recursos que uma campanha produz, não apenas aos dois que você gerou aqui.
11. Próximas etapas
Criar avaliações
As avaliações agênticas, ou "evals", são testes automatizados usados para medir a qualidade do raciocínio, do planejamento, do uso de ferramentas e da conclusão de fluxos de trabalho de várias etapas por agentes de IA autônomos. Eles são semelhantes aos testes de unidade para agentes, mas consideram a natureza não determinista deles.
Um dos nossos agentes inclui um Human in the loop, o que significa que ele aguarda uma entrada. Você precisa permitir que a Antigravity faça modificações no código atual para que esse comportamento não atrapalhe as avaliações automáticas.
Uma das melhores maneiras de começar é perguntar à CLI do Antigravity.
Digite o seguinte no Cloud Shell para iniciar a CLI:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
agy
Este é um comando inicial:
Explore this repository (`pitch-generator` and `visual-director`), including their agent instructions, tools, and `SKILL.md` files. Design and implement a creative `agents-cli eval` evaluation suite under `tests/eval/` that stress-tests the agent's behavior.
Você também pode encontrar um exemplo de avaliações e instruções para executá-las no repositório clonado.
12. Limpar
Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste codelab, exclua o projeto (recomendado) ou os recursos individuais criados durante o codelab:
gcloud run services delete visual-director --region=<YOUR_REGION> --project=<YOUR_PROJECT_ID> --quiet
bq rm -r -f -d <YOUR_PROJECT_ID>:bwg
bq rm --connection --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> pitch-connection
gcloud storage rm -r gs://<YOUR_PROJECT_ID>-bwg
13. Parabéns!
Neste codelab, você criou um gerador de sugestões de campanhas multiagente com o Kit de Desenvolvimento de Agente. Você conectou três agentes a um fluxo de trabalho de gráfico determinístico, empacotou diretrizes de marca como uma habilidade de agente que é carregada sob demanda e deu a um agente uma ferramenta de geração de imagens que salva a saída como um artefato. Em seguida, você dividiu o Visual Director em um serviço A2A próprio, incorporou-o de volta ao gráfico como um nó remoto, implantou no Cloud Run e chamou todo o fluxo de trabalho pelo A2A de um coordenador em execução localmente. Por fim, você fez referência aos recursos visuais principais gerados pelo BigQuery com ObjectRef e usou AI.SCORE para compará-los às diretrizes da sua marca.