Criar um gerador de propostas agênticas: orquestração, avaliação e análise com o Google Cloud

1. Introdução

banner da masterclass para agentes

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

  1. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto na nuvem.
  2. 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.

  1. Abra o Google Cloud Shell no navegador ou clique em Ativar o Cloud Shell na parte de cima do console do Google Cloud:
    https://shell.cloud.google.com/
    
    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.
  2. Configure o ID do projeto e a região do Google Cloud (por exemplo, us-central1) no seu ambiente colando este no terminal (substitua e pelos seus valores):
    export PROJECT_ID="<YOUR_PROJECT_ID>"
    export REGION="<YOUR_REGION>"
    gcloud config set project $PROJECT_ID
    
    Colar no terminalVocê vai receber uma mensagem semelhante a esta:
    Your active configuration is: [cloudshell-####]
    Updated property [core/project]
    
  3. 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.

CLI do Antigravity

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.

Comando da CLI do Antigravity

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

Sair da CLI do Antigravity

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.

  1. No Cloud Shell, torne o script de configuração executável e execute-o:
    chmod +x codelab-setup.sh setenv.sh
    ./codelab-setup.sh
    
  2. Esse processo leva cerca de um minuto. Você vai ver esta mensagem indicando sucesso: "🎉 🦄 Script execution complete."sucesso
  3. 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:
    source ./setenv.sh
    
    Seu projeto, região e local serão repetidos.

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

  1. No Cloud Shell, crie o projeto colando este comando no terminal:
    cd ~/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-approve
    
    Isso grava um projeto completo e executável: um agente, um Dockerfile, testes, Terraform para mais tarde e um arquivo .env com 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.
    O agente lê o projeto e a região do ambiente que você exportou com setenv.sh. Portanto, não é necessário configurar nada aqui.
  2. Instale as dependências:
    cd pitch-generator
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    
    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.

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

(a, b, c)

Uma cadeia: a, depois b, depois c, um após o outro

(a, (b, c))

Uma distribuição de dados. a, depois b e c ao mesmo tempo

((a, b), c)

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:

gráfico inicial do fluxo de trabalho

Criar o pipeline

  1. Cole o seguinte no terminal para abrir o arquivo do agente:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
    O agent.py estruturado 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.Usar o Gemini Code Assist
  2. Substitua todo o arquivo em pitch_generator/agent.py no 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",
    )
    
  3. O agents-cli inclui uma interface de desenvolvimento da Web integrada chamada playground para inspeção, semelhante ao ADK. Para iniciar o playground:
    agents-cli playground --port 8080
    
    Você vai receber uma pergunta sobre se quer ativar a telemetria. Digite n e pressione Enter.
  4. Clique em Visualização na Web, o ícone de olho no canto superior direito do terminal, e escolha Visualizar na porta 8080.clicar em &quot;Visualização na Web&quot;
  5. 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.fluxo de trabalho de demonstraçãoInspecione como essa resposta foi produzida. O playground mostra cada etapa que o gráfico seguiu, em ordem: creative_director, copywriter, assemble e package. Essa sequência é o gráfico que você conectou, e assim você pode confirmar rapidamente o gráfico que acabou de criar.
  6. Volte ao terminal e interrompa o processo agents-cli playground em 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:

Equipar o segundo agente com uma habilidade

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

  1. 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
    
  2. Cole o conteúdo a seguir no arquivo SKILL.md aberto no Editor e salve-o:Habilidade
    ---
    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.

  1. Abra o arquivo do agente:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Substitua todo o conteúdo do arquivo visual_director/agent.py pelo seguinte código:
    import 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",
    )
    
    O agente visual-director agora 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.

a ferramenta e o artefato que ela grava

