Crea un generador de sugerencias con agentes: organización, evaluación y análisis con Google Cloud

1. Introducción

Banner de la clase magistral para agentes

Aprende a crear un motor multimodal de múltiples agentes que toma un resumen de tema sin procesar y coordina la creación de activos sociales muy atractivos. Con el Kit de desarrollo de agentes (ADK) de Google y el protocolo Agent2Agent (A2A), compondrás microagentes autónomos, empaquetarás reglas de la marca como una habilidad de agente reutilizable, implementarás un servicio de agente en Cloud Run y calificarás los elementos visuales clave generados para verificar el cumplimiento de la marca con las funciones de IA de BigQuery.

Actividades

En este codelab, aprenderás a realizar las siguientes tareas:

  • Organiza varios agentes del ADK con un flujo de trabajo de gráfico determinístico.
  • Empaqueta los lineamientos de desarrollo de la marca como una habilidad del agente que se carga solo cuando es necesario.
  • Equipar a un agente con una herramienta de generación de imágenes que guarde su resultado como un artefacto
  • Exponer un agente como un servicio A2A y llamarlo desde otro agente como un nodo remoto
  • Implementa el director visual en Cloud Run y llama al flujo de trabajo a través de A2A.
  • Califica las imágenes clave generadas para determinar si se ajustan a la marca en BigQuery con funciones de IA.

Requisitos

  • Un navegador web (como Chrome)
  • Una cuenta de Google Cloud y un proyecto de Google Cloud con la facturación habilitada

2. Antes de comenzar

Crea un proyecto de Google Cloud

  1. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.
  2. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Obtén información para verificar si la facturación está habilitada en un proyecto.

Inicie Cloud Shell

Usarás Google Cloud Shell para descargar el código, ejecutar secuencias de comandos de configuración y, luego, implementar la aplicación.

  1. Abre Google Cloud Shell en tu navegador (o haz clic en Activar Cloud Shell en la parte superior de la consola de Google Cloud):
    https://shell.cloud.google.com/
    
    Haz clic en Continuar si recibes avisos adicionales.Espera a que se carguen por completo la terminal y el editor. Este proceso puede demorar uno o dos minutos.
  2. Para configurar el ID del proyecto y la región de Google Cloud (por ejemplo, us-central1) en tu entorno, pega este comando en la terminal (reemplaza y por tus valores):
    export PROJECT_ID="<YOUR_PROJECT_ID>"
    export REGION="<YOUR_REGION>"
    gcloud config set project $PROJECT_ID
    
    Pegar en la terminalDeberías ver un mensaje similar al siguiente:
    Your active configuration is: [cloudshell-####]
    Updated property [core/project]
    
  3. Habilita las APIs de Google Cloud requeridas para tu proyecto:
    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
    

Clona el repositorio

Tenemos una plantilla de código para que comiences con el codelab. Es relativamente pequeño, pero el repositorio de GitHub en el que se encuentra es bastante grande. Para acelerar la clonación y ahorrar espacio en el disco, normalmente usaríamos git clone con la opción --filter=blob:none, git sparse-checkout y algunos comandos más.

Esto suena muy complejo, pero Antigravity puede hacerlo todo por nosotros.

Primero, configuremos la CLI de Antigravity para que pueda resolver nuestro problema de inmediato y, luego, iniciémosla.

En Cloud Shell, ejecuta 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)

El comando configura la CLI de Antigravity y la inicia en la misma terminal. Cuando salgas de la CLI de Antigravity, volverás al símbolo del sistema de Cloud Shell.

CLI de Antigravity

Solo necesitamos la carpeta codelabs/bwg-multi-agent-pitch-generator en el repositorio. Pidámosle a Antigravity que descargue solo esa carpeta y que la tenga de forma local en ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/.

Dale a la CLI de Antigravity esta tarea:

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.

Instrucción de la CLI de Antigravity

Antigravity ejecutará la tarea. Se te solicitarán permisos para ejecutar algunos comandos. Una vez que se descargue el código, sal de la CLI de Antigravity ejecutando el comando /exit:

/exit

Cómo salir de la CLI de Antigravity

Abre el espacio de trabajo del repositorio

Ahora, abramos el espacio de trabajo de código en Cloud Shell. Ejecuta este comando en la terminal de Cloud Shell:

cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
cloudshell workspace .

Ejecuta la secuencia de comandos de configuración

Para que esta experiencia sea más fluida, ejecuta la secuencia de comandos de configuración desde el repositorio. Configura variables de entorno, instala agents-cli, crea el bucket de Cloud Storage para los artefactos, crea la conexión a recursos de Cloud de BigQuery y otorga los permisos de IAM necesarios a su cuenta de servicio con reintentos automáticos.

La secuencia de comandos de configuración también llama a setenv.sh, un pequeño asistente que deriva las variables que necesitan ambos agentes (GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_REGION, GOOGLE_CLOUD_LOCATION y GOOGLE_GENAI_USE_VERTEXAI) de los objetos PROJECT_ID y REGION que exportaste anteriormente. También guarda esos dos valores en un archivo .env en tu directorio principal para que puedas recuperarlos más adelante.

  1. En Cloud Shell, haz que la secuencia de comandos de configuración sea ejecutable y ejecútala:
    chmod +x codelab-setup.sh setenv.sh
    ./codelab-setup.sh
    
  2. Este proceso tardará alrededor de un minuto. Deberías ver este mensaje que indica que la operación se realizó correctamente: "🎉 🦄 Script execution complete."éxito
  3. La secuencia de comandos de configuración se ejecutó en su propia shell, por lo que sus variables no llegaron a tu terminal. Carga los datos en tu sesión:
    source ./setenv.sh
    
    Deberías ver tu proyecto, región y ubicación.

3. Cómo organizar agentes con un flujo de trabajo de gráfico

En esta tarea, crearás el proyecto del generador de sugerencias y compilarás un flujo de trabajo de gráfico que ejecuta dos agentes y combina sus resultados.

Una sugerencia necesita un concepto, es decir, la idea y los recursos que venden ese concepto. En este caso de uso, diseñaremos agentes que tengan diferentes responsabilidades en el alcance del trabajo, lo que demostrará cómo puedes desacoplar tareas complejas y delegarlas a diferentes agentes con responsabilidades distintas, lo que facilitará su administración.

Dividir este tipo de trabajo entre especialistas requiere coordinar algo cuando se ejecuta cada agente. Puedes dejar que tu agente decida cuándo llamar a cada subagente o aplicar un enfoque más determinístico. En este codelab, usarás un enfoque determinístico con un flujo de trabajo de gráficos.

Genera el primer proyecto del agente

  1. En Cloud Shell, crea el proyecto pegando este comando en la 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
    
    Esto escribe un proyecto completo y ejecutable: un agente, un Dockerfile, pruebas, Terraform para más adelante y un archivo .env con su plantilla .env.example. En este codelab, son importantes dos partes:
    • pitch_generator/agent.py es el agente y el archivo que reescribes.
    • pitch_generator/ es el nombre del paquete, establecido por --agent-directory.
    El agente lee su proyecto y región del entorno que exportaste con setenv.sh, por lo que no hay nada que configurar aquí.
  2. Instala las dependencias:
    cd pitch-generator
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    
    Nota: El código también conecta el proyecto para que publique A2A, el protocolo que se usa más adelante en este codelab. Por ahora, deja esos archivos tal como están.

Cómo comprender el gráfico

El ADK v2.x admite flujos de trabajo de gráficos. Un gráfico se compila a partir de nodos, que son las unidades de trabajo, y bordes, que los conectan. Por lo general, un nodo es un agente, aunque también funciona una función simple de Python. Una arista indica que, cuando termina un nodo, se ejecuta otro a continuación.

Los bordes se escriben como una lista, y el ADK determina el orden:

edges=[
    ("START", creative_director),        ## the graph begins here
    (creative_director, copywriter),     ## then this
]

El anidamiento le da significado a un borde:

Edge

Significado

(a, b, c)

Una cadena: a, luego b, luego c, uno tras otro

(a, (b, c))

Una expansión. Primero a, luego b y c al mismo tiempo

((a, b), c)

Un fan-in. C espera a A y B.

Para un fan-in, usas un JoinNode, que espera a cada predecesor y pasa sus resultados juntos.

En esta tarea, compilaremos el siguiente flujo de trabajo:

Gráfico de flujo de trabajo inicial

Compila la canalización

  1. Pega lo siguiente en la terminal para abrir el archivo del agente:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
    Verás el agent.py estructurado en el editor de Cloud Shell.💡 Sugerencia: En cualquier momento, puedes seleccionar el código y usar la bombilla para pedirle a Gemini que lo explique. También puedes hacerle preguntas a la CLI de Antigravity.Usa Gemini Code Assist
  2. Reemplaza todo el archivo en pitch_generator/agent.py en el editor por lo siguiente:
    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. agents-cli incluye una IU de desarrollo web integrada llamada playground para la inspección, similar al ADK. Para iniciar el entorno de pruebas, haz lo siguiente:
    agents-cli playground --port 8080
    
    Se te preguntará si quieres habilitar la telemetría. Escribe n y presiona Intro.
  4. Haz clic en Vista previa en la Web (el ícono de ojo en la parte superior derecha de la terminal) y elige Vista previa en el puerto 8080.Haz clic en Vista previa en la Web.
  5. Verás la siguiente interfaz para chatear con el agente. Prueba con la instrucción "Patinetas voladoras para gatos". Verás una respuesta como la que se muestra a continuación, en la que también puedes inspeccionar el gráfico de flujo de trabajo completo.flujo de trabajo de demostraciónInspecciona cómo se produjo esa respuesta. El Playground muestra cada paso que siguió el gráfico, en orden: creative_director, luego copywriter, luego assemble y, por último, package. Esa secuencia es el gráfico que conectaste, y de esta manera puedes confirmar rápidamente el gráfico que acabas de crear.
  6. Regresa a la terminal y presiona Ctrl+C dos veces para detener el proceso de agents-cli playground en ejecución.

4. Cómo asignarle una habilidad a un agente

En esta tarea, crearás un segundo agente y le darás un conjunto de lineamientos de desarrollo de la marca empaquetados como una habilidad.

La canalización escribe palabras. Una imagen necesita un estilo de la casa: una paleta, una forma de iluminar las cosas, composiciones que usas y otras que nunca usas. Esa es una página de reglas, y una página de reglas necesita un mejor mecanismo para administrarse. Una forma de hacerlo es empaquetarlo como una "habilidad del agente".

Una habilidad es una carpeta que contiene un archivo SKILL.md. La estructura del directorio de habilidades se ve como el siguiente ejemplo (con visual_director, el próximo agente que compilarás):

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

El punto de entrada de la habilidad del agente es el archivo SKILL.md, que incluye una sección de metadatos iniciales en formato YAML como la que se muestra a continuación:

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

La etiqueta es el frontmatter. Es todo lo que ve el agente de antemano, por lo que la descripción debe indicar cuándo vale la pena cargar la habilidad. El cuerpo es el contenido y permanece fuera del contexto hasta que el agente llama a load_skill.

Esa división es el punto. La instrucción sigue siendo breve, el agente extrae las reglas solo cuando las necesita y cambiar el estilo significa editar un archivo en lugar de buscar en una cadena de instrucciones.

Comenzaremos a compilar el segundo agente, visual-director, utilizando las habilidades del agente, como se muestra en el siguiente diagrama:

Equipar al segundo agente con la habilidad

Cómo estructurar el Visual Director

Compilarás un agente especializado para controlar el componente visual, visual-director. Es un proyecto independiente porque, más adelante, se convertirá en su propio servicio para demostrar cómo se comunican varios agentes entre sí con el protocolo A2A. Al final, tendrás dos proyectos uno al lado del otro:

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. Para crear el proyecto y, luego, instalar sus dependencias, pega lo siguiente en la 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
    

Escribe los lineamientos de desarrollo de la marca

  1. Crea la carpeta de la habilidad y abre el archivo:
    mkdir -p visual_director/skills/brand-guidelines
    cloudshell edit visual_director/skills/brand-guidelines/SKILL.md
    
  2. Pega el siguiente contenido en el archivo SKILL.md abierto en el editor y guárdalo:Habilidad
    ---
    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.
    

Actualiza visual_director/agent.py

Actualiza el archivo agent.py y configúralo para que esté equipado con la habilidad definida anteriormente.

  1. Abre el archivo del agente:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Reemplaza todo el contenido del archivo visual_director/agent.py por el siguiente 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",
    )
    
    El agente visual-director ahora está configurado correctamente con una habilidad del agente. Antes de probarlo, equiparás a este agente con una herramienta para generar recursos de imagen como una de sus capacidades clave.

5. Equipa un agente con una herramienta de generación de imágenes

En esta tarea, le proporcionarás al director visual una herramienta que renderiza la dirección de arte como una imagen y la guarda como un artefacto. Un artefacto es una parte con nombre y versión de datos binarios o contenido de archivo que un agente de IA crea, modifica o usa durante una tarea.

la herramienta y el artefacto que escribe

Implementa y agrega la herramienta al agente

  1. Abre el archivo del agente:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Copia la siguiente herramienta generate_key_visual antes de la definición de 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. Agrega generate_key_visual debajo de SkillToolset para equipar al agente con la herramienta:
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
          generate_key_visual,
       ],
    
  4. agents-cli incluye una IU de desarrollo web integrada llamada playground para la inspección, similar al ADK. Para iniciar el entorno de pruebas, haz lo siguiente:
    agents-cli playground --port 8080
    
  5. Haz clic en Vista previa en la Web (el ícono de ojo en la parte superior derecha de la terminal) y elige Vista previa en el puerto 8080.Haz clic en Vista previa en la Web.
  6. Verás la siguiente interfaz para chatear con el agente. Intenta enviar la instrucción "Nueve vidas. Gravedad cero".flujo de trabajo de demostraciónPuedes inspeccionar las interacciones para ver cómo el agente visual_director ejecuta load_skill("brand-guidelines") para leer la habilidad de los lineamientos de desarrollo de la marca y, luego, ejecuta generate_key_visual para comenzar a generar la imagen. La imagen resultante se almacena como un artefacto, que también se muestra en la IU y se puede inspeccionar fácilmente.
  7. Regresa a la terminal y presiona Ctrl+C dos veces para detener el proceso de agents-cli playground en ejecución.

6. Arquitectura de varios agentes con el protocolo A2A

En esta tarea, expondrás el director visual como un servicio al que pueden llamar otros agentes y lo configurarás para que responda con la imagen generada.

Agent2Agent (A2A) es un protocolo abierto para que un agente llame a otro a través de una red. La pieza central es la tarjeta de agente, un documento JSON que el agente publica sobre sí mismo a través de una URL conocida, que indica su nombre, descripción y capacidades. El llamador recupera la tarjeta y sabe cómo hablar con el agente que está detrás de ella, sin código fuente, dependencias ni lenguaje compartidos.

Protocolo A2A

agents-cli ya conectó esto de forma predeterminada. El visual_director/app_utils/a2a.py generado por el andamio, que compila la tarjeta desde tu agente y activa las rutas, y visual_director/fast_api_app.py, que las publica. Solo necesitas una pequeña modificación para permitir que también envíe los artefactos al cliente.

La arquitectura se verá como se muestra a continuación. Ten en cuenta que el agente aún no está integrado en nuestro flujo de trabajo inicial:

Configura el servidor A2A

El arquetipo del servidor A2A agents-cli

El siguiente código y módulo ya se proporcionan cuando inicializamos el agente visual-director con agents-cli. Para abrir estos archivos, pega los siguientes comandos en la terminal:

cloudshell edit visual_director/fast_api_app.py
cloudshell edit visual_director/app_utils/a2a.py

visual_director/fast_api_app.py inicia el agente y activa las rutas de A2A en una ruta de acceso creada a partir del nombre de la app:

await attach_a2a_routes(
    app,
    agent=root_agent,
    runner=runner,
    rpc_path=f"/a2a/{adk_app.name}",   ## becomes /a2a/visual_director
)

visual_director/app_utils/a2a.py hace el resto en tres movimientos.

Primero, crea la tarjeta de agente a partir del agente. El nombre, la descripción y cada herramienta se convierten en entradas que un llamador puede leer:

agent_card = await AgentCardBuilder(
    agent=agent,
    rpc_url=f"{resolved_app_url}{rpc_path}",
).build()

En segundo lugar, coloca un ejecutor entre el protocolo y tu agente. Las solicitudes entrantes de A2A se traducen en ejecuciones de ADK, y los resultados se traducen de nuevo:

request_handler = DefaultRequestHandler(
    agent_executor=A2aAgentExecutor(runner=runner, force_new_version=True),
    task_store=task_store,
    agent_card=agent_card,
)

En tercer lugar, se activan dos rutas: una que entrega la tarjeta y otra que acepta llamadas.

Enruta

Objetivo

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

Es la tarjeta. El llamador lee esto primero para saber qué puede hacer el agente.

/a2a/visual_director

Es el extremo JSON-RPC. A dónde van las solicitudes reales

Permite que los artefactos crucen el límite

De forma predeterminada, una respuesta de A2A incluye texto. La imagen es un artefacto, y un artefacto permanece en el almacén del agente de entrega, a menos que lo configures para que se incluya.

  1. Abre el archivo A2A generado:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    cloudshell edit visual_director/app_utils/a2a.py
    
  2. Busca la importación de AgentCardBuilder y agrega las importaciones de A2aAgentExecutorConfig y include_artifacts_in_a2a_event_interceptor debajo de ella:
    ## ...
    ## 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. Busca la línea agent_executor=A2aAgentExecutor(runner=runner), y reemplázala por la siguiente (agrega A2aAgentExecutorConfig):
             agent_executor=A2aAgentExecutor(
                runner=runner,
                force_new_version=True,
                config=A2aAgentExecutorConfig(
                   execute_interceptors=[include_artifacts_in_a2a_event_interceptor]
                ),
             ),
    

Inicia el servicio y lee la tarjeta A2A publicada

  1. Inicia el servidor en segundo plano. Debes declarar APP_URL, ya que esta es la dirección que la tarjeta promociona como propia. Pega esto en la 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. Espera unos segundos para que se inicie y, luego, confirma que esté en funcionamiento:
    sleep 10
    tail -2 /tmp/visual-director.log
    
    Deberías ver lo siguiente:
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit)
    
  3. Lee la tarjeta que ahora publica el agente:
    curl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.tool
    
    La tarjeta enumera el nombre, la descripción, la URL y las habilidades del agente, incluido generate_key_visual. Se genera directamente a partir de la definición del agente en el 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"
    }
    
    Ya puedes incorporar el agente visual-director en el flujo de trabajo principal pitch-generator.

7. Incorpora el agente remoto de A2A en el flujo de trabajo

En esta tarea, agregarás el agente remoto al gráfico como una tercera rama.

Un agente remoto se encapsula en RemoteA2aAgent y se coloca en el gráfico como cualquier otro nodo. Los bordes conservan la misma forma, y el coordinador no sabe cuáles de sus nodos son locales.

Apunta el coordinador al servicio y actualiza el flujo de trabajo del gráfico

  1. Abre el archivo del agente:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
  2. Agrega estas importaciones en la parte superior del archivo, junto con las existentes:
    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. Agrega la siguiente definición de RemoteA2aAgent sobre la declaración de 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. Reemplaza o sobrescribe la función de Python del nodo package con la siguiente definición:
    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. Agrega una puerta de aprobación humana antes de que continúe la generación de nivel inferior. En pitch_generator/agent.py, define los nodos de aprobación sobre 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 último, actualiza las importaciones en la parte superior para incluir RequestInput y node de google.adk.events y google.adk.workflow:
    from google.adk.events import Event, RequestInput
    from google.adk.workflow import JoinNode, Workflow, node
    
    y modifica el flujo de trabajo root_agent edges para que se dirija a través de user_approval:
        edges=[
            ("START", creative_director),
            (creative_director, user_approval, (copywriter, visual_director)),
            ((creative_director, copywriter, visual_director), assemble),
            (assemble, package),
        ],
    
    Vale la pena señalar varios detalles:
    • Aprobación con interacción humana (HITL): approve_concept genera un RequestInput. Cuando el flujo de trabajo llega a este nodo, la ejecución se pausa y se emite una interrupción a la persona que llama. Las ramas secundarias (copywriter y visual_director) no se ejecutarán hasta que se confirme la aprobación del usuario.
    • visual_director aparece en los bordes exactamente igual que copywriter, aunque uno está a una llamada de función y el otro está a través de una red.
    • La imagen que se devuelve se lee de los eventos de la sesión, en lugar de node_input, ya que, actualmente, solo el texto se promueve a la salida de un nodo. La imagen cruzó el límite a través del interceptor de A2A, pero llegó adjunta a los eventos del agente remoto, por lo que la función auxiliar _key_visual la recupera de allí.

Prueba la integración

Ahora probarás que el director visual (que se ejecuta en segundo plano) y el generador de sugerencias pueden comunicarse a través del protocolo A2A.

  1. Confirma que el Director visual siga ejecutándose en segundo plano:
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
    Aquí deberías obtener un resultado de visual director: 200.En caso de que obtengas cualquier otro estado, ejecuta el siguiente comando para reiniciarlo:
    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. Inicia la vista previa:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator
    agents-cli playground --port 8080
    
  3. Haz clic en Vista previa en la Web (el ícono de ojo en la parte superior derecha de la terminal) y elige Vista previa en el puerto 8080.Haz clic en Vista previa en la Web.
  4. Verás la interfaz para chatear con el agente. Intenta enviar la instrucción "Patinetas voladoras para gatos".flujo de trabajo de demostraciónflujo de trabajo de demostraciónVerás la interacción completa en la que se inicia el flujo de trabajo y se ejecuta cada agente. Después de que creative_director redacta el concepto, el flujo de trabajo se detiene en la puerta de aprobación y te pide que confirmes lo siguiente: "Aprueba el concepto de la campaña (sí/no)". Ingresa "sí" en el campo de entrada para continuar.Una vez que se apruebe, el gráfico se expandirá: podrás ver cuándo el agente visual-director comienza a cargar la habilidad y, en el medio de la generación de imágenes, el agente copywriter termina de generar la leyenda de redes sociales, lo que demuestra que ambos agentes se ejecutan en paralelo sin esperarse. Después de que ambos finalizan, el nodo assemble une los resultados con el concepto aprobado que generó el creative_director, lo que finaliza la entrega de contenido de la sugerencia en la función package.Los dos servicios, conectados a través de A2A

8. Persiste las imágenes clave en Cloud Storage

Hasta ahora, cada imagen clave se ha almacenado en la memoria. En el momento en que se detiene el proceso, la imagen desaparece, lo que está bien para un espacio de pruebas y es inútil para una campaña. En esta tarea, le asignarás al flujo de trabajo un bucket en el que escribir y crearás un conjunto de datos de BigQuery que usarás en la última tarea.

El ADK decide dónde van los artefactos durante el inicio. El app_utils/services.py generado lee una sola variable de entorno:

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

Si configuras LOGS_BUCKET_NAME, cada llamada a save_artifact se realizará en Cloud Storage.

Integrar el servicio de artefactos con el bucket de GCS

Crea el conjunto de datos de BigQuery

  1. En Cloud Shell, ejecuta el siguiente comando para crear un conjunto de datos de BigQuery:
    bq mk --dataset --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> bwg
    
  2. Haz que el almacén de artefactos del coordinador apunte al bucket:
    export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"
    
    GcsArtifactService toma un nombre de bucket simple, sin el prefijo gs://. Al igual que las otras variables, esta reside en tu shell, y la tarea de implementación la pasa a Cloud Run.
  3. Verifica el contenido de tu bucket. En una nueva pestaña del navegador, abre la siguiente URL:
    https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
    
    Haz clic en el bucket. Según las pruebas anteriores, es posible que contenga imágenes generadas previamente.

9. Implementa el agente de Visual Director en Cloud Run

Hasta ahora, todo se ejecutó en una sola máquina, con el director visual accesible en localhost:8801. En esta tarea, implementarás el agente de Visual Director como su propio servicio de Cloud Run y permitirás que el generador de sugerencias se comunique con él a través de la red con el protocolo Agent2Agent (A2A).

El director visual obtiene una URL de https:// real, por lo que el _cloud_run_client que escribiste antes comienza a firmar solicitudes en lugar de devolver None. Además, Cloud Run realiza implementaciones privadas de forma predeterminada.

Ambos agentes como servicios de Cloud Run

Implementa Visual Director

  1. Detén el servicio en segundo plano local y el Playground si aún se están ejecutando:
    fuser -k 8801/tcp 2>/dev/null || true
    fuser -k 8000/tcp 2>/dev/null || true
    
  2. Implementa el agente. Esto compila un contenedor y lo envía.⏰ Este proceso tardará unos 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"
    

Ejecuta el Generador de sugerencias que usa el Director visual implementado en Cloud Run

  1. Lee la URL del director visual y ejecuta el generador de sugerencias que la 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 &
    

Llama al flujo de trabajo implementado

Ambos agentes admiten el protocolo A2A. El repositorio incluye un cliente pequeño para llamar a Pitch Generator a través de A2A.

Debe establecerse PITCH_GENERATOR_URL en la URL del Generador de sugerencias que se ejecuta de forma local. Mientras tanto, el director visual se ejecuta en Cloud Run, y el generador de sugerencias se comunica con él a través de A2A.

  1. Copia el cliente en tu proyecto y ejecútalo:
    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
    
    Debido a que el flujo de trabajo contiene una puerta de aprobación humana, la secuencia de comandos se pausa después de generar el concepto de borrador:
    === HUMAN APPROVAL REQUIRED ===
    
    [DRAFT CONCEPT]
    Concept Line: "Gravity is a Suggestion."
    ...
    
    Please approve the campaign concept (yes/no):
    
    Escribe yes y presiona Intro para aprobar y continuar el flujo de trabajo. (También puedes pasar -y o --auto-approve para aprobar automáticamente).
  2. Abre call_agent.py y examina cómo interactúa con el agente usando a2a-sdk:
    • ClientFactory compila una Client segmentación https://SERVICE_URL/a2a/pitch_generator oficial de A2A.
    • generate_pitch_async envía un SendMessageRequest inicial. Cuando la respuesta se devuelve con TaskState.TASK_STATE_INPUT_REQUIRED, detecta la parte adk_request_input, muestra el concepto del borrador, recopila tu aprobación y envía una solicitud de reanudación que incluye tu respuesta a la tarea.
    • El bucle de análisis lee las secciones CONCEPT, COPY y ART DIRECTION de los artefactos de la tarea completada y guarda la imagen binaria en key_visual.png.
    Deberías ver las tres secciones de texto impresas y la imagen guardada en key_visual.png.
  3. Sube la imagen clave a tu bucket, con un prefijo al que puedas dirigir BigQuery:
    gcloud storage cp ./key_visual.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png
    
  4. Genera una segunda sugerencia para tener algo con qué compararla y también súbela:
    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. Cómo calificar la adecuación de los elementos visuales clave para la marca en BigQuery

Ahora tienes imágenes en un bucket y los lineamientos de desarrollo de la marca que se suponía que les darían forma. En esta tarea, combinarás ambos elementos: harás referencia a las imágenes desde BigQuery y, luego, le pedirás a un modelo de Gemini que califique cada una según los lineamientos, sin mover un solo byte fuera de Cloud Storage.

Dos funciones de BigQuery realizan el trabajo:

  • ObjectRef es un valor que apunta a un objeto de Cloud Storage. OBJ.MAKE_REF compila uno a partir de un URI y una conexión que otorga a BigQuery acceso delegado al bucket. El objeto pitch-connection que crea la secuencia de comandos de configuración es esa conexión y ya contiene Storage Object Viewer y Agent Platform User.
  • AI.SCORE toma una instrucción y devuelve un FLOAT64. La instrucción puede combinar cadenas con valores de ObjectRef, de modo que el modelo vea tu rúbrica y la imagen juntas.

BigQuery lee el bucket

Crea una tabla de imágenes clave

  1. Abre BigQuery Studio en una pestaña nueva del navegador:
    https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID>
    
  2. Haz clic en Crear consulta nueva (el ícono +), luego, pega y ejecuta lo siguiente (reemplaza y por el ID del proyecto y la región). Crea una fila por cada imagen clave, en la que se vincula el concepto de la campaña con una referencia a la imagen:
    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 compila la referencia a partir del URI y la conexión. Luego, OBJ.FETCH_METADATA se comunica con Cloud Storage y completa el tamaño, el tipo de contenido y la generación del objeto. Esa segunda llamada también es la forma más rápida de demostrar que tu conexión funciona: si los permisos son incorrectos, el campo details muestra el error en lugar de los metadatos.
  3. Inspecciona lo que almacenaste. Reemplaza la consulta anterior por esta y ejecútala:
    SELECT campaign, concept, key_visual.uri, key_visual.details
    FROM bwg.key_visuals;
    
    La tabla contiene un puntero y sus metadatos, no la imagen. Los bytes nunca salieron del bucket.

Califica cada imagen según los lineamientos de la marca

Se le indicó al director visual que debía seguir una paleta, una regla de iluminación y una regla de composición. Si realmente lo hizo, es una decisión que se debe tomar, y para eso sirve AI.SCORE.

  1. Ejecuta la siguiente consulta (reemplaza por tu región). La instrucción es una tupla: tu rúbrica, el concepto de la campaña y el ObjectRef de la imagen:
    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. Convierte la puntuación en un veredicto sobre el que tu equipo pueda tomar medidas:
    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 devuelve un FLOAT64 y nada más, por lo que el umbral se encuentra en SQL simple, donde puedes ajustarlo sin modificar la instrucción. La puntuación se ejecuta por fila, lo que significa que se aplica la misma rúbrica a cada recurso que produce una campaña, no solo a los dos que generaste aquí.

11. Próximos pasos

Crea evaluaciones

Las evaluaciones de agentes o "evals" son pruebas automatizadas que se usan para medir qué tan bien razonan, planifican, usan herramientas y completan flujos de trabajo de varios pasos los agentes de IA autónomos. Son similares a las pruebas de unidades para agentes, pero tienen en cuenta su naturaleza no determinística.

Uno de nuestros agentes incluye un Human in the loop, lo que significa que espera una entrada. Deberás permitir que Antigravity realice modificaciones en el código actual para que este comportamiento no interfiera en las evaluaciones automáticas.

Una de las mejores formas de comenzar es preguntarle a la CLI de Antigravity.

Escribe lo siguiente en Cloud Shell para iniciar la CLI:

cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
agy

Esta es una instrucción para comenzar:

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.

También puedes encontrar un ejemplo de evaluaciones y las instrucciones para ejecutarlas en el repositorio clonado.

12. Limpia

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos que usaste en este codelab, borra el proyecto (recomendado) o los recursos individuales que creaste durante el 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. ¡Felicitaciones!

En este codelab, creaste un generador de sugerencias de campañas multiagente con el Kit de desarrollo de agentes. Conectaste tres agentes en un flujo de trabajo de gráfico determinístico, empaquetaste los lineamientos de la marca como una habilidad del agente que se carga a pedido y le proporcionaste a un agente una herramienta de generación de imágenes que guarda su resultado como un artefacto. Luego, dividiste el director visual en su propio servicio A2A, lo incorporaste al gráfico como un nodo remoto, lo implementaste en Cloud Run y llamaste a todo el flujo de trabajo a través de A2A desde un coordinador que se ejecuta de forma local. Por último, hiciste referencia a las imágenes clave generadas desde BigQuery con ObjectRef y usaste AI.SCORE para compararlas con los lineamientos de tu propia marca.