Créer un générateur de pitchs agentiques : orchestration, évaluation et analyse avec Google Cloud

1. Introduction

bannière de la masterclass pour les agents

Découvrez comment créer un moteur multi-agent multimodal qui prend un brief thématique brut et orchestre la création d'assets sociaux très attrayants. À l'aide de l'Agent Development Kit (ADK) et du protocole Agent2Agent (A2A) de Google, vous allez composer des micro-agents autonomes, regrouper les règles de marque sous forme de compétence d'agent réutilisable, déployer un service d'agent sur Cloud Run et évaluer la conformité des visuels clés générés avec la marque à l'aide des fonctions d'IA BigQuery.

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez apprendre à effectuer les tâches suivantes :

  • Orchestrez plusieurs agents ADK avec un workflow de graphique déterministe.
  • Regroupez les consignes relatives à la marque dans une compétence d'agent qui ne se charge qu'en cas de besoin.
  • Équipez un agent d'un outil de génération d'images qui enregistre sa sortie en tant qu'artefact.
  • Exposez un agent en tant que service A2A et appelez-le depuis un autre agent en tant que nœud distant.
  • Déployez le Visual Director sur Cloud Run et appelez le workflow via A2A.
  • Évaluez l'adéquation des visuels clés générés à la marque dans BigQuery à l'aide de fonctions d'IA.

Prérequis

  • Un navigateur Web (tel que Chrome).
  • Un compte Google Cloud et un projet Google Cloud pour lequel la facturation est activée.

2. Avant de commencer

Créer un projet Google Cloud

  1. Dans la console Google Cloud, sur la page du sélecteur de projet, sélectionnez ou créez un projet Google Cloud.
  2. Assurez-vous que la facturation est activée pour votre projet Cloud. Découvrez comment vérifier si la facturation est activée sur un projet.

Démarrer Cloud Shell

Vous utiliserez Google Cloud Shell pour télécharger le code, exécuter les scripts de configuration et déployer l'application.

  1. Ouvrez Google Cloud Shell dans votre navigateur (ou cliquez sur Activer Cloud Shell en haut de la console Google Cloud) :
    https://shell.cloud.google.com/
    
    Cliquez sur Continuer si vous recevez des notifications supplémentaires.Attendez que le terminal et l'éditeur soient entièrement chargés. L'opération peut prendre une à deux minutes.
  2. Configurez l'ID et la région de votre projet Google Cloud (par exemple, us-central1) dans votre environnement en collant cette commande dans le terminal (remplacez et par vos valeurs) :
    export PROJECT_ID="<YOUR_PROJECT_ID>"
    export REGION="<YOUR_REGION>"
    gcloud config set project $PROJECT_ID
    
    Coller dans le terminalUn message semblable au suivant devrait s'afficher :
    Your active configuration is: [cloudshell-####]
    Updated property [core/project]
    
  3. Activez les API Google Cloud requises pour votre projet :
    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
    

Cloner le dépôt

Nous avons un modèle de code pour vous aider à démarrer l'atelier de programmation. Il est relativement petit, mais le dépôt GitHub dans lequel il se trouve est plutôt volumineux. Pour accélérer le clonage et économiser de l'espace disque, nous utiliserions normalement git clone avec l'option --filter=blob:none, git sparse-checkout et d'autres commandes.

Cela peut sembler très complexe, mais Antigravity peut tout faire pour nous !

Commençons par configurer Antigravity CLI pour qu'il puisse résoudre notre problème directement, puis lançons-le.

Dans Cloud Shell, exécutez la commande suivante :

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)

La commande configure la CLI Antigravity et la lance dans le même terminal. Lorsque vous quittez l'interface de ligne de commande Antigravity, vous revenez à l'invite Cloud Shell.

CLI Antigravity

Nous n'avons besoin que du dossier codelabs/bwg-multi-agent-pitch-generator dans le dépôt. Demandons à Antigravity de télécharger uniquement ce dossier et de le placer localement dans ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/.

Donnez cette tâche à la CLI 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.

Invite de la CLI Antigravity

Antigravity exécutera la tâche. Il vous demandera l'autorisation d'exécuter quelques commandes. Une fois le code téléchargé, quittez la CLI Antigravity en exécutant la commande /exit :

/exit

Quitter la CLI Antigravity

Ouvrir l'espace de travail du dépôt

Ouvrons maintenant l'espace de travail de code dans Cloud Shell. Exécutez cette commande dans le terminal Cloud Shell :

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

Exécuter le script de configuration

Pour rendre cette expérience plus fluide, exécutez le script de configuration à partir du dépôt. Il configure les variables d'environnement, installe agents-cli, crée le bucket Cloud Storage pour les artefacts, crée la connexion aux ressources Cloud BigQuery et accorde les autorisations IAM requises à son compte de service avec nouvelle tentative automatique.

Le script de configuration appelle également setenv.sh, un petit utilitaire qui dérive les variables dont les deux agents ont besoin (GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_REGION, GOOGLE_CLOUD_LOCATION et GOOGLE_GENAI_USE_VERTEXAI) à partir des PROJECT_ID et REGION que vous avez exportés ci-dessus. Il enregistre également ces deux valeurs dans un fichier .env de votre répertoire d'accueil afin que vous puissiez les récupérer ultérieurement.

  1. Dans Cloud Shell, rendez le script de configuration exécutable et exécutez-le :
    chmod +x codelab-setup.sh setenv.sh
    ./codelab-setup.sh
    
  2. L'exécution prend environ une minute. Le message suivant doit s'afficher pour indiquer que l'opération a réussi : 🎉 🦄 Script execution complete.success
  3. Le script de configuration s'est exécuté dans un shell qui lui est propre. Ses variables n'ont donc pas atteint votre terminal. Chargez-les dans votre session :
    source ./setenv.sh
    
    Votre projet, votre région et votre emplacement devraient s'afficher.

3. Orchestrer des agents avec un workflow de graphique

Dans cette tâche, vous allez créer le projet de générateur de pitch et créer un workflow de graphique qui exécute deux agents et combine leurs sorties.

Un argumentaire doit comporter un concept, c'est-à-dire l'idée et les éléments qui la vendent. Dans ce cas d'utilisation, nous allons concevoir des agents ayant différentes responsabilités dans le cadre du travail. Nous allons ainsi vous montrer comment dissocier des tâches complexes et les déléguer à différents agents ayant des responsabilités distinctes, ce qui les rend plus faciles à gérer.

Pour répartir ce type de travail entre plusieurs spécialistes, il faut un élément de coordination lorsque chaque agent s'exécute. Vous pouvez choisir de laisser votre agent décider quand appeler chaque sous-agent ou appliquer une approche plus déterministe. Dans cet atelier de programmation, vous allez utiliser une approche déterministe avec un workflow de graphiques.

Créer la structure du premier projet d'agent

  1. Dans Cloud Shell, créez le projet en collant la commande suivante dans le 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
    
    Cette commande écrit un projet complet et exécutable : un agent, un Dockerfile, des tests, Terraform pour plus tard et un fichier .env avec son modèle .env.example. Deux parties sont importantes pour cet atelier de programmation :
    • pitch_generator/agent.py est l'agent et le fichier que vous réécrivez.
    • pitch_generator/ est le nom du package défini par --agent-directory.
    L'agent lit son projet et sa région à partir de l'environnement que vous avez exporté avec setenv.sh. Il n'y a donc rien à configurer ici.
  2. Installez les dépendances :
    cd pitch-generator
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    
    Remarque : L'échafaudage connecte également le projet pour qu'il serve A2A, le protocole utilisé plus loin dans cet atelier de programmation. Pour l'instant, ne touchez pas à ces fichiers.

Comprendre le graphique

L'ADK v2.x est compatible avec les workflows de graphiques. Un graphique est construit à partir de nœuds, qui sont les unités de travail, et d'arêtes, qui les relient. Un nœud est généralement un agent, mais une simple fonction Python fonctionne également. Une arête indique qu'un autre nœud s'exécute après la fin d'un nœud.

Les arêtes sont écrites sous forme de liste, et l'ADK détermine l'ordre :

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

L'imbrication donne un sens à l'avantage :

Edge

Signification

(a, b, c)

Une chaîne : a, puis b, puis c, l'un après l'autre

(a, (b, c))

Distribution ramifiée : a, puis b et c en même temps

((a, b), c)

Un fan-in. c attend à la fois a et b

Pour un fan-in, vous utilisez un JoinNode, qui attend chaque prédécesseur et transmet ses sorties ensemble.

Dans cette tâche, nous allons créer le workflow suivant :

Graphique de workflow initial

Créer le pipeline

  1. Ouvrez le fichier de l'agent en collant ce dans le terminal :
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
    Le fichier agent.py échafaudé s'affiche dans l'éditeur Cloud Shell.💡 Conseil : À tout moment, vous pouvez sélectionner le code et utiliser l'ampoule pour demander à Gemini de l'expliquer. Vous pouvez également interroger l'interface de ligne de commande Antigravity.Utiliser Gemini Code Assist
  2. Remplacez l'intégralité du fichier dans pitch_generator/agent.py dans l'éditeur par le code suivant :
    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 inclut une UI de développement Web intégrée appelée playground pour l'inspection, comme ADK. Pour démarrer le bac à sable :
    agents-cli playground --port 8080
    
    Vous serez invité à activer la télémétrie. Saisissez n et appuyez sur Entrée.
  4. Cliquez sur Aperçu sur le Web (l'icône en forme d'œil en haut à droite du terminal), puis sélectionnez Prévisualiser sur le port 8080.cliquer sur &quot;Aperçu sur le Web&quot;
  5. L'interface suivante s'affiche pour vous permettre de discuter avec l'agent. Essayez d'envoyer la requête Skateboards volants pour chats. Vous verrez une réponse comme celle ci-dessous, où vous pourrez également inspecter le graphique complet du workflow.workflow de démonstrationInspecter la façon dont cette réponse a été produite. L'atelier de programmation montre chaque étape effectuée par le graphique, dans l'ordre : creative_director, puis copywriter, puis assemble, puis package. Cette séquence correspond au graphique que vous avez câblé. Vous pouvez ainsi confirmer rapidement le graphique que vous venez de créer.
  6. Revenez au terminal et arrêtez le processus agents-cli playground en cours d'exécution en appuyant deux fois sur Ctrl+C.

4. Attribuer une compétence à un agent

Dans cette tâche, vous allez créer un deuxième agent et lui fournir un ensemble de consignes relatives à la marque sous forme de compétence.

Le pipeline écrit des mots. Une image a besoin d'un style : une palette, une façon d'éclairer les choses, des compositions que vous utilisez et celles que vous n'utilisez jamais. Il s'agit d'une page de règles, et une page de règles a besoin d'un meilleur mécanisme pour être gérée. Une façon de le faire est de l'empaqueter en tant que "compétence d'agent".

Une skill est un dossier contenant un fichier SKILL.md. La structure du répertoire de compétences ressemble à l'exemple suivant (à l'aide de visual_director, le prochain agent que vous allez créer) :

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

Le point d'entrée de la compétence de l'agent est le fichier SKILL.md, qui inclut une section de métadonnées YAML comme celle illustrée ci-dessous :

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

Le frontmatter est le libellé. C'est tout ce que l'agent voit au départ. C'est pourquoi la description doit indiquer quand la compétence vaut la peine d'être chargée. Le corps correspond au contenu. Il reste hors contexte jusqu'à ce que l'agent appelle load_skill.

C'est le but de cette séparation. La requête reste courte, l'agent n'extrait les règles que lorsqu'il en a besoin, et le changement de style implique de modifier un seul fichier au lieu de chercher dans une chaîne d'instructions.

Nous allons commencer à créer le deuxième agent, visual-director, en utilisant les compétences de l'agent, comme indiqué dans le schéma ci-dessous :

Équiper le deuxième agent avec une compétence

Échafauder le Visual Director

Vous allez créer un agent spécialisé pour gérer le composant visuel, visual-director. Il s'agit d'un projet distinct, car il deviendra plus tard son propre service pour montrer comment plusieurs agents communiquent entre eux à l'aide du protocole A2A. Vous obtiendrez deux projets côte à côte :

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. Créez le projet et installez ses dépendances en collant ce dans le 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
    

Rédiger les consignes relatives à la marque

  1. Créez le dossier de compétence et ouvrez le fichier :
    mkdir -p visual_director/skills/brand-guidelines
    cloudshell edit visual_director/skills/brand-guidelines/SKILL.md
    
  2. Collez le contenu suivant dans le fichier SKILL.md ouvert dans l'éditeur, puis enregistrez-le :Compétence
    ---
    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.
    

Mettre à jour visual_director/agent.py

Mettez à jour le fichier agent.py et configurez-le pour qu'il soit équipé de la compétence définie précédemment.

  1. Ouvrez le fichier de l'agent :
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Remplacez l'intégralité du contenu du fichier visual_director/agent.py par le code suivant :
    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",
    )
    
    L'agent visual-director est désormais correctement configuré avec une compétence d'agent. Avant de le tester, vous allez équiper cet agent d'un outil permettant de générer des composants d'image, qui sera l'une de ses principales capacités.

5. Équiper un agent d'un outil de génération d'images

Dans cette tâche, vous allez fournir au directeur artistique un outil qui affiche la direction artistique sous forme d'image et l'enregistre en tant qu'artefact. Un artefact est un élément nommé et versionné de données binaires ou de contenu de fichier qu'un agent d'IA crée, modifie ou utilise lors d'une tâche.

l'outil et l'artefact qu'il écrit

Implémenter et ajouter l'outil à l'agent

  1. Ouvrez le fichier de l'agent :
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Copiez l'outil generate_key_visual suivant avant la définition 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. Ajoutez generate_key_visual sous SkillToolset pour équiper l'agent de l'outil :
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
          generate_key_visual,
       ],
    
  4. agents-cli inclut une UI de développement Web intégrée appelée playground pour l'inspection, comme ADK. Pour démarrer le bac à sable :
    agents-cli playground --port 8080
    
  5. Cliquez sur Aperçu sur le Web (l'icône en forme d'œil en haut à droite du terminal), puis sélectionnez Prévisualiser sur le port 8080.cliquer sur &quot;Aperçu sur le Web&quot;
  6. L'interface suivante s'affiche pour vous permettre de discuter avec l'agent. Essayez d'envoyer la requête Neuf vies. Zéro gravité."workflow de démonstrationVous pouvez inspecter les interactions pour voir comment l'agent visual_director exécute load_skill("brand-guidelines") pour lire la compétence des consignes relatives à la marque, puis exécute generate_key_visual pour commencer à générer l'image. L'image obtenue est stockée en tant qu'artefact, qui est également affiché dans l'UI et peut être facilement inspecté.
  7. Revenez au terminal et arrêtez le processus agents-cli playground en cours d'exécution en appuyant deux fois sur Ctrl+C.

6. Architecture multi-agents avec le protocole A2A

Dans cette tâche, vous allez exposer le Visual Director en tant que service que d'autres agents peuvent appeler, et le configurer pour qu'il réponde avec l'image générée.

Agent2Agent (A2A) est un protocole ouvert permettant à un agent d'en appeler un autre sur un réseau. L'élément central est la carte d'agent, un document JSON que l'agent publie sur lui-même via une URL bien connue, en indiquant son nom, sa description et ses capacités. Un appelant récupère la fiche et sait comment parler à l'agent qui se trouve derrière, sans code source, dépendances ni langage partagés.

Protocole A2A

agents-cli a déjà configuré cette option par défaut. Le scaffold généré visual_director/app_utils/a2a.py, qui crée la fiche à partir de votre agent et monte les routes, et visual_director/fast_api_app.py, qui les sert. Il vous suffit d'apporter une petite modification pour qu'il puisse également renvoyer les artefacts au client.

L'architecture se présentera comme suit. Notez que l'agent n'est pas encore intégré à notre workflow initial :

Configurer un serveur A2A

Structure du serveur A2A agents-cli

Le code et le module suivants sont déjà fournis lorsque nous initialisons l'agent visual-director à l'aide de agents-cli. Ouvrez ces fichiers en collant les commandes suivantes dans le terminal :

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

visual_director/fast_api_app.py démarre l'agent et monte les routes A2A sous un chemin d'accès créé à partir du nom de l'application :

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 s'occupe du reste en trois étapes.

Tout d'abord, il crée la carte de l'agent à partir de l'agent lui-même. Le nom, la description et chaque outil deviennent des entrées qu'un appelant peut lire :

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

Deuxièmement, il place un exécuteur entre le protocole et votre agent. Les requêtes A2A entrantes sont traduites en exécutions ADK, et les résultats sont retraduits :

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

Troisièmement, il monte deux routes : l'une servant la carte, l'autre acceptant les appels.

Route

Objectif

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

La carte. Le correspondant lit ce message en premier pour savoir ce que l'agent peut faire.

/a2a/visual_director

Point de terminaison JSON-RPC. Où sont envoyées les demandes

Autoriser les artefacts à franchir la limite

Par défaut, une réponse A2A contient du texte. L'image est un artefact, et un artefact reste dans le magasin de l'agent de diffusion, sauf si vous configurez son inclusion.

  1. Ouvrez le fichier A2A généré :
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    cloudshell edit visual_director/app_utils/a2a.py
    
  2. Recherchez l'importation AgentCardBuilder et ajoutez les importations A2aAgentExecutorConfig et include_artifacts_in_a2a_event_interceptor en dessous :
    ## ...
    ## 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. Recherchez la ligne agent_executor=A2aAgentExecutor(runner=runner), et remplacez-la par la ligne suivante (en ajoutant A2aAgentExecutorConfig) :
             agent_executor=A2aAgentExecutor(
                runner=runner,
                force_new_version=True,
                config=A2aAgentExecutorConfig(
                   execute_interceptors=[include_artifacts_in_a2a_event_interceptor]
                ),
             ),
    

Démarrer le service et lire la carte A2A publiée

  1. Démarrez le serveur en arrière-plan. Vous devez déclarer APP_URL, car il s'agit de l'adresse que la carte indique comme étant la sienne. Collez-le dans le 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. Attendez quelques secondes qu'il démarre, puis vérifiez qu'il est en cours d'exécution :
    sleep 10
    tail -2 /tmp/visual-director.log
    
    Vous devriez obtenir le résultat suivant :
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit)
    
  3. Lisez la fiche que l'agent publie maintenant :
    curl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.tool
    
    La fiche liste le nom, la description, l'URL et les compétences de l'agent, y compris generate_key_visual. Il est généré directement à partir de la définition de l'agent dans le code.
    {
       "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"
    }
    
    Vous êtes prêt à intégrer l'agent visual-director dans le workflow pitch-generator principal.

7. Intégrer l'agent distant A2A au workflow

Dans cette tâche, vous allez ajouter l'agent distant au graphique en tant que troisième branche.

Un agent à distance est encapsulé dans RemoteA2aAgent et placé dans le graphique comme n'importe quel autre nœud. Les arêtes conservent la même forme et le coordinateur ne sait pas lesquels de ses nœuds sont locaux.

Pointer le coordinateur vers le service et mettre à jour le workflow du graphique

  1. Ouvrez le fichier de l'agent :
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
  2. Ajoutez ces importations en haut du fichier, à côté de celles qui existent déjà :
    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. Ajoutez la définition RemoteA2aAgent suivante au-dessus de la déclaration 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. Écrasez ou remplacez la fonction Python du nœud package par la définition suivante :
    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. Ajoutez un jalon d'approbation humaine avant que la génération en aval ne se poursuive. Dans pitch_generator/agent.py, définissez les nœuds d'approbation au-dessus 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. Enfin, mettez à jour les importations en haut de la page pour inclure RequestInput et node à partir de google.adk.events et google.adk.workflow :
    from google.adk.events import Event, RequestInput
    from google.adk.workflow import JoinNode, Workflow, node
    
    et modifiez le workflow root_agent edges pour qu'il passe par user_approval :
        edges=[
            ("START", creative_director),
            (creative_director, user_approval, (copywriter, visual_director)),
            ((creative_director, copywriter, visual_director), assemble),
            (assemble, package),
        ],
    
    Voici quelques points importants à retenir :
    • Approbation avec intervention humaine (HITL) : approve_concept génère RequestInput. Lorsque le workflow atteint ce nœud, l'exécution s'interrompt et une interruption est émise à l'appelant. Les branches en aval (copywriter et visual_director) ne s'exécuteront pas tant que l'approbation de l'utilisateur n'aura pas été confirmée.
    • visual_director apparaît dans les périphéries exactement comme copywriter, bien que l'un soit un appel de fonction et l'autre sur un réseau.
    • L'image renvoyée est lue à partir des événements de session plutôt que de node_input, car actuellement, seul le texte est promu dans la sortie d'un nœud. L'image a franchi la limite via l'intercepteur A2A, mais est arrivée en pièce jointe aux événements de l'agent distant. La fonction d'assistance _key_visual la récupère donc à partir de là.

Tester l'intégration

Vous allez maintenant tester la communication entre le Visual Director (qui s'exécute en arrière-plan) et le générateur de pitch via le protocole A2A.

  1. Vérifiez que le Visual Director est toujours en cours d'exécution en arrière-plan :
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
    Vous devriez obtenir un résultat visual director: 200.Si vous obtenez un autre état, exécutez la commande suivante pour le redémarrer :
    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. Lancez l'aperçu :
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator
    agents-cli playground --port 8080
    
  3. Cliquez sur Aperçu sur le Web (l'icône en forme d'œil en haut à droite du terminal), puis sélectionnez Prévisualiser sur le port 8080.cliquer sur &quot;Aperçu sur le Web&quot;
  4. L'interface de chat avec l'agent s'affiche. Essayez d'envoyer la requête "Skateboards volants pour chats".workflow de démonstrationworkflow de démonstration Vous verrez l'intégralité de l'interaction, du début du workflow à l'exécution de chaque agent. Une fois que creative_director a rédigé le concept, le workflow s'interrompt au niveau de la porte d'approbation et vous demande de confirmer : Veuillez approuver le concept de la campagne (oui/non). Saisissez oui dans le champ de saisie pour continuer.Une fois approuvé, le graphique s'étend : vous pouvez voir quand l'agent visual-director commence à charger la compétence et, au milieu de la génération d'image, l'agent copywriter termine de générer la légende pour les réseaux sociaux, ce qui montre que les deux agents s'exécutent en parallèle sans attendre l'un l'autre. Une fois les deux tâches terminées, le nœud assemble combine les résultats avec le concept approuvé généré par creative_director, finalisant ainsi la diffusion du contenu de la présentation dans la fonction package.les deux services, connectés via A2A

8. Conserver les principaux éléments visuels dans Cloud Storage

Jusqu'à présent, chaque élément visuel clé était stocké en mémoire. Au moment où le processus s'arrête, l'image disparaît, ce qui est acceptable pour un terrain de jeu, mais inutile pour une campagne. Dans cette tâche, vous allez attribuer un bucket au workflow pour l'écriture et créer un ensemble de données BigQuery que vous utiliserez dans la dernière tâche.

L'ADK décide où placer les artefacts au démarrage. Le app_utils/services.py généré lit une seule variable d'environnement :

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 vous définissez LOGS_BUCKET_NAME, chaque appel save_artifact sera redirigé vers Cloud Storage.

intégrer le service d'artefacts au bucket GCS

Créer l'ensemble de données BigQuery

  1. Dans Cloud Shell, exécutez la commande suivante pour créer un ensemble de données BigQuery :
    bq mk --dataset --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> bwg
    
  2. Pointez le magasin d'artefacts du coordinateur vers le bucket :
    export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"
    
    GcsArtifactService accepte un nom de bucket brut, sans le préfixe gs://. Comme les autres variables, celle-ci réside dans votre shell, et la tâche de déploiement la transmet à Cloud Run.
  3. Vérifiez le contenu de votre bucket. Dans un nouvel onglet de navigateur, ouvrez l'URL suivante :
    https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
    
    Cliquez sur le bucket. En fonction de vos tests précédents, il peut contenir des images générées précédemment.

9. Déployer l'agent Visual Director sur Cloud Run

Jusqu'à présent, tout s'est exécuté sur une seule machine, avec le Visual Director accessible à l'adresse localhost:8801. Dans cette tâche, vous allez déployer l'agent Visual Director en tant que service Cloud Run distinct et permettre au générateur de pitch de communiquer avec lui sur le réseau à l'aide du protocole Agent2Agent (A2A).

Le directeur visuel obtient une véritable URL https://. Par conséquent, le _cloud_run_client que vous avez écrit précédemment commence à signer les requêtes au lieu de renvoyer None. De plus, Cloud Run déploie les services de manière privée par défaut.

les deux agents en tant que services Cloud Run.

Déployer Visual Director

  1. Arrêtez le service d'arrière-plan local et le bac à sable s'ils sont toujours en cours d'exécution :
    fuser -k 8801/tcp 2>/dev/null || true
    fuser -k 8000/tcp 2>/dev/null || true
    
  2. Déployez l'agent. Cette opération crée un conteneur et le publie.⏰ Cela prendra quelques minutes.
    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"
    

Exécuter le générateur de pitchs qui utilise le Visual Director déployé sur Cloud Run

  1. Lisez l'URL du réalisateur et exécutez le générateur de pitch qui l'utilise :
    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 &
    

Appeler le workflow déployé

Les deux agents sont compatibles avec le protocole A2A. Le dépôt fournit un petit client pour appeler le générateur de pitch via A2A.

PITCH_GENERATOR_URL doit être défini sur l'URL du générateur de suggestions qui s'exécute en local. Pendant ce temps, le Visual Director s'exécute dans Cloud Run, et le Pitch Generator communique avec lui via A2A.

  1. Copiez le client dans votre projet et exécutez-le :
    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
    
    Comme le workflow contient une étape d'approbation humaine, le script s'interrompt après avoir généré le brouillon du concept :
    === HUMAN APPROVAL REQUIRED ===
    
    [DRAFT CONCEPT]
    Concept Line: "Gravity is a Suggestion."
    ...
    
    Please approve the campaign concept (yes/no):
    
    Saisissez yes et appuyez sur Entrée pour approuver et poursuivre le workflow. (Vous pouvez également transmettre -y ou --auto-approve pour approuver automatiquement.)
  2. Ouvrez call_agent.py et examinez comment il interagit avec l'agent à l'aide de a2a-sdk :
    • ClientFactory crée un Client A2A officiel ciblant https://SERVICE_URL/a2a/pitch_generator.
    • generate_pitch_async envoie un SendMessageRequest initial. Lorsque la réponse renvoie TaskState.TASK_STATE_INPUT_REQUIRED, elle détecte la partie adk_request_input, affiche le concept de brouillon, recueille votre approbation et renvoie une demande de reprise contenant votre réponse à la tâche.
    • La boucle d'analyse lit les sections CONCEPT, COPY et ART DIRECTION des artefacts de la tâche terminée, puis enregistre l'image binaire dans key_visual.png.
    Les trois sections de texte devraient être imprimées et l'image enregistrée dans key_visual.png.
  3. Importez le visuel clé dans votre bucket, sous un préfixe vers lequel vous pouvez rediriger BigQuery :
    gcloud storage cp ./key_visual.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png
    
  4. Générez une deuxième suggestion pour avoir un élément de comparaison, puis importez-la également :
    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. Évaluer l'adéquation des principaux éléments visuels avec la marque dans BigQuery

Vous disposez maintenant d'images dans un bucket et des consignes relatives à la marque qui devaient les façonner. Dans cette tâche, vous allez combiner les deux : référencer les images depuis BigQuery, puis demander à un modèle Gemini de les évaluer par rapport aux consignes, sans déplacer un seul octet hors de Cloud Storage.

Deux fonctionnalités BigQuery sont utilisées :

  • ObjectRef est une valeur qui pointe vers un objet Cloud Storage. OBJ.MAKE_REF en crée un à partir d'un URI et d'une connexion qui accorde à BigQuery un accès délégué au bucket. Le pitch-connection créé par le script de configuration est cette connexion. Il contient déjà les rôles "Lecteur d'objets Storage" et "Utilisateur d'Agent Platform".
  • AI.SCORE prend un prompt et renvoie un FLOAT64. La requête peut mélanger des chaînes avec des valeurs ObjectRef, de sorte que le modèle voit à la fois votre grille d'évaluation et l'image.

BigQuery lit le bucket

Créer un tableau des principaux éléments visuels

  1. Ouvrez BigQuery Studio dans un nouvel onglet du navigateur :
    https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID>
    
  2. Cliquez sur Créer une requête (icône +), puis collez et exécutez la requête suivante (en remplaçant et par l'ID de votre projet et votre région). Elle crée une ligne par visuel clé, en associant le concept de la campagne à une référence à l'image :
    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 crée la référence à partir de l'URI et de la connexion. OBJ.FETCH_METADATA contacte ensuite Cloud Storage et renseigne la taille, le type de contenu et la génération de l'objet. Ce deuxième appel est également le moyen le plus rapide de prouver que votre connexion fonctionne : si les autorisations sont incorrectes, le champ details renvoie l'erreur au lieu des métadonnées.
  3. Inspectez ce que vous avez stocké. Remplacez la requête précédente par celle-ci, puis exécutez-la :
    SELECT campaign, concept, key_visual.uri, key_visual.details
    FROM bwg.key_visuals;
    
    La table contient un pointeur et ses métadonnées, mais pas l'image. Les octets n'ont jamais quitté le bucket.

Évaluez chaque image par rapport aux consignes relatives à la marque.

Le directeur visuel a été chargé de respecter une palette, une règle d'éclairage et une règle de composition. C'est à vous de décider si c'est le cas ou non. C'est à cela que sert AI.SCORE.

  1. Exécutez la requête suivante (en remplaçant par votre région). La requête est un tuple : votre grille d'évaluation, le concept de la campagne et le ObjectRef de l'image :
    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. Transformez le score en verdict sur lequel votre équipe peut agir :
    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 renvoie un FLOAT64 et rien d'autre. Le seuil se trouve donc en SQL brut, où vous pouvez l'ajuster sans modifier la requête. La notation s'effectue par ligne, ce qui signifie que la même grille d'évaluation est appliquée à chaque composant généré par une campagne, et pas seulement aux deux que vous avez créés ici.

11. Étapes suivantes

Créer des évaluations

Les évaluations agentiques sont des tests automatisés utilisés pour mesurer la capacité des agents IA autonomes à raisonner, à planifier, à utiliser des outils et à effectuer des workflows en plusieurs étapes. Ils sont semblables aux tests unitaires pour les agents, mais tiennent compte de leur nature non déterministe.

L'un de nos agents inclut un Human in the loop, ce qui signifie qu'il attend une entrée. Vous devrez autoriser Antigravity à modifier le code actuel afin que ce comportement n'entrave pas les évaluations automatiques.

L'un des meilleurs moyens de commencer est de poser des questions à la CLI Antigravity.

Saisissez la commande suivante dans Cloud Shell pour démarrer l'interface CLI :

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

Voici un prompt de départ :

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.

Vous trouverez également un exemple d'évaluations et des instructions pour les exécuter dans le dépôt cloné.

12. Effectuer un nettoyage

Pour éviter que les ressources utilisées dans cet atelier de programmation ne soient facturées sur votre compte Google Cloud, supprimez le projet (recommandé) ou supprimez les ressources individuelles créées pendant l'atelier de programmation :

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. Félicitations !

Dans cet atelier de programmation, vous avez créé un générateur de pitch de campagne multi-agents avec l'Agent Development Kit. Vous avez câblé trois agents dans un workflow de graphe déterministe, empaqueté les consignes relatives à la marque en tant que compétence d'agent qui se charge à la demande et fourni à un agent un outil de génération d'images qui enregistre sa sortie en tant qu'artefact. Vous avez ensuite divisé le Visual Director en son propre service A2A, l'avez réintégré dans le graphique en tant que nœud distant, l'avez déployé sur Cloud Run et avez appelé l'ensemble du workflow sur A2A à partir d'un coordinateur s'exécutant en local. Enfin, vous avez référencé les visuels clés générés à partir de BigQuery avec ObjectRef et utilisé AI.SCORE pour les évaluer par rapport à vos propres consignes relatives à la marque.