Crea un generatore di proposte agentico: orchestrazione, valutazione e analisi con Google Cloud

1. Introduzione

banner della masterclass per gli agenti

Scopri come creare un motore multimodale multi-agente che prende una bozza di argomento grezza e orchestra la creazione di asset social altamente coinvolgenti. Utilizzando Agent Development Kit (ADK) e il protocollo Agent2Agent (A2A) di Google, comporrai micro-agenti autonomi, impacchetterai le regole del brand come competenza riutilizzabile dell'agente, eseguirai il deployment di un servizio di agente su Cloud Run e assegnerai un punteggio alle immagini chiave generate per la conformità al brand con le funzioni AI di BigQuery.

In questo lab proverai a:

In questo codelab imparerai a:

  • Orchestra più agenti ADK con un workflow grafico deterministico.
  • Raggruppa le linee guida per il brand come skill dell'agente che viene caricata solo quando necessario.
  • Fornisci a un agente uno strumento di generazione di immagini che salva l'output come artefatto.
  • Esporre un agente come servizio A2A e chiamarlo da un altro agente come nodo remoto.
  • Esegui il deployment di Visual Director su Cloud Run e chiama il flusso di lavoro tramite A2A.
  • Assegna un punteggio alle immagini chiave generate per la compatibilità con il brand in BigQuery con le funzioni AI.

Che cosa ti serve

  • Un browser web (ad esempio Chrome).
  • Un account Google Cloud e un progetto Google Cloud con la fatturazione abilitata.

2. Prima di iniziare

Crea un progetto Google Cloud

  1. Nella console Google Cloud, nella pagina di selezione del progetto, seleziona o crea un progetto Google Cloud.
  2. Verifica che la fatturazione sia attivata per il tuo progetto Cloud. Scopri come verificare se la fatturazione è abilitata per un progetto.

Avvia Cloud Shell

Utilizzerai Google Cloud Shell per scaricare il codice, eseguire gli script di configurazione e implementare l'applicazione.

  1. Apri Google Cloud Shell nel browser (o fai clic su Attiva Cloud Shell nella parte superiore della console Google Cloud):
    https://shell.cloud.google.com/
    
    Fai clic su Continua se vengono visualizzati ulteriori avvisi.Attendi che il terminale e l'editor siano completamente caricati. L'operazione può richiedere alcuni minuti.
  2. Configura l'ID progetto Google Cloud e la regione (ad esempio us-central1) nel tuo ambiente incollando questo nel terminale (sostituisci e con i tuoi valori):
    export PROJECT_ID="<YOUR_PROJECT_ID>"
    export REGION="<YOUR_REGION>"
    gcloud config set project $PROJECT_ID
    
    Incolla nel terminaleDovresti visualizzare un messaggio simile al seguente:
    Your active configuration is: [cloudshell-####]
    Updated property [core/project]
    
  3. Abilita le API Google Cloud richieste per il tuo progetto:
    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 il repository

Abbiamo un modello di codice per aiutarti a iniziare il codelab. È relativamente piccolo, ma il repository GitHub in cui si trova è piuttosto grande. Per velocizzare la clonazione e risparmiare spazio su disco, in genere utilizziamo git clone con l'opzione --filter=blob:none, git sparse-checkout e altri comandi.

Sembra molto complesso, ma Antigravity può fare tutto per noi.

Innanzitutto, configuriamo Antigravity CLI in modo che possa risolvere direttamente il nostro problema e avviamolo.

In Cloud Shell, esegui questo 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)

Il comando configura Antigravity CLI e la avvia nello stesso terminale. Quando esci da Antigravity CLI, torni al prompt di Cloud Shell.

Antigravity CLI

Nel repository è necessaria solo la cartella codelabs/bwg-multi-agent-pitch-generator. Chiediamo ad Antigravity di scaricare solo quella cartella e di averla localmente in ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/.

Assegna ad Antigravity CLI questa attività:

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.

Prompt di Antigravity CLI

Antigravity eseguirà l'attività. Ti chiederà le autorizzazioni per eseguire alcuni comandi. Una volta scaricato il codice, esci da Antigravity CLI eseguendo il comando /exit:

/exit

Esci da Antigravity CLI

Apri il workspace del repository

Ora apriamo lo spazio di lavoro del codice in Cloud Shell. Esegui questo comando nel terminale Cloud Shell:

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

Esegui lo script di configurazione

Per rendere questa esperienza più fluida, esegui lo script di configurazione dal repository. Configura le variabili di ambiente, installa agents-cli, crea il bucket Cloud Storage per gli artefatti, crea la connessione alla risorsa cloud BigQuery e concede al service account le autorizzazioni IAM richieste con il nuovo tentativo automatico.

Lo script di configurazione chiama anche setenv.sh, un piccolo helper che deriva le variabili necessarie a entrambi gli agenti (GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_REGION, GOOGLE_CLOUD_LOCATION e GOOGLE_GENAI_USE_VERTEXAI) da PROJECT_ID e REGION che hai esportato in precedenza. Salva anche questi due valori in un file .env nella tua home directory, in modo da poterli recuperare in un secondo momento.

  1. In Cloud Shell, rendi eseguibile lo script di configurazione ed eseguilo:
    chmod +x codelab-setup.sh setenv.sh
    ./codelab-setup.sh
    
  2. L'operazione richiederà circa un minuto. Dovresti visualizzare questo messaggio che indica l'operazione riuscita: "🎉 🦄 Script execution complete."operazione riuscita
  3. Lo script di configurazione è stato eseguito in una shell propria, quindi le sue variabili non hanno raggiunto il terminale. Caricali nella sessione:
    source ./setenv.sh
    
    Dovresti visualizzare il tuo progetto, la tua regione e la tua località.

3. Orchestrare gli agenti con un workflow grafico

In questa attività creerai il progetto del generatore di proposte e creerai un flusso di lavoro del grafico che esegue due agenti e combina il loro output.

Una proposta musicale deve avere un concept, ovvero l'idea e gli asset che la vendono. In questo caso d'uso, progetteremo agenti con responsabilità diverse nell'ambito del lavoro, dimostrando come è possibile separare attività complesse e delegarle a diversi agenti con responsabilità distinte, rendendole più facili da gestire.

La suddivisione di questo tipo di lavoro tra gli specialisti richiede un coordinamento per l'esecuzione di ogni agente. Puoi scegliere di lasciare che l'agente decida quando chiamare ogni subagente o applicare un approccio più deterministico. In questo codelab utilizzerai un approccio deterministico con un flusso di lavoro del grafico.

Crea la struttura del primo progetto di agente

  1. In Cloud Shell, crea il progetto incollando questo codice nel terminale:
    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
    
    Viene scritto un progetto completo e eseguibile: un agente, un Dockerfile, test, Terraform per un uso successivo e un file .env con il relativo modello .env.example. Per questo codelab sono importanti due parti:
    • pitch_generator/agent.py è l'agente e il file che riscrivi.
    • pitch_generator/ è il nome del pacchetto, impostato da --agent-directory.
    L'agente legge il progetto e la regione dall'ambiente che hai esportato con setenv.sh, quindi non devi configurare nulla qui.
  2. Installa le dipendenze:
    cd pitch-generator
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    
    Nota: lo scaffold collega anche il progetto per pubblicare A2A, il protocollo utilizzato più avanti in questo codelab. Per il momento, non modificare questi file.

Comprendere il grafico

ADK v2.x supporta i workflow grafici. Un grafico è composto da nodi, che sono le unità di lavoro, e da archi, che li collegano. Un nodo è in genere un agente, anche se funziona anche una semplice funzione Python. Un arco indica che quando un nodo termina, viene eseguito il successivo.

I bordi vengono scritti come un elenco e ADK determina l'ordine:

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

L'annidamento conferisce un significato al bordo:

Edge

Significato

(a, b, c)

Una catena. a, poi b, poi c, uno dopo l'altro

(a, (b, c))

Un fan-out. a, poi b e c contemporaneamente

((a, b), c)

A fan-in. c waits for both a and b

Per un fan-in, utilizzi un JoinNode, che attende ogni predecessore e passa i relativi output insieme.

In questa attività, creeremo il seguente flusso di lavoro:

grafico del workflow iniziale

Crea la pipeline

  1. Apri il file dell'agente incollando questo nel terminale:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
    Vedrai lo scaffold agent.py nell'editor di Cloud Shell.💡 Suggerimento: in qualsiasi momento, puoi selezionare il codice e utilizzare la lampadina per chiedere a Gemini di spiegarlo. Puoi anche chiedere a Antigravity CLI.Usa Gemini Code Assist
  2. Sostituisci l'intero file in pitch_generator/agent.py nell'editor con quanto segue:
    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 include una UI di sviluppo web integrata chiamata playground per l'ispezione, simile ad ADK. Per avviare il playground:
    agents-cli playground --port 8080
    
    Ti verrà chiesto se vuoi attivare la telemetria. Digita n e premi Invio.
  4. Fai clic su Anteprima web, l'icona a forma di occhio in alto a destra nel terminale, e scegli Anteprima sulla porta 8080.Fai clic su Anteprima web.
  5. Visualizzerai la seguente interfaccia per chattare con l'agente. Prova a inviare il prompt "Skateboard volanti per gatti". Visualizzerai una risposta come mostrato di seguito, in cui potrai anche esaminare il grafico del flusso di lavoro completo.workflow demoEsamina come è stata prodotta la risposta. Il playground mostra ogni passaggio eseguito dal grafico, in ordine: creative_director, poi copywriter, poi assemble, poi package. Questa sequenza è il grafico che hai creato e in questo modo puoi confermare rapidamente il grafico che hai appena creato.
  6. Torna al terminale e interrompi il processo agents-cli playground in esecuzione premendo Ctrl+C due volte.

4. Assegnare una competenza a un agente

In questa attività, creerai un secondo agente e gli assegnerai un insieme di linee guida per il brand impacchettate come skill.

La pipeline scrive le parole. Un'immagine ha bisogno di uno stile: una tavolozza, un modo di illuminare le cose, composizioni che usi e altre che non usi mai. Si tratta di una pagina di regole e una pagina di regole ha bisogno di un meccanismo migliore per essere gestita. Un modo per farlo è pacchettizzarlo come "skill dell'agente".

Una skill è una cartella contenente un file SKILL.md. La struttura della directory delle skill è simile all'esempio seguente (utilizzando visual_director, il prossimo agente che creerai):

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

Il punto di ingresso della skill dell'agente è il file SKILL.md, che include una sezione frontmatter YAML come quella mostrata di seguito:

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

Il frontmatter è l'etichetta. È tutto ciò che l'agente vede in primo piano, motivo per cui la descrizione deve indicare quando vale la pena caricare la skill. Il corpo è il contenuto e rimane fuori dal contesto finché l'agente non chiama load_skill.

Questa divisione è il punto. Il prompt rimane breve, l'agente estrae le regole solo quando ne ha bisogno e cambiare stile significa modificare un file anziché cercare in una stringa di istruzioni.

Inizieremo a creare il secondo agente, visual-director, utilizzando le skill dell'agente come mostrato nel diagramma seguente:

dotare il secondo agente della skill

Struttura del Visual Director

Creerai un agente specializzato per gestire il componente visivo, visual-director. Si tratta di un progetto separato perché in seguito diventa un servizio autonomo per dimostrare come più agenti comunicano tra loro utilizzando il protocollo A2A. Avrai due progetti affiancati:

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. Crea il progetto e installa le relative dipendenze incollando questo nel terminale:
    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
    

Scrivere le linee guida per il brand

  1. Crea la cartella delle competenze e apri il file:
    mkdir -p visual_director/skills/brand-guidelines
    cloudshell edit visual_director/skills/brand-guidelines/SKILL.md
    
  2. Incolla i seguenti contenuti nel file SKILL.md aperto nell'editor e salvalo:Competenza
    ---
    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.
    

Aggiorna visual_director/agent.py

Aggiorna il file agent.py e configuralo in modo che sia dotato della skill definita in precedenza.

  1. Apri il file dell'agente:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Sostituisci l'intero contenuto del file visual_director/agent.py con il seguente codice:
    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'agente visual-director è ora configurato correttamente con una skill dell'agente. Prima di testarlo, doterai questo agente di uno strumento per generare asset immagine come una delle sue funzionalità chiave.

5. Dotare un agente di uno strumento di generazione di immagini

In questa attività, fornisci al Visual Director uno strumento che esegue il rendering della direzione artistica come immagine e la salva come artefatto. Un artefatto è un elemento di dati binari o di contenuti di file con nome e versione che un agente AI crea, modifica o utilizza durante un'attività.

lo strumento e l'artefatto che scrive

Implementa e aggiungi lo strumento all'agente

  1. Apri il file dell'agente:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Copia il seguente strumento generate_key_visual prima della definizione di 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. Aggiungi generate_key_visual sotto SkillToolset per dotare l'agente dello strumento:
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
          generate_key_visual,
       ],
    
  4. agents-cli include una UI di sviluppo web integrata chiamata playground per l'ispezione, simile ad ADK. Per avviare il playground:
    agents-cli playground --port 8080
    
  5. Fai clic su Anteprima web, l'icona a forma di occhio in alto a destra nel terminale, e scegli Anteprima sulla porta 8080.Fai clic su Anteprima web.
  6. Visualizzerai la seguente interfaccia per chattare con l'agente. Prova a inviare il prompt "Nove vite. Gravità zero."workflow demoDalle interazioni puoi ispezionare il modo in cui l'agente visual_director esegue load_skill("brand-guidelines") per leggere la skill delle linee guida per il brand e poi esegue generate_key_visual per iniziare a generare l'immagine. L'immagine risultante viene archiviata come artefatto, visualizzata anche nell'interfaccia utente e può essere facilmente esaminata.
  7. Torna al terminale e interrompi il processo agents-cli playground in esecuzione premendo Ctrl+C due volte.

6. Architettura multi-agente con protocollo A2A

In questa attività esporrai Visual Director come servizio che altri agenti possono chiamare e lo configurerai in modo che risponda con l'immagine generata.

Agent2Agent (A2A) è un protocollo aperto che consente a un agente di chiamarne un altro su una rete. L'elemento centrale è la scheda dell'agente, un documento JSON che l'agente pubblica su se stesso tramite un URL noto, fornendo il proprio nome, la descrizione e le funzionalità. Il chiamante recupera la scheda e sa come parlare con l'agente che la gestisce, senza codice sorgente, dipendenze o lingua condivisi.

Protocollo A2A

agents-cli ha già configurato questa opzione per impostazione predefinita. Lo scaffold generato visual_director/app_utils/a2a.py, che crea la scheda dall'agente e monta le route, e visual_director/fast_api_app.py, che le pubblica. È sufficiente una piccola modifica per consentire l'invio degli artefatti al client.

L'architettura avrà l'aspetto mostrato di seguito. Tieni presente che l'agente non è ancora integrato nel nostro flusso di lavoro iniziale:

configura il server A2A

Lo scaffold del server agents-cli A2A

Il seguente codice e modulo vengono già forniti quando inizializziamo l'agente visual-director utilizzando agents-cli. Apri questi file incollando i seguenti comandi nel terminale:

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

visual_director/fast_api_app.py avvia l'agente e monta le route A2A in un percorso creato a partire dal nome dell'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 fa il resto in tre mosse.

Innanzitutto, crea la scheda dell'agente a partire dall'agente stesso. Il nome, la descrizione e ogni strumento diventano voci che un chiamante può leggere:

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

In secondo luogo, inserisce un executor tra il protocollo e l'agente. Le richieste A2A in entrata vengono convertite in esecuzioni ADK e i risultati vengono riconvertiti:

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

Terzo, monta due intent: uno per la carta, uno per accettare le chiamate.

Itinerario

Finalità

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

La scheda. Chi chiama legge questo messaggio per scoprire cosa può fare l'agente

/a2a/visual_director

L'endpoint JSON-RPC. Dove vengono inviate le richieste effettive

Consenti agli artefatti di attraversare il confine

Per impostazione predefinita, una risposta da app ad app contiene testo. L'immagine è un artefatto e rimane nell'archivio dell'agente di pubblicazione, a meno che tu non la configuri in modo che venga inclusa.

  1. Apri il file A2A generato:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    cloudshell edit visual_director/app_utils/a2a.py
    
  2. Trova l'importazione AgentCardBuilder e aggiungi le importazioni A2aAgentExecutorConfig e include_artifacts_in_a2a_event_interceptor sotto:
    ## ...
    ## 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. Trova la riga agent_executor=A2aAgentExecutor(runner=runner), e sostituiscila con la seguente (aggiungendo A2aAgentExecutorConfig):
             agent_executor=A2aAgentExecutor(
                runner=runner,
                force_new_version=True,
                config=A2aAgentExecutorConfig(
                   execute_interceptors=[include_artifacts_in_a2a_event_interceptor]
                ),
             ),
    

Avvia il servizio e leggi la tessera A2A pubblicata

  1. Avvia il server in background. Devi dichiarare APP_URL, in quanto questo è l'indirizzo che la carta pubblicizza come proprio. Incolla questo codice nel terminale:
    APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 &
    
  2. Attendi qualche secondo, poi verifica che sia in esecuzione:
    sleep 10
    tail -2 /tmp/visual-director.log
    
    Dovresti vedere:
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit)
    
  3. Leggi la scheda che l'agente pubblica ora:
    curl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.tool
    
    La scheda elenca il nome, la descrizione, l'URL e le competenze dell'agente, tra cui generate_key_visual. Viene generato direttamente dalla definizione dell'agente nel codice.
    {
       "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"
    }
    
    Ora puoi incorporare l'agente visual-director nel flusso di lavoro principale pitch-generator.

7. Incorpora l'agente remoto A2A nel workflow

In questa attività, aggiungerai l'agente remoto al grafico come terzo ramo.

Un agente remoto è racchiuso tra RemoteA2aAgent e inserito nel grafico come qualsiasi altro nodo. I bordi mantengono la stessa forma e il coordinatore non sa quali dei suoi nodi sono locali.

Punta il coordinatore al servizio e aggiorna il flusso di lavoro del grafico

  1. Apri il file dell'agente:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
  2. Aggiungi queste importazioni all'inizio del file, insieme a quelle esistenti:
    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. Aggiungi la seguente definizione di RemoteA2aAgent sopra la dichiarazione di 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. Sovrascrivi o sostituisci la funzione Python del nodo package con la seguente definizione:
    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. Aggiungi un Human Approval Gate prima che venga eseguita la generazione downstream. In pitch_generator/agent.py, definisci i nodi di approvazione sopra 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. Infine, aggiorna le importazioni nella parte superiore in modo da includere RequestInput e node da google.adk.events e google.adk.workflow:
    from google.adk.events import Event, RequestInput
    from google.adk.workflow import JoinNode, Workflow, node
    
    e modifica il workflow root_agent edges in modo che venga indirizzato tramite user_approval:
        edges=[
            ("START", creative_director),
            (creative_director, user_approval, (copywriter, visual_director)),
            ((creative_director, copywriter, visual_director), assemble),
            (assemble, package),
        ],
    
    Ci sono diversi dettagli da notare:
    • Approvazione human-in-the-loop (HITL): approve_concept produce un RequestInput. Quando il workflow raggiunge questo nodo, l'esecuzione si interrompe e viene inviato un interrupt al chiamante. I rami downstream (copywriter e visual_director) non verranno eseguiti finché l'approvazione dell'utente non sarà confermata.
    • visual_director viene visualizzato nei bordi esattamente come copywriter, anche se uno è una chiamata di funzione e l'altro si trova su una rete.
    • L'immagine restituita viene letta dagli eventi della sessione anziché da node_input, perché al momento solo il testo viene promosso all'output di un nodo. L'immagine ha attraversato il confine tramite l'intercettore A2A, ma è arrivata allegata agli eventi dell'agente remoto, quindi la funzione helper _key_visual la recupera da lì.

Testare l'integrazione

Ora testerai che Visual Director (in esecuzione in background) e Pitch Generator possano comunicare tramite il protocollo A2A.

  1. Verifica che Visual Director sia ancora in esecuzione in background:
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
    Qui dovresti ottenere un risultato visual director: 200.Se ottieni un altro stato, esegui questo comando per riavviare:
    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. Avvia l'anteprima:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator
    agents-cli playground --port 8080
    
  3. Fai clic su Anteprima web, l'icona a forma di occhio in alto a destra nel terminale, e scegli Anteprima sulla porta 8080.Fai clic su Anteprima web.
  4. Vedrai l'interfaccia per chattare con l'agente. Prova a inviare il prompt "Skateboard volanti per gatti".workflow demoworkflow demoVedrai l'interazione completa in cui viene avviato il flusso di lavoro e viene eseguito ogni agente. Dopo che creative_director ha creato la bozza del concept, il flusso di lavoro si interrompe al gate di approvazione e ti chiede di confermare: "Approva il concept della campagna (sì/no)." Inserisci "yes" nel campo di immissione per procedere.Una volta approvato, il grafico si apre a ventaglio: puoi vedere quando l'agente visual-director inizia a caricare la skill e, a metà della generazione di immagini, l'agente copywriter termina di generare la didascalia per i social media, il che dimostra che entrambi gli agenti vengono eseguiti in parallelo senza attendere l'uno l'altro. Al termine di entrambe, il nodo assemble unisce i risultati al concept approvato generato da creative_director, finalizzando la pubblicazione dei contenuti della proposta nella funzione package.i due servizi, collegati tramite A2A

8. Rendere persistenti le risorse visive chiave in Cloud Storage

Finora ogni elemento visivo chiave è stato memorizzato. Nel momento in cui il processo si interrompe, l'immagine scompare, il che va bene per un playground, ma è inutile per una campagna. In questa attività, assegni al flusso di lavoro un bucket in cui scrivere e crei un set di dati BigQuery che utilizzerai nell'ultima attività.

L'ADK decide dove vanno gli artefatti all'avvio. Il app_utils/services.py generato legge una singola variabile di ambiente:

def get_artifact_service():
    """Process-wide artifact service: GCS when a bucket is set, else in-memory."""
    if bucket := os.environ.get("LOGS_BUCKET_NAME"):
        return GcsArtifactService(bucket_name=bucket)
    return InMemoryArtifactService()

Se imposti LOGS_BUCKET_NAME, ogni chiamata save_artifact verrà inserita in Cloud Storage.

integra il servizio artefatti con il bucket GCS

Crea il set di dati BigQuery

  1. In Cloud Shell, esegui questo comando per creare un set di dati BigQuery:
    bq mk --dataset --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> bwg
    
  2. Punta l'archivio degli artefatti del coordinatore al bucket:
    export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"
    
    GcsArtifactService accetta un nome del bucket semplice, senza il prefisso gs://. Come le altre variabili, questa si trova nella shell e l'attività di deployment la trasmette a Cloud Run.
  3. Controlla i contenuti del bucket. In una nuova scheda del browser, apri il seguente URL:
    https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
    
    Fai clic sul bucket. A seconda dei test precedenti, potrebbe contenere immagini generate in precedenza.

9. Esegui il deployment dell'agente Visual Director su Cloud Run

Finora tutto è stato eseguito su una sola macchina, con il Visual Director raggiungibile all'indirizzo localhost:8801. In questa attività esegui il deployment dell'agente Visual Director come servizio Cloud Run autonomo e consenti al generatore di pitch di comunicare con lui sulla rete utilizzando il protocollo Agent2Agent (A2A).

Visual Director riceve un URL https:// reale, quindi il _cloud_run_client che hai scritto in precedenza inizia a firmare le richieste anziché restituire None. Inoltre, Cloud Run esegue il deployment privato per impostazione predefinita.

entrambi gli agenti come servizi Cloud Run

Implementare Visual Director

  1. Arresta il servizio in background locale e il playground, se sono ancora in esecuzione:
    fuser -k 8801/tcp 2>/dev/null || true
    fuser -k 8000/tcp 2>/dev/null || true
    
  2. Esegui il deployment dell'agente. Viene creato un container e viene eseguito il push.⏰ L'operazione richiede alcuni minuti.
    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"
    

Esegui il generatore di pitch che utilizza il Visual Director di cui è stato eseguito il deployment in Cloud Run

  1. Leggi l'URL del regista visivo ed esegui il generatore di pitch che lo utilizza:
    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 &
    

Chiama il workflow di cui è stato eseguito il deployment

Entrambi gli agenti supportano il protocollo A2A. Il repository include un piccolo client per chiamare Pitch Generator tramite A2A.

Deve essere impostato su PITCH_GENERATOR_URL l'URL del generatore di proposte che viene eseguito localmente. Nel frattempo, il Visual Director viene eseguito in Cloud Run e il Pitch Generator comunica con lui tramite A2A.

  1. Copia il client nel tuo progetto ed eseguilo:
    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
    
    Poiché il flusso di lavoro contiene un cancello di approvazione umana, lo script si interrompe dopo aver generato la bozza del concept:
    === HUMAN APPROVAL REQUIRED ===
    
    [DRAFT CONCEPT]
    Concept Line: "Gravity is a Suggestion."
    ...
    
    Please approve the campaign concept (yes/no):
    
    Digita yes e premi Invio per approvare e continuare il flusso di lavoro. Puoi anche passare -y o --auto-approve per l'approvazione automatica.
  2. Apri call_agent.py ed esamina il modo in cui interagisce con l'agente utilizzando a2a-sdk:
    • ClientFactory crea un targeting Client A2A ufficiale https://SERVICE_URL/a2a/pitch_generator.
    • generate_pitch_async invia un SendMessageRequest iniziale. Quando la risposta viene restituita con TaskState.TASK_STATE_INPUT_REQUIRED, rileva la parte adk_request_input, mostra la bozza del concetto, raccoglie la tua approvazione e invia una richiesta di riepilogo con la tua risposta all'attività.
    • Il ciclo di analisi legge le sezioni CONCEPT, COPY e ART DIRECTION dagli artefatti dell'attività completata e salva l'immagine binaria in key_visual.png.
    Dovresti vedere le tre sezioni di testo stampate e l'immagine salvata in key_visual.png.
  3. Carica l'immagine chiave nel bucket, in un prefisso a cui puoi indirizzare BigQuery:
    gcloud storage cp ./key_visual.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png
    
  4. Genera una seconda proposta in modo da avere qualcosa da confrontare e caricala:
    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. Assegna un punteggio alle immagini chiave per l'idoneità al brand in BigQuery

Ora hai le immagini in un bucket e le linee guida per il brand che avrebbero dovuto modellarle. In questa attività, unisci le due cose: fai riferimento alle immagini di BigQuery, poi chiedi a un modello Gemini di valutarle in base alle linee guida, senza spostare un singolo byte da Cloud Storage.

Due funzionalità di BigQuery svolgono il lavoro:

  • ObjectRef è un valore che punta a un oggetto Cloud Storage. OBJ.MAKE_REF ne crea uno da un URI più una connessione che concede a BigQuery l'accesso delegato al bucket. pitch-connection creato dallo script di configurazione è la connessione e contiene già Storage Object Viewer e Utente di Agent Platform.
  • AI.SCORE prende un prompt e restituisce un FLOAT64. Il prompt può combinare stringhe con valori ObjectRef, in modo che il modello veda insieme la rubrica e l'immagine.

BigQuery legge il bucket

Crea una tabella delle immagini chiave

  1. Apri BigQuery Studio in una nuova scheda del browser:
    https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID>
    
  2. Fai clic su Crea nuova query (l'icona +), poi incolla ed Esegui quanto segue (sostituisci e con il tuo ID progetto e la tua regione). Crea una riga per ogni elemento visivo chiave, abbinando il concept della campagna a un riferimento all'immagine:
    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 crea il riferimento dall'URI e dalla connessione. OBJ.FETCH_METADATA, quindi contatta Cloud Storage e compila le dimensioni, il tipo di contenuto e la generazione dell'oggetto. La seconda chiamata è anche il modo più rapido per dimostrare che la connessione funziona: se le autorizzazioni sono errate, il campo details restituisce l'errore anziché i metadati.
  3. Controlla i contenuti che hai archiviato. Sostituisci la query precedente con questa ed eseguila:
    SELECT campaign, concept, key_visual.uri, key_visual.details
    FROM bwg.key_visuals;
    
    La tabella contiene un puntatore e i relativi metadati, non l'immagine. I byte non hanno mai lasciato il bucket.

Valuta ogni immagine in base alle linee guida per il brand

Al direttore della fotografia è stato detto di rispettare una tavolozza, una regola di illuminazione e una regola di composizione. Se sia effettivamente così, è una questione di giudizio, ed è a questo che serve AI.SCORE.

  1. Esegui la seguente query (sostituendo con la tua regione). Il prompt è una tupla: la rubrica, il concept della campagna e il ObjectRef per l'immagine:
    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. Trasforma il punteggio in un verdetto su cui il tuo team può intervenire:
    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 restituisce un FLOAT64 e nient'altro, quindi la soglia si trova in SQL semplice, dove puoi modificarla senza toccare il prompt. Il punteggio viene calcolato per riga, il che significa che la stessa rubrica viene applicata a ogni asset prodotto da una campagna, non solo ai due che hai generato qui.

11. Passaggi successivi

Crea valutazioni

Le valutazioni agentiche o "evals" sono test automatizzati utilizzati per misurare il livello di ragionamento, pianificazione, utilizzo degli strumenti e completamento del workflow in più passaggi degli agenti AI autonomi. Sono simili ai test unitari per gli agenti, ma tengono conto della loro natura non deterministica.

Uno dei nostri agenti include un Human in the loop, il che significa che attende l'input. Devi consentire ad Antigravity di apportare modifiche al codice attuale in modo che questo comportamento non ostacoli le valutazioni automatiche.

Uno dei modi migliori per iniziare è chiedere ad Antigravity CLI.

Digita quanto segue in Cloud Shell per avviare la CLI:

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

Ecco un prompt iniziale:

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.

Puoi anche trovare un esempio di valutazioni e istruzioni per eseguirle nel repository clonato.

12. Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo codelab, elimina il progetto (opzione consigliata) o le singole risorse create durante il 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. Complimenti!

In questo codelab, hai creato un generatore di proposte di campagne multi-agente con Agent Development Kit. Hai collegato tre agenti a un flusso di lavoro del grafico deterministico, hai incluso le linee guida per il brand come skill dell'agente che viene caricata su richiesta e hai fornito a un agente uno strumento di generazione di immagini che salva l'output come artefatto. Poi hai suddiviso Visual Director nel suo servizio A2A, lo hai incorporato di nuovo nel grafico come nodo remoto, lo hai implementato in Cloud Run e hai chiamato l'intero flusso di lavoro tramite A2A da un coordinatore in esecuzione in locale. Infine, hai fatto riferimento alle immagini chiave generate da BigQuery con ObjectRef e hai utilizzato AI.SCORE per valutarle in base alle linee guida per il brand.