Tworzenie generatora prezentacji opartego na agentach: orkiestracja, ocena i analiza w Google Cloud

1. Wprowadzenie

baner lekcji mistrzowskich dla agentów

Dowiedz się, jak zbudować wielomodowy silnik z wieloma agentami, który na podstawie krótkiego opisu tematu koordynuje tworzenie angażujących zasobów do mediów społecznościowych. Za pomocą pakietu Agent Development Kit (ADK) od Google i protokołu Agent2Agent (A2A) utworzysz autonomiczne mikroagenty, spakujesz reguły marki jako umiejętność agenta wielokrotnego użytku, wdrożysz usługę agenta w Cloud Run i ocenisz wygenerowane kluczowe elementy wizualne pod kątem zgodności z marką za pomocą funkcji AI BigQuery.

Jakie zadania wykonasz

Z tego modułu dowiesz się, jak:

  • Aranżuj wielu agentów ADK za pomocą deterministycznego przepływu pracy w formie wykresu.
  • Zapakuj wytyczne dotyczące marki jako umiejętność agenta, która jest wczytywana tylko wtedy, gdy jest potrzebna.
  • Wyposaż agenta w narzędzie do generowania obrazów, które zapisuje wyniki jako artefakty.
  • Udostępnij agenta jako usługę A2A i wywołaj go z innego agenta jako węzeł zdalny.
  • Wdróż Visual Director w Cloud Run i wywołaj przepływ pracy za pomocą A2A.
  • Oceniaj wygenerowane kluczowe elementy wizualne pod kątem dopasowania do marki w BigQuery za pomocą funkcji AI.

Czego potrzebujesz

  • przeglądarka internetowa (np. Chrome);
  • Konto Google Cloud i projekt Google Cloud z włączonymi płatnościami.

2. Zanim zaczniesz

Tworzenie projektu Google Cloud

  1. W konsoli Google Cloud na stronie wyboru projektu wybierz lub utwórz projekt w chmurze.
  2. Sprawdź, czy w projekcie Cloud włączone są płatności. Dowiedz się, jak sprawdzić, czy w projekcie są włączone płatności.

Uruchamianie Cloud Shell

Do pobrania kodu, uruchomienia skryptów konfiguracji i wdrożenia aplikacji użyjesz Google Cloud Shell.

  1. Otwórz Google Cloud Shell w przeglądarce (lub kliknij Aktywuj Cloud Shell u góry konsoli Google Cloud):
    https://shell.cloud.google.com/
    
     Jeśli pojawią się dodatkowe powiadomienia, kliknij Dalej.Poczekaj, aż terminal i edytor wczytają się w całości. Może to potrwać minutę lub dwie.
  2. Skonfiguruj identyfikator projektu Google Cloud i region (np. us-central1) w swoim środowisku, wklejając to w terminalu (zastąp i swoimi wartościami):
    export PROJECT_ID="<YOUR_PROJECT_ID>"
    export REGION="<YOUR_REGION>"
    gcloud config set project $PROJECT_ID
    
    Wklej w terminaluPowinien wyświetlić się komunikat podobny do tego:
    Your active configuration is: [cloudshell-####]
    Updated property [core/project]
    
  3. Włącz wymagane interfejsy Google Cloud API w projekcie:
    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
    

Klonowanie repozytorium

Aby ułatwić Ci rozpoczęcie pracy z tym kursem, przygotowaliśmy szablon kodu. Jest on stosunkowo mały, ale repozytorium GitHub, w którym się znajduje, jest dość duże. Aby przyspieszyć klonowanie i zaoszczędzić miejsce na dysku, zwykle używamy poleceń git clone z opcją --filter=blob:none i git sparse-checkout oraz kilku innych poleceń.

Brzmi to bardzo skomplikowanie, ale Antigravity może to wszystko zrobić za nas.

Najpierw skonfigurujemy interfejs Antigravity CLI, aby mógł od razu rozwiązać nasz problem, a następnie go uruchomimy.

W Cloud Shell uruchom to polecenie:

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)

Polecenie konfiguruje interfejs Antigravity CLI i uruchamia go w tym samym terminalu. Gdy zamkniesz Antigravity CLI, wrócisz do prompta Cloud Shell.

Antigravity CLI

W repozytorium potrzebujemy tylko folderu codelabs/bwg-multi-agent-pitch-generator. Poprośmy Antigravity o pobranie tylko tego folderu i umieszczenie go lokalnie w ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/.

Powierz Antigravity CLI to zadanie:

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 Antigravity CLI

Antigravity wykona zadanie. Poprosi Cię o uprawnienia do uruchomienia kilku poleceń. Po pobraniu kodu zamknij interfejs wiersza poleceń Antigravity, wykonując polecenie /exit:

/exit

Zamykanie interfejsu Antigravity CLI

Otwieranie obszaru roboczego repozytorium

Teraz otwórz obszar roboczy kodu w Cloud Shell. Uruchom to polecenie w terminalu Cloud Shell:

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

Uruchamianie skryptu konfiguracji

Aby ułatwić sobie to zadanie, uruchom skrypt konfiguracji z repozytorium. Konfiguruje zmienne środowiskowe, instaluje agents-cli, tworzy zasobnik Cloud Storage na potrzeby artefaktów, tworzy połączenie z zasobem Cloud BigQuery i przyznaje wymagane uprawnienia IAM kontu usługi z automatyczną ponowną próbą.

Skrypt konfiguracji wywołuje też setenv.sh, czyli mały program pomocniczy, który na podstawie wyeksportowanych powyżej zmiennych PROJECT_ID i REGION wyznacza zmienne potrzebne obu agentom: GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_REGION, GOOGLE_CLOUD_LOCATION i GOOGLE_GENAI_USE_VERTEXAI. Zapisuje też te 2 wartości w pliku .env w katalogu głównym, aby można było je później odzyskać.

  1. W Cloud Shell ustaw skrypt konfiguracji jako wykonywalny i uruchom go:
    chmod +x codelab-setup.sh setenv.sh
    ./codelab-setup.sh
    
  2. Potrwa to około minuty. Powinien wyświetlić się komunikat o powodzeniu: „🎉 🦄 Wykonanie skryptu zakończone”.sukces
  3. Skrypt konfiguracji został uruchomiony w osobnej powłoce, więc jego zmienne nie dotarły do terminala. Wczytaj je do sesji:
    source ./setenv.sh
    
    Powinny się wyświetlić informacje o projekcie, regionie i lokalizacji.

3. Aranżowanie agentów za pomocą przepływu pracy w formie grafu

W tym zadaniu utworzysz projekt generatora propozycji i zbudujesz przepływ pracy w postaci wykresu, który uruchamia 2 agenty i łączy ich dane wyjściowe.

Prezentacja musi zawierać koncepcję, czyli pomysł i elementy, które go sprzedają. W tym przypadku zaprojektujemy agentów, którzy będą mieli różne obowiązki w zakresie pracy. Pokażemy, jak można rozdzielić złożone zadania i przekazać je różnym agentom o odmiennych obowiązkach, co ułatwi zarządzanie nimi.

Podział tego rodzaju pracy między specjalistów wymaga koordynacji, gdy każdy agent jest uruchamiany. Możesz pozwolić agentowi decydować, kiedy wywołać każdego subagenta, lub zastosować bardziej deterministyczne podejście. W tym ćwiczeniu użyjesz deterministycznego podejścia z przepływem pracy w postaci wykresu.

Utwórz szkielet pierwszego projektu agenta

  1. W Cloud Shell utwórz projekt, wklejając ten kod w terminalu:
    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
    
    Tworzy to kompletny, gotowy do uruchomienia projekt: agenta, Dockerfile, testy, Terraform do późniejszego wykorzystania i plik .env z jego szablonem .env.example. W tym ćwiczeniu ważne są 2 elementy:
    • pitch_generator/agent.py to agent i plik, który przepisujesz.
    • pitch_generator/ to nazwa pakietu ustawiona przez --agent-directory.
    Agent odczytuje projekt i region ze środowiska wyeksportowanego za pomocą setenv.sh, więc nie musisz niczego konfigurować.
  2. Zainstaluj zależności:
    cd pitch-generator
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    
    Uwaga: szkielet projektu jest też skonfigurowany do obsługi protokołu A2A, który będzie używany w dalszej części tego modułu. Na razie nie zmieniaj tych plików.

Odczytywanie wykresu

ADK w wersji 2.x obsługuje przepływy pracy z wykresami. Graf składa się z węzłów, czyli jednostek pracy, oraz krawędzi, które je łączą. Węzeł jest zwykle agentem, ale może też być zwykłą funkcją Pythona. Krawędź oznacza, że gdy jeden węzeł zakończy działanie, następny zostanie uruchomiony.

Krawędzie są zapisywane w postaci listy, a ADK ustala ich kolejność:

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

Zagnieżdżenie nadaje krawędzi znaczenie:

Edge

Znaczenie

(a, b, c)

Łańcuch: a, potem b, potem c, jeden po drugim

(a, (b, c))

Zwielokrotnienie wyjściowe: a, a następnie b i c w tym samym czasie

((a, b), c)

A fan-in. c czeka na a i b

W przypadku fan-in używasz JoinNode, który czeka na wszystkich poprzedników i przekazuje ich dane wyjściowe razem.

W tym zadaniu utworzymy ten przepływ pracy:

początkowy wykres przepływu pracy,

Tworzenie potoku

  1. Otwórz plik agenta, wklejając to w terminalu:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
    W edytorze Cloud Shell zobaczysz szkielet agent.py.💡 Wskazówka: w dowolnym momencie możesz wybrać kod i użyć ikony żarówki, aby poprosić Gemini o wyjaśnienie go. Możesz też zadać pytanie w interfejsie wiersza poleceń Antigravity CLI.Użyj Gemini Code Assist
  2. Zastąp cały plik w pitch_generator/agent.py edytorze tym kodem:
    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 zawiera wbudowany interfejs programistyczny do tworzenia aplikacji internetowych o nazwie playground, który umożliwia inspekcję podobną do ADK. Aby uruchomić środowisko testowe:
    agents-cli playground --port 8080
    
    Pojawi się pytanie, czy chcesz włączyć telemetrię. Wpisz n i naciśnij Enter.
  4. Kliknij Podgląd w przeglądarce (ikonę oka w prawym górnym rogu terminala) i wybierz Podejrzyj na porcie 8080.kliknąć podgląd w przeglądarce,
  5. Aby porozmawiać z pracownikiem obsługi klienta, zobaczysz ten interfejs. Wyślij prompta „Latające deskorolki dla kotów”. Zobaczysz odpowiedź jak poniżej. Możesz też sprawdzić pełny wykres przepływu pracy.przepływ pracy w wersji demonstracyjnej,Sprawdź, jak powstała ta odpowiedź. W piaskownicy widać każdy krok wykonany przez wykres w kolejności: creative_director, copywriter, assemble, package. Ta sekwencja to wykres, który został przez Ciebie utworzony. W ten sposób możesz szybko potwierdzić, że wykres został utworzony prawidłowo.
  6. Wróć do terminala i zatrzymaj działający proces agents-cli playground, naciskając dwukrotnie Ctrl+C.

4. Nadawanie agentowi umiejętności

W tym zadaniu utworzysz drugiego agenta i przypiszesz mu zestaw wytycznych dotyczących marki w postaci umiejętności.

Potok zapisuje słowa. Obraz potrzebuje stylu: palety kolorów, sposobu oświetlania, kompozycji, których używasz, i tych, których nigdy nie używasz. Jest to strona reguł, a strona reguł wymaga lepszego mechanizmu zarządzania. Jednym ze sposobów jest spakowanie go jako „umiejętności agenta”.

Umiejętność to folder zawierający plik SKILL.md. Struktura katalogu umiejętności wygląda tak jak w tym przykładzie (z użyciem visual_director, czyli kolejnego agenta, którego utworzysz):

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

Punktem wejścia umiejętności agenta jest plik SKILL.md, który zawiera sekcję z metadanymi YAML, taką jak pokazana poniżej:

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

Etykietą jest frontmatter. To wszystko, co agent widzi na początku, dlatego opis musi zawierać informację, kiedy warto wczytać umiejętność. Treść to zawartość, która pozostaje poza kontekstem, dopóki agent nie wywoła funkcji load_skill.

Ten podział jest kluczowy. Prompt pozostaje krótki, agent pobiera reguły tylko wtedy, gdy ich potrzebuje, a zmiana stylu oznacza edytowanie jednego pliku zamiast przeszukiwania ciągu instrukcji.

Zaczniemy tworzyć drugiego agenta, visual-director, wykorzystując umiejętności agenta zgodnie z diagramem poniżej:

wyposażenie drugiego agenta w umiejętność,

Wspieranie dyrektora wizualnego

Utworzysz wyspecjalizowanego agenta do obsługi komponentu wizualnego visual-director. Jest to osobny projekt, ponieważ później staje się odrębną usługą, która pokazuje, jak wielu agentów komunikuje się ze sobą za pomocą protokołu A2A. Obok siebie zobaczysz 2 projekty:

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. Utwórz projekt i zainstaluj jego zależności, wklejając ten kod w terminalu:
    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
    

Tworzenie wskazówek dotyczących marki

  1. Utwórz folder umiejętności i otwórz plik:
    mkdir -p visual_director/skills/brand-guidelines
    cloudshell edit visual_director/skills/brand-guidelines/SKILL.md
    
  2. Wklej do otwartego pliku SKILL.md w edytorze tę zawartość i zapisz go:Umiejętność
    ---
    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.
    

Zaktualizuj visual_director/agent.py

Zaktualizuj plik agent.py i skonfiguruj go tak, aby zawierał wcześniej zdefiniowaną umiejętność.

  1. Otwórz plik agenta:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Zastąp całą zawartość pliku visual_director/agent.py tym kodem:
    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",
    )
    
    Agent visual-director jest teraz prawidłowo skonfigurowany z umiejętnością agenta. Zanim przetestujesz tego agenta, wyposażysz go w narzędzie do generowania komponentów z obrazem jako jedną z jego kluczowych funkcji.

5. Wyposażanie agenta w narzędzie do generowania obrazów

W tym zadaniu udostępnisz Reżyserowi wizualnemu narzędzie, które renderuje kierunek artystyczny jako obraz i zapisuje go jako artefakt. Artefakt to nazwany, wersjonowany fragment danych binarnych lub zawartości pliku, który agent AI tworzy, modyfikuje lub wykorzystuje podczas wykonywania zadania.

narzędzie i utworzony przez nie artefakt,

Wdrażanie i dodawanie narzędzia do agenta

  1. Otwórz plik agenta:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. Skopiuj to narzędzie generate_key_visual przed definicją 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. Dodaj generate_key_visual poniżej SkillToolset, aby wyposażyć agenta w narzędzie:
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
          generate_key_visual,
       ],
    
  4. agents-cli zawiera wbudowany interfejs programistyczny do tworzenia aplikacji internetowych o nazwie playground, który umożliwia inspekcję podobną do ADK. Aby uruchomić środowisko testowe:
    agents-cli playground --port 8080
    
  5. Kliknij Podgląd w przeglądarce (ikonę oka w prawym górnym rogu terminala) i wybierz Podejrzyj na porcie 8080.kliknąć podgląd w przeglądarce,
  6. Aby porozmawiać z pracownikiem obsługi klienta, zobaczysz ten interfejs. Spróbuj wysłać prompt „Nine lives. Zero gravity”.przepływ pracy w wersji demonstracyjnej,Z interakcji możesz sprawdzić, jak visual_director agent wykonuje load_skill("brand-guidelines"), aby odczytać umiejętność wytycznych dotyczących marki, a następnie wykonuje generate_key_visual, aby rozpocząć generowanie obrazu. Wynikowy obraz jest przechowywany jako artefakt, który jest też wyświetlany w interfejsie i można go łatwo sprawdzić.
  7. Wróć do terminala i zatrzymaj działający proces agents-cli playground, naciskając dwukrotnie Ctrl+C.

6. Architektura systemu wieloagentowego z protokołem A2A

W tym zadaniu udostępnisz narzędzie Visual Director jako usługę, z której mogą korzystać inne agenty, i skonfigurujesz je tak, aby odpowiadało wygenerowanym obrazem.

Agent2Agent (A2A) to otwarty protokół umożliwiający jednemu agentowi wywoływanie innego w sieci. Centralnym elementem jest karta agenta, czyli dokument JSON, który agent publikuje o sobie pod znanym adresem URL, podając swoją nazwę, opis i możliwości. Dzwoniący pobiera kartę i wie, jak rozmawiać z agentem, który za nią stoi, bez wspólnego kodu źródłowego, zależności ani języka.

protokół A2A

agents-cli domyślnie już to skonfigurował. Wygenerowany szkielet visual_director/app_utils/a2a.py, który tworzy kartę na podstawie agenta i montuje trasy, oraz visual_director/fast_api_app.py, który je obsługuje. Wystarczy niewielka modyfikacja, aby umożliwić mu również wysyłanie artefaktów z powrotem do klienta.

Architektura będzie wyglądać tak, jak pokazano poniżej. Pamiętaj, że agent nie jest jeszcze zintegrowany z naszym początkowym przepływem pracy:

konfigurowanie serwera A2A,

szkielet serwera A2A agents-cli,

Podczas inicjowania agenta visual-director za pomocą agents-cli podany niżej kod i moduł są już dostępne. Otwórz te pliki, wklejając w terminalu te polecenia:

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

visual_director/fast_api_app.py uruchamia agenta i montuje trasy A2A w ścieżce utworzonej na podstawie nazwy aplikacji:

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 wykona resztę w 3 ruchach.

Najpierw tworzy kartę agenta na podstawie samego agenta. Nazwa, opis i każde narzędzie stają się wpisami, które rozmówca może odczytać:

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

Po drugie, umieszcza wykonawcę między protokołem a agentem. Przychodzące żądania A2A są tłumaczone na uruchomienia ADK, a wyniki są tłumaczone z powrotem:

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

Po trzecie, montuje 2 trasy: jedną do wyświetlania karty, a drugą do odbierania połączeń.

Trasa

Cel

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

kartę, Dzwoniący czyta to jako pierwsze, aby dowiedzieć się, co może zrobić agent.

/a2a/visual_director

Punkt końcowy RPC JSON. Gdzie trafiają rzeczywiste żądania

Zezwalanie na przekraczanie granicy przez artefakty

Domyślnie odpowiedź A2A zawiera tekst. Obraz jest artefaktem, a artefakt pozostaje w pamięci agenta obsługującego, chyba że skonfigurujesz jego uwzględnianie.

  1. Otwórz wygenerowany plik A2A:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    cloudshell edit visual_director/app_utils/a2a.py
    
  2. Znajdź import AgentCardBuilder i dodaj pod nim importy A2aAgentExecutorConfig i include_artifacts_in_a2a_event_interceptor:
    ## ...
    ## 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. Znajdź wiersz agent_executor=A2aAgentExecutor(runner=runner), i zastąp go tym wierszem (dodając A2aAgentExecutorConfig):
             agent_executor=A2aAgentExecutor(
                runner=runner,
                force_new_version=True,
                config=A2aAgentExecutorConfig(
                   execute_interceptors=[include_artifacts_in_a2a_event_interceptor]
                ),
             ),
    

Uruchom usługę i odczytaj opublikowaną kartę A2A

  1. Uruchom serwer w tle. Musisz zadeklarować APP_URL, ponieważ jest to adres, który jest reklamowany na karcie jako jej własny. Wklej to w terminalu:
    APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 &
    
  2. Zaczekaj kilka sekund, aż zacznie działać, a potem sprawdź, czy działa:
    sleep 10
    tail -2 /tmp/visual-director.log
    
    Powinno pojawić się:
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit)
    
  3. Odczytaj kartę, którą agent właśnie opublikował:
    curl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.tool
    
    Karta zawiera nazwę, opis, adres URL i umiejętności agenta, w tym generate_key_visual. Jest on generowany bezpośrednio z definicji agenta w kodzie.
    {
       "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"
    }
    
    Możesz teraz osadzić agenta visual-director w głównym przepływie pracy pitch-generator.

7. Osadź agenta zdalnego A2A w przepływie pracy

W tym zadaniu dodasz agenta zdalnego do wykresu jako trzecią gałąź.

Agent zdalny jest umieszczony w nawiasach RemoteA2aAgent i umieszczony na wykresie jak każdy inny węzeł. Krawędzie zachowują ten sam kształt, a koordynator nie wie, które z jego węzłów są lokalne.

Skieruj koordynatora na usługę i zaktualizuj przepływ pracy wykresu.

  1. Otwórz plik agenta:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
  2. Dodaj te instrukcje importu na początku pliku obok istniejących:
    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. Dodaj tę definicję RemoteA2aAgent nad deklaracją 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. Zastąp funkcję Pythona węzła package tą definicją:
    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. Dodaj bramkę zatwierdzania przez człowieka, zanim nastąpi dalsze generowanie. W sekcji pitch_generator/agent.py zdefiniuj węzły zatwierdzania powyżej 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. Na koniec zaktualizuj importy u góry, aby uwzględnić RequestInput i node z google.adk.events i google.adk.workflow:
    from google.adk.events import Event, RequestInput
    from google.adk.workflow import JoinNode, Workflow, node
    
    i zmodyfikuj root_agent przepływ pracy edges, aby kierować ruch przez user_approval:
        edges=[
            ("START", creative_director),
            (creative_director, user_approval, (copywriter, visual_director)),
            ((creative_director, copywriter, visual_director), assemble),
            (assemble, package),
        ],
    
    Warto zwrócić uwagę na kilka szczegółów:
    • Zatwierdzenie w ramach procesu z udziałem człowieka: approve_concept daje RequestInput. Gdy przepływ pracy dotrze do tego węzła, wykonanie zostanie wstrzymane i do rozmówcy zostanie wysłane przerwanie. Gałęzie podrzędne (copywriter i visual_director) nie będą działać, dopóki użytkownik nie potwierdzi zatwierdzenia.
    • visual_director pojawia się na krawędziach dokładnie tak samo jak copywriter, chociaż jedno jest wywołaniem funkcji, a drugie jest przesyłane przez sieć.
    • Zwrócony obraz jest odczytywany ze zdarzeń sesji, a nie z node_input, ponieważ obecnie tylko tekst jest promowany do danych wyjściowych węzła. Obraz przekroczył granicę za pomocą interfejsu A2A, ale dotarł do zdarzeń agenta zdalnego, więc funkcja pomocnicza _key_visual pobiera go stamtąd.

Testowanie integracji

Teraz sprawdzisz, czy Reżyser wizualny (działający w tle) i Generator propozycji mogą się komunikować za pomocą protokołu A2A.

  1. Sprawdź, czy Visual Director nadal działa w tle:
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
    Powinien się wyświetlić wynik visual director: 200.Jeśli pojawi się inny stan, uruchom ponownie usługę za pomocą tego polecenia:
    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. Rozpocznij podgląd:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator
    agents-cli playground --port 8080
    
  3. Kliknij Podgląd w przeglądarce (ikonę oka w prawym górnym rogu terminala) i wybierz Podejrzyj na porcie 8080.kliknąć podgląd w przeglądarce,
  4. Zobaczysz interfejs do czatowania z agentem. Spróbuj wysłać prompt „Latające deskorolki dla kotów”.przepływ pracy w wersji demonstracyjnej,przepływ pracy w wersji demonstracyjnej,Zobaczysz pełną interakcję, w której uruchamia się przepływ pracy i działa każdy agent. Gdy creative_director przygotuje koncepcję, przepływ pracy zatrzyma się na etapie zatwierdzania i poprosi Cię o potwierdzenie: „Zatwierdź koncepcję kampanii (tak/nie)”. Aby kontynuować, w polu do wprowadzania danych wpisz „yes”.Po zatwierdzeniu wykres się rozszerzy: zobaczysz, kiedy agent visual-director zaczyna wczytywać umiejętność, a w trakcie generowania obrazów agent copywriter kończy generowanie podpisu do mediów społecznościowych – co pokazuje, że oba agenty działają równolegle, nie czekając na siebie. Po zakończeniu obu procesów węzeł assemble łączy wyniki z zatwierdzoną koncepcją wygenerowaną przez węzeł creative_director, finalizując dostarczenie treści prezentacji w funkcji package.dwie usługi połączone za pomocą A2A,

8. Przechowywanie kluczowych elementów wizualnych w Cloud Storage

Do tej pory każdy kluczowy element wizualny był przechowywany w pamięci. W momencie zatrzymania procesu obraz znika, co jest w porządku w przypadku placu zabaw, ale bezużyteczne w kampanii. W tym zadaniu przypiszesz przepływowi pracy zasobnik, w którym będzie zapisywać dane, i utworzysz zbiór danych BigQuery, którego użyjesz w ostatnim zadaniu.

ADK decyduje, gdzie mają się znajdować artefakty podczas uruchamiania. Wygenerowany kod app_utils/services.py odczytuje jedną zmienną środowiskową:

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

Jeśli ustawisz LOGS_BUCKET_NAME, każde połączenie save_artifact będzie trafiać do Cloud Storage.

integrować usługę artefaktów z zasobnikiem GCS,

Tworzenie zbioru danych BigQuery

  1. Aby utworzyć zbiór danych BigQuery, uruchom w Cloud Shell to polecenie:
    bq mk --dataset --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> bwg
    
  2. Skieruj magazyn artefaktów koordynatora na zasobnik:
    export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"
    
    GcsArtifactService przyjmuje samą nazwę zasobnika bez prefiksu gs://. Podobnie jak inne zmienne, ta zmienna znajduje się w powłoce, a zadanie wdrażania przekazuje ją do Cloud Run.
  3. sprawdzić zawartość zasobnika, W nowej karcie przeglądarki otwórz ten adres URL:
    https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
    
     Kliknij zasobnik. W zależności od poprzednich testów może zawierać wygenerowane wcześniej obrazy.

9. Wdrażanie agenta Visual Director w Cloud Run

Wszystkie dotychczasowe działania były wykonywane na jednym komputerze, a Dyrektor wizualny był dostępny pod adresem localhost:8801. W tym zadaniu wdrożysz agenta Visual Director jako osobną usługę Cloud Run i umożliwisz generatorowi prezentacji komunikację z nim w sieci za pomocą protokołu Agent2Agent (A2A).

Reżyser wizualny otrzymuje prawdziwy adres URL https://, więc napisana wcześniej funkcja _cloud_run_client zaczyna podpisywać żądania zamiast zwracać wartość None. Cloud Run domyślnie wdraża usługi prywatne.

oba agenty jako usługi Cloud Run,

Wdrażanie narzędzia Visual Director

  1. Zatrzymaj lokalną usługę w tle i plac zabaw, jeśli nadal działają:
    fuser -k 8801/tcp 2>/dev/null || true
    fuser -k 8000/tcp 2>/dev/null || true
    
  2. Wdróż agenta. Spowoduje to utworzenie kontenera i jego przesłanie.⏰ Potrwa to kilka minut.
    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"
    

Uruchamianie generatora propozycji, który korzysta z usługi Visual Director wdrożonej w Cloud Run

  1. Odczytaj adres URL reżysera wizualnego i uruchom generator prezentacji, który go używa:
    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 &
    

Wywoływanie wdrożonego przepływu pracy

Oba agenty obsługują protokół A2A. W repozytorium znajduje się mały klient do wywoływania generatora propozycji za pomocą A2A.

Musi mieć ustawioną wartość PITCH_GENERATOR_URL na adres URL generatora propozycji, który jest uruchomiony lokalnie. Dyrektor wizualny działa w Cloud Run, a Generator propozycji komunikuje się z nim za pomocą protokołu A2A.

  1. Skopiuj klienta do projektu i uruchom go:
    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
    
    Ponieważ przepływ pracy zawiera bramkę zatwierdzania przez człowieka, skrypt wstrzymuje się po wygenerowaniu wersji roboczej:
    === HUMAN APPROVAL REQUIRED ===
    
    [DRAFT CONCEPT]
    Concept Line: "Gravity is a Suggestion."
    ...
    
    Please approve the campaign concept (yes/no):
    
     Wpisz yes i naciśnij Enter, aby zatwierdzić i kontynuować proces. (Możesz też przekazać wartość -y lub --auto-approve, aby automatycznie zatwierdzić prośbę).
  2. Otwórz plik call_agent.py i sprawdź, jak wchodzi on w interakcje z agentem za pomocą a2a-sdk:
    • ClientFactory tworzy oficjalne Client kierowanie https://SERVICE_URL/a2a/pitch_generator A2A.
    • generate_pitch_async wysyła początkowy SendMessageRequest. Gdy odpowiedź zostanie zwrócona z wartością TaskState.TASK_STATE_INPUT_REQUIRED, wykryje ona część adk_request_input, wyświetli wersję roboczą, zbierze Twoją zgodę i wyśle z Twoją odpowiedzią prośbę o podsumowanie z powrotem do zadania.
    • Pętla analizująca odczytuje sekcje CONCEPT, COPY i ART DIRECTION z artefaktów ukończonego zadania i zapisuje obraz binarny w key_visual.png.
    Powinny być widoczne 3 wydrukowane sekcje tekstu, a obraz powinien być zapisany w folderze key_visual.png.
  3. Prześlij obraz kluczowy do zasobnika pod prefiksem, na który możesz wskazać BigQuery:
    gcloud storage cp ./key_visual.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png
    
  4. Wygeneruj drugą propozycję, aby mieć coś do porównania, i też ją prześlij:
    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. Ocena kluczowych elementów wizualnych pod kątem dopasowania do marki w BigQuery

Masz już obrazy w zasobniku i wskazówki dotyczące marki, które miały na nie wpłynąć. W tym zadaniu połączysz te 2 elementy: odwołasz się do obrazów z BigQuery, a potem poprosisz model Gemini o ocenę każdego z nich na podstawie wytycznych bez przenoszenia ani jednego bajta z Cloud Storage.

Odpowiadają za to 2 funkcje BigQuery:

  • ObjectRef to wartość wskazująca obiekt Cloud Storage. OBJ.MAKE_REF tworzy go na podstawie identyfikatora URI i połączenia, które przyznaje BigQuery przekazywany dostęp do zasobnika. pitch-connection utworzone przez skrypt konfiguracyjny to połączenie, które ma już uprawnienia Przeglądający obiekty w Cloud Storage i Użytkownik platformy Agent Platform.
  • AI.SCORE przyjmuje prompta i zwraca FLOAT64. Prompt może łączyć ciągi znaków z wartościami ObjectRef, dzięki czemu model widzi rubrykę i obraz razem.

BigQuery odczytuje zasobnik

Tworzenie tabeli z najważniejszymi elementami wizualnymi

  1. Otwórz BigQuery Studio w nowej karcie przeglądarki:
    https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID>
    
  2. Kliknij Utwórz nowe zapytanie (ikonę +), a następnie wklej i uruchom poniższe zapytanie (zastępując symbole  i  identyfikatorem projektu i regionem). Tworzy ona 1 wiersz na każdy kluczowy element wizualny, łącząc koncepcję kampanii z odniesieniem do obrazu:
    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 tworzy odniesienie na podstawie identyfikatora URI i połączenia. OBJ.FETCH_METADATA następnie kontaktuje się z Cloud Storage i uzupełnia rozmiar, typ zawartości i generację obiektu. Drugie wywołanie jest też najszybszym sposobem na sprawdzenie, czy połączenie działa: jeśli uprawnienia są nieprawidłowe, pole details zwraca błąd zamiast metadanych.
  3. Sprawdź, co zostało zapisane. Zastąp poprzednie zapytanie tym i uruchom je:
    SELECT campaign, concept, key_visual.uri, key_visual.details
    FROM bwg.key_visuals;
    
    Tabela zawiera wskaźnik i jego metadane, a nie obraz. Bajty nigdy nie opuściły zasobnika.

Ocena każdego obrazu pod kątem zgodności ze wskazówkami dotyczącymi marki

Dyrektor wizualny otrzymał polecenie, aby przestrzegać palety kolorów, reguły oświetlenia i reguły kompozycji. To, czy tak się stało, zależy od oceny, i właśnie do tego służy AI.SCORE.

  1. Uruchom to zapytanie (zastępując nazwą swojego regionu). Podpowiedź to krotka: kryteria oceny, koncepcja kampanii i ObjectRef dla obrazu:
    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. Przekształć wynik w werdykt, na podstawie którego Twój zespół może podjąć działania:
    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 zwraca FLOAT64 i nic więcej, więc próg znajduje się w zwykłym SQL-u, w którym możesz go dostosować bez zmiany promptu. Ocena jest przeprowadzana wiersz po wierszu, co oznacza, że te same kryteria oceny są stosowane do każdego zasobu wygenerowanego przez kampanię, a nie tylko do tych dwóch, które zostały wygenerowane tutaj.

11. Dalsze kroki

Tworzenie ocen

Oceny agentów to zautomatyzowane testy służące do pomiaru tego, jak dobrze autonomiczne agenty AI rozumują, planują, korzystają z narzędzi i wykonują wieloetapowe przepływy pracy. Są one podobne do testów jednostkowych agentów, ale uwzględniają ich niedeterministyczny charakter.

Jeden z naszych agentów zawiera Human in the loop, co oznacza, że czeka na dane wejściowe. Musisz zezwolić firmie Antigravity na wprowadzenie zmian w obecnym kodzie, aby to zachowanie nie przeszkadzało w automatycznych ocenach.

Jednym z najlepszych sposobów na rozpoczęcie jest zadanie pytania interfejsowi Antigravity CLI.

Aby uruchomić interfejs CLI, wpisz w Cloud Shell to polecenie:

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

Oto prompt na początek:

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.

Przykłady ocen i instrukcje ich przeprowadzania znajdziesz też w sklonowanym repozytorium.

12. Czyszczenie danych

Aby uniknąć obciążenia konta Google Cloud opłatami za zasoby użyte w tym laboratorium, usuń projekt (zalecane) lub poszczególne zasoby utworzone w ramach laboratorium:

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

W tym ćwiczeniu utworzyliśmy generator propozycji kampanii z wieloma agentami za pomocą pakietu Agent Development Kit. Połączono 3 agenty z deterministycznym przepływem pracy w formie wykresu, spakowano wytyczne dotyczące marki jako umiejętność agenta, która jest wczytywana na żądanie, i udostępniono agentowi narzędzie do generowania obrazów, które zapisuje wyniki w postaci artefaktu. Następnie podzielono usługę Visual Director na osobną usługę A2A, osadzono ją z powrotem w grafie jako węzeł zdalny, wdrożono w Cloud Run i wywołano cały przepływ pracy za pomocą A2A z koordynatora działającego lokalnie. Na koniec odwołałeś się do wygenerowanych w BigQuery kluczowych elementów wizualnych za pomocą funkcji ObjectRef i użyłeś funkcji AI.SCORE, aby ocenić je pod kątem własnych wytycznych dotyczących marki.