Implementar e adicionar a ferramenta ao agente

  1. Abra o arquivo do agente:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Copie a seguinte ferramenta generate_key_visual antes da definição root_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,
       }
    
  3. Adicione generate_key_visual abaixo de SkillToolset para equipar o agente com a ferramenta:
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
          generate_key_visual,
       ],
    
  4. O agents-cli inclui uma interface de desenvolvimento da Web integrada chamada playground para inspeção, semelhante ao ADK. Para iniciar o playground:
    agents-cli playground --port 8080
    
  5. Clique em Visualização na Web, o ícone de olho no canto superior direito do terminal, e escolha Visualizar na porta 8080.clicar em &quot;Visualização na Web&quot;
  6. Você vai ver a seguinte interface para conversar com o agente. Tente enviar o comando "Nove vidas. Gravidade zero."fluxo de trabalho de demonstraçãoVocê pode inspecionar nas interações como o agente visual_director executa load_skill("brand-guidelines") para ler a habilidade de diretrizes da marca e, em seguida, executa generate_key_visual para começar a gerar a imagem. A imagem resultante é armazenada como um artefato, que também é exibido na interface e pode ser facilmente inspecionado.
  7. Volte ao terminal e interrompa o processo agents-cli playground em 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.

Protocolo A2A

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:

configurar o servidor A2A

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

/a2a/visual_director/.well-known/agent-card.json

O card. O usuário lê isso primeiro para saber o que o agente pode fazer.

/a2a/visual_director

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.

  1. Abra o arquivo A2A gerado:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    cloudshell edit visual_director/app_utils/a2a.py
    
  2. Encontre a importação AgentCardBuilder e adicione as importações A2aAgentExecutorConfig e include_artifacts_in_a2a_event_interceptor abaixo 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,
    )
    
  3. Encontre a linha agent_executor=A2aAgentExecutor(runner=runner), e substitua pelo seguinte (adicionando o A2aAgentExecutorConfig):
             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

  1. 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 &
    
  2. Aguarde alguns segundos para que ele seja iniciado e confirme se está veiculando:
    sleep 10
    tail -2 /tmp/visual-director.log
    
    Você vai ver:
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit)
    
  3. Leia o card que o agente publica:
    curl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.tool
    
    O card lista o nome, a descrição, o URL e as habilidades do agente, incluindo generate_key_visual. Ele é gerado diretamente da definição do agente no código.
    {
       "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"
    }
    
    Agora você pode incorporar o agente visual-director ao fluxo de trabalho principal pitch-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

  1. Abra o arquivo do agente:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
  2. 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
    
  3. Adicione a seguinte definição de RemoteA2aAgent acima da declaração creative_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,
    )
    
  4. Substitua ou substitua a função Python do nó package pela 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)
    
  5. 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 de root_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")
    
  6. Por fim, atualize as importações na parte de cima para incluir RequestInput e node de google.adk.events e google.adk.workflow:
    from google.adk.events import Event, RequestInput
    from google.adk.workflow import JoinNode, Workflow, node
    
    e modifique o fluxo de trabalho root_agent edges para rotear por user_approval:
        edges=[
            ("START", creative_director),
            (creative_director, user_approval, (copywriter, visual_director)),
            ((creative_director, copywriter, visual_director), assemble),
            (assemble, package),
        ],
    
    Alguns detalhes importantes:
    • Aprovação human-in-the-loop (HITL): approve_concept gera um RequestInput. 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 (copywriter e visual_director) não serão executadas até que a aprovação do usuário seja confirmada.
    • visual_director aparece nas bordas exatamente como copywriter, 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_visual a recupera de lá.

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.

  1. Confirme se o Diretor visual ainda está em execução em segundo plano:
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
    Você vai receber um resultado visual director: 200 aqui.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
    
  2. Inicie a prévia:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator
    agents-cli playground --port 8080
    
  3. Clique em Visualização na Web, o ícone de olho no canto superior direito do terminal, e escolha Visualizar na porta 8080.clicar em &quot;Visualização na Web&quot;
  4. A interface para conversar com o agente vai aparecer. Envie o comando "Skates voadores para gatos".fluxo de trabalho de demonstraçãofluxo de trabalho de demonstraçãoVocê vai ver a interação completa em que o fluxo de trabalho começa e cada agente é executado. Depois que o creative_director cria 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 agente visual-director começa a carregar a habilidade e, no meio da geração de imagens, o agente copywriter termina 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ó assemble vai juntar os resultados com o conceito aprovado gerado pelo creative_director, finalizando a entrega do conteúdo da sugestão na função package.os dois serviços, conectados por A2A

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.

integrar o serviço de artefatos ao bucket do GCS

Crie o conjunto de dados do BigQuery

  1. 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
    
  2. Aponte o repositório de artefatos do coordenador para o bucket:
    export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"
    
    GcsArtifactService usa um nome de bucket simples, sem o prefixo gs://. Assim como as outras variáveis, essa fica no shell, e a tarefa de implantação a transmite para o Cloud Run.
  3. Verifique o conteúdo do bucket. Em uma nova guia do navegador, abra o seguinte URL:
    https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
    
    Clique no bucket. Dependendo dos seus testes anteriores, ela pode conter imagens geradas anteriormente.

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.

ambos os agentes como serviços do Cloud Run

Implantar o Visual Director

  1. 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
    
  2. 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

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

  1. Copie o cliente para o projeto e execute-o:
    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
    
    Como o fluxo de trabalho contém um portal de aprovação humana, o script é pausado após gerar o conceito de rascunho:
    === HUMAN APPROVAL REQUIRED ===
    
    [DRAFT CONCEPT]
    Concept Line: "Gravity is a Suggestion."
    ...
    
    Please approve the campaign concept (yes/no):
    
    Digite yes e pressione Enter para aprovar e continuar o fluxo de trabalho. Também é possível transmitir -y ou --auto-approve para aprovar automaticamente.
  2. Abra call_agent.py e examine como ele interage com o agente usando a2a-sdk:
    • O ClientFactory cria um Client A2A oficial para https://SERVICE_URL/a2a/pitch_generator.
    • O generate_pitch_async envia um SendMessageRequest inicial. Quando a resposta retorna com TaskState.TASK_STATE_INPUT_REQUIRED, ela detecta a parte adk_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, COPY e ART DIRECTION dos artefatos da tarefa concluída e salva a imagem binária em key_visual.png.
    As três seções de texto vão aparecer impressas, e a imagem será salva em key_visual.png.
  3. 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
    
  4. 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. O OBJ.MAKE_REF cria um URI e uma conexão que concede ao BigQuery acesso delegado ao bucket. O pitch-connection criado 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.SCORE recebe um comando e retorna um FLOAT64. O comando pode misturar strings com valores ObjectRef para que o modelo veja a rubrica e a imagem juntas.

O BigQuery lê o bucket

Criar uma tabela de recursos visuais principais

  1. Abra o BigQuery Studio em uma nova guia do navegador:
    https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID>
    
  2. Clique em Criar nova consulta (ícone +), cole e Execute o seguinte (substituindo e pelo 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'
      )
    ]);
    
    bqOBJ.MAKE_REF cria a referência do URI e da conexão. Em seguida, OBJ.FETCH_METADATA entra 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 campo details vai retornar o erro em vez dos metadados.
  3. Inspecione o que você armazenou. Substitua a consulta anterior por esta e execute:
    SELECT campaign, concept, key_visual.uri, key_visual.details
    FROM bwg.key_visuals;
    
    A tabela contém um ponteiro e os metadados dele, não a imagem. Os bytes nunca saíram do bucket.

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.

  1. Execute a consulta a seguir, substituindo pela sua região. O comando é uma tupla: sua rubrica, o conceito da campanha e o ObjectRef da 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;
    
  2. 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.SCORE retorna um FLOAT64 e 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.