1. מבוא

איך יוצרים מנוע מרובה סוכנים (multi-agent) עם יכולות multimodal, שמקבל תדרוך בנושא מסוים ומארגן את יצירת הנכסים לרשתות החברתיות בצורה מושכת במיוחד. באמצעות הערכה לפיתוח סוכנים (ADK) של Google ופרוטוקול Agent2Agent (A2A), תיצרו מיקרו-סוכנים אוטונומיים, תאגדו כללי מיתוג ככישורי סוכנים לשימוש חוזר, תפרסו שירות סוכנים ב-Cloud Run ותדרגו את הנכסים החזותיים שנוצרו בהתאם לתאימות למותג באמצעות פונקציות AI של BigQuery.
הפעולות שתבצעו:
ב-Codelab הזה תלמדו איך לבצע את המשימות הבאות:
- תזמור של כמה סוכני ADK באמצעות תהליך עבודה דטרמיניסטי של גרף.
- אפשר לארוז את הנחיות המותג ככישורים של סוכן שנטענים רק כשצריך.
- מציידים סוכן בכלי ליצירת תמונות ששומר את הפלט שלו כארטיפקט.
- לחשוף סוכן כשירות A2A ולקרוא לו מסוכן אחר כצומת מרוחק.
- פורסים את Visual Director ב-Cloud Run ומפעילים את תהליך העבודה באמצעות A2A.
- אפשר להשתמש בפונקציות AI ב-BigQuery כדי לתת ציון לתוצרים החזותיים המרכזיים שנוצרו, בהתאם להתאמה שלהם למותג.
הדרישות
- דפדפן אינטרנט (כמו Chrome).
- חשבון ב-Google Cloud ופרויקט ב-Google Cloud שהחיוב בו מופעל.
2. לפני שמתחילים
יצירת פרויקט ב-Google Cloud
- במסוף Google Cloud, בדף לבחירת הפרויקט, בוחרים פרויקט בענן או יוצרים פרויקט בענן.
- הקפידו לוודא שהחיוב מופעל בפרויקט שלכם ב-Cloud. כך בודקים אם החיוב מופעל בפרויקט
הפעלת Cloud Shell
תשתמשו ב-Google Cloud Shell כדי להוריד את הקוד, להריץ סקריפטים של הגדרה ולפרוס את האפליקציה.
- פותחים את Google Cloud Shell בדפדפן (או לוחצים על Activate Cloud Shell בחלק העליון של מסוף Google Cloud):
אם מוצגות הודעות נוספות, לוחצים על המשך.ממתינים עד שהטרמינל והעורך ייטענו במלואם. הפעולה הזו עשויה להימשך דקה או שתיים.https://shell.cloud.google.com/ - מגדירים את מזהה הפרויקט ב-Google Cloud ואת האזור (לדוגמה,
us-central1) בסביבה על ידי הדבקת הפקודה הבאה בטרמינל (מחליפים אתואתבערכים שלכם):export PROJECT_ID="<YOUR_PROJECT_ID>" export REGION="<YOUR_REGION>" gcloud config set project $PROJECT_ID
אמורה להופיע הודעה דומה לזו:Your active configuration is: [cloudshell-####] Updated property [core/project] - מפעילים את ממשקי ה-Cloud APIs הנדרשים של Google Cloud בפרויקט:
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
שכפול המאגר
יש לנו תבנית קוד שתעזור לכם להתחיל את ה-Codelab. הוא קטן יחסית, אבל המאגר ב-GitHub שבו הוא נמצא גדול למדי. כדי להאיץ את השכפול ולחסוך במקום בדיסק, בדרך כלל נשתמש ב-git clone עם האפשרות --filter=blob:none, ב-git sparse-checkout ובעוד כמה פקודות.
זה נשמע מאוד מורכב, אבל Antigravity יכול לעשות את הכול בשבילנו!
קודם כול, נגדיר את Antigravity CLI כדי שיוכל לפתור את הבעיה שלנו באופן מיידי, ואז נפעיל אותו.
ב-Cloud Shell, מריצים את הפקודה הבאה:
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)
הפקודה מגדירה את Antigravity CLI ומפעילה אותו באותו טרמינל. כשיוצאים מ-Antigravity CLI, חוזרים להנחיה של Cloud Shell.

אנחנו צריכים רק את התיקייה codelabs/bwg-multi-agent-pitch-generator במאגר. נבקש מ-Antigravity להוריד רק את התיקייה הזו, ולשמור אותה באופן מקומי ב-~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/.
נותנים ל-Antigravity CLI את המשימה הבאה:
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.

Antigravity יבצע את המשימה. תתבקשו לתת הרשאות להפעלת כמה פקודות. אחרי שמורידים את הקוד, יוצאים מ-Antigravity CLI באמצעות הפקודה /exit:
/exit

פתיחת סביבת העבודה של המאגר
עכשיו נפתח את סביבת העבודה של הקוד ב-Cloud Shell. מריצים את הפקודה הזו במסוף של Cloud Shell:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
cloudshell workspace .
הפעלת סקריפט ההגדרה
כדי לשפר את חוויית השימוש, מריצים את סקריפט ההגדרה מהמאגר. הוא מגדיר משתני סביבה, מתקין את agents-cli, יוצר את קטגוריית Cloud Storage לארטיפקטים, יוצר את החיבור למשאב BigQuery Cloud ומעניק את הרשאות ה-IAM הנדרשות לחשבון השירות שלו עם ניסיון חוזר אוטומטי.
סקריפט ההגדרה קורא גם ל-setenv.sh, כלי עזר קטן שמחלץ את המשתנים ששני הסוכנים צריכים – GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_REGION, GOOGLE_CLOUD_LOCATION ו-GOOGLE_GENAI_USE_VERTEXAI – מ-PROJECT_ID ומ-REGION שייצאתם למעלה. הוא גם שומר את שני הערכים האלה בקובץ .env בספריית הבית, כדי שתוכלו לשחזר אותם מאוחר יותר.
- ב-Cloud Shell, הופכים את סקריפט ההגדרה לסקריפט שאפשר להפעיל ומריצים אותו:
chmod +x codelab-setup.sh setenv.sh ./codelab-setup.sh - ההרצה תימשך כדקה. אחרי ההצלחה תופיע ההודעה הבאה: "🎉 🦄 Script execution complete."

- סקריפט ההגדרה פעל במעטפת משלו, ולכן המשתנים שלו לא הגיעו למסוף. טוענים אותם לסשן:
הפרויקט, האזור והמיקום שלכם אמורים להופיע.source ./setenv.sh
3. תזמור סוכנים באמצעות תהליך עבודה של גרף
במשימה הזו תיצרו את פרויקט יצירת הצעת הערך ותבנו תהליך עבודה של גרף שמפעיל שני סוכנים ומשלב את הפלט שלהם.
פיץ' צריך לכלול קונספט, כלומר את הרעיון והנכסים שמשמשים למכירת הקונספט. בתרחיש השימוש הזה, נתכנן סוכנים עם תחומי אחריות שונים במסגרת העבודה, כדי להדגים איך אפשר להפריד משימות מורכבות ולהקצות אותן לסוכנים שונים עם תחומי אחריות נפרדים, וכך להקל על הניהול שלהן.
כדי לחלק את העבודה הזו בין מומחים, צריך משהו שיסנכרן את הפעולות של כל סוכן. אתם יכולים לבחור לתת לסוכן להחליט מתי להתקשר לכל סוכן משנה, או להשתמש בגישה יותר דטרמיניסטית. ב-Codelab הזה תשתמשו בגישה דטרמיניסטית עם תהליך עבודה של גרף.
יצירת תבנית לפרויקט הסוכן הראשון
- ב-Cloud Shell, יוצרים את הפרויקט על ידי הדבקת הפקודה הבאה בטרמינל:
הפעולה הזו יוצרת פרויקט מלא וניתן להפעלה: סוכן, קובץ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-approveDockerfile, בדיקות, Terraform לשימוש מאוחר יותר וקובץ.envעם תבנית.env.exampleשלו. יש שני חלקים שחשובים ל-Codelab הזה:-
pitch_generator/agent.pyהוא הסוכן והקובץ שכותבים מחדש. -
pitch_generator/הוא שם החבילה, שמוגדר על ידי--agent-directory.
setenv.sh, כך שאין צורך להגדיר כאן דבר. -
- יחסי תלות של התקנות:
הערה: ה-scaffold גם מקשר את הפרויקט להצגת A2A, הפרוטוקול שבו נעשה שימוש בהמשך במעבדת התכנות הזו. בינתיים, אל תעשו כלום עם הקבצים האלה.cd pitch-generator agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
הסבר על התרשים
ADK v2.x תומך בתהליכי עבודה של גרפים. גרף מורכב מצמתים, שהם יחידות העבודה, ומקשתות שמקשרות ביניהם. צומת הוא בדרך כלל סוכן, אבל אפשר להשתמש גם בפונקציית Python רגילה. קצה מציין שכשאחד מהצמתים מסתיים, הצומת הבא מופעל.
הקצוות נכתבים כרשימה, ו-ADK קובע את הסדר:
edges=[
("START", creative_director), ## the graph begins here
(creative_director, copywriter), ## then this
]
הקינון נותן יתרון למשמעות:
Edge | משמעות |
| שרשרת. a, ואז b, ואז c, אחד אחרי השני |
| פיצול. a, ואז b ו-c בו-זמנית |
| מאוורר פנימי. c ממתין גם ל-a וגם ל-b |
ב-fan-in, משתמשים ב-JoinNode, שמחכה לכל קודם ומעביר את התוצאות שלהם יחד.
במשימה הזו ניצור את תהליך העבודה הבא:

פיתוח הפייפליין
- פותחים את קובץ הסוכן על ידי הדבקת הטקסט הבא במסוף:
תראו את ה-scaffoldedcd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator cloudshell edit pitch_generator/agent.pyagent.pyבעורך של Cloud Shell.💡 טיפ: בכל שלב, אפשר לבחור את הקוד ולהשתמש בסמל הנורה כדי לבקש מ-Gemini הסבר. אפשר גם לשאול את Antigravity CLI.
- מחליפים את כל הקובץ ב-
pitch_generator/agent.pyבעורך בפקודה הבאה: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", ) -
agents-cliכולל ממשק משתמש מובנה לפיתוח אתרים שנקראplaygroundלבדיקה, בדומה ל-ADK. כדי להפעיל את Playground: תתבקשו לאשר את הפעלת הטלמטריה. מקלידיםagents-cli playground --port 8080nומקישים על Enter. - לוחצים על Web Preview (תצוגה מקדימה של אתר), סמל העין בפינה השמאלית העליונה של הטרמינל, ובוחרים באפשרות Preview on port 8080 (תצוגה מקדימה ביציאה 8080).

- יוצג הממשק הבא לצ'אט עם הסוכן. אפשר לנסות לשלוח את ההנחיה "Flying skateboards for cats" (סקייטבורדים מעופפים לחתולים). תופיע תשובה כמו בדוגמה שלמטה, שבה אפשר גם לבדוק את הגרף המלא של תהליך העבודה.
בדיקת אופן יצירת התשובה ב-Playground מוצג כל שלב שהתרשים עבר, לפי הסדר: creative_director, אחר כךcopywriter, אחר כךassemble, אחר כךpackage. הרצף הזה הוא הגרף שחיברתם, וכך תוכלו לאשר במהירות את הגרף שבניתם. - חוזרים למסוף ומפסיקים את התהליך
agents-cli playgroundשפועל על ידי הקשה על Ctrl+C פעמיים.
4. הקצאת סקיל לסוכן
במשימה הזו, תיצרו סוכן שני ותתנו לו קבוצה של הנחיות מיתוג שמאוגדות ככישורים.
הצינור כותב מילים. תמונה צריכה סגנון ביתי: פלטת צבעים, שיטת תאורה, קומפוזיציות שבהן משתמשים וקומפוזיציות שבהן אף פעם לא משתמשים. זהו דף של כללים, ודף של כללים צריך מנגנון טוב יותר לניהול. אחת הדרכים לעשות זאת היא לארוז אותו כ'מיומנות של סוכן'.
מיומנות היא תיקייה שמכילה קובץ SKILL.md. המבנה של ספריית הכישורים נראה כמו בדוגמה הבאה (באמצעות visual_director, הסוכן הבא שתבנו):
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
...
נקודת הכניסה של מיומנות הסוכן היא הקובץ SKILL.md, שכולל קטע YAML frontmatter כמו זה שמוצג בהמשך:
---
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...
התווית היא frontmatter. זה כל מה שהסוכן רואה בהתחלה, ולכן בתיאור צריך לציין מתי כדאי לטעון את המיומנות. body הוא התוכן, והוא לא נכלל בהקשר עד שהסוכן קורא ל-load_skill.
הפיצול הזה הוא העיקר. ההנחיה נשארת קצרה, הסוכן מאחזר את הכללים רק כשהוא צריך אותם, ושינוי הסגנון מתבצע על ידי עריכה של קובץ אחד במקום חיפוש בתוך מחרוזת הוראות.
נתחיל לבנות את הסוכן השני, visual-director, באמצעות כישורי סוכן כמו שמוצג בתרשים הבא:

יצירת תשתית למנהל החזותי
תבנו נציג מומחה שיטפל ברכיב החזותי, visual-director. זהו פרויקט נפרד כי הוא הופך בהמשך לשירות משלו כדי להדגים איך כמה סוכני AI מתקשרים אחד עם השני באמצעות פרוטוקול A2A. בסופו של דבר יהיו לכם שני פרויקטים זה לצד זה:
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
- כדי ליצור את הפרויקט ולהתקין את יחסי התלות שלו, מדביקים את הטקסט הבא במסוף:
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
כתיבת ספר המותג
- יוצרים את תיקיית המיומנות ופותחים את הקובץ:
mkdir -p visual_director/skills/brand-guidelines cloudshell edit visual_director/skills/brand-guidelines/SKILL.md - מדביקים את התוכן הבא בקובץ
SKILL.mdהפתוח בעורך ושומרים אותו:
--- 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.
עדכון של visual_director/agent.py
מעדכנים את הקובץ agent.py ומגדירים אותו כך שהוא יכלול את המיומנות שהוגדרה קודם.
- פותחים את קובץ הסוכן:
cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py - מחליפים את כל התוכן של הקובץ
visual_director/agent.pyבקוד הבא: הסוכן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", )visual-directorמוגדר עכשיו בצורה תקינה עם מיומנות של סוכן. לפני שתבדקו אותו, תציידו את הסוכן הזה בכלי ליצירת נכסי תמונות כאחת מהיכולות העיקריות שלו.
5. איך מציידים סוכן בכלי ליצירת תמונות
במשימה הזו, נותנים לבמאי הוויזואלי כלי שמציג את ההנחיות האומנותיות כתמונה ושומר אותה כארטיפקט. ארטיפקט הוא נתון בינארי או תוכן קובץ עם שם ומספר גרסה, שסוכן AI יוצר, משנה או משתמש בו במהלך משימה.

הטמעה והוספה של הכלי לסוכן
- פותחים את קובץ הסוכן:
cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py - מעתיקים את הכלי
generate_key_visualהבא לפני ההגדרה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, } - מוסיפים את
generate_key_visualמתחת ל-SkillToolsetכדי לצייד את הסוכן בכלי:tools=[ SkillToolset(skills=[BRAND_SKILL]), ## Supply the `generate_key_visual` tool below this comment generate_key_visual, ], -
agents-cliכולל ממשק משתמש מובנה לפיתוח אתרים שנקראplaygroundלבדיקה, בדומה ל-ADK. כדי להפעיל את Playground:agents-cli playground --port 8080 - לוחצים על Web Preview (תצוגה מקדימה של אתר), סמל העין בפינה השמאלית העליונה של הטרמינל, ובוחרים באפשרות Preview on port 8080 (תצוגה מקדימה ביציאה 8080).

- יוצג הממשק הבא לצ'אט עם הסוכן. אפשר לנסות לשלוח את ההנחיה "Nine lives. Zero gravity."
אפשר לראות מהאינטראקציות איך visual_directorהסוכן מבצעload_skill("brand-guidelines")כדי לקרוא את ספר המותג, ואז מבצעgenerate_key_visualכדי להתחיל ליצור את התמונה. התמונה שנוצרת מאוחסנת כארטיפקט, שמוצג גם בממשק המשתמש ואפשר לבדוק אותו בקלות. - חוזרים למסוף ומפסיקים את התהליך
agents-cli playgroundשפועל על ידי הקשה על Ctrl+C פעמיים.
6. ארכיטקטורה מרובת סוכנים עם פרוטוקול A2A
במשימה הזו, תחשפו את הבמאי הוויזואלי כשירות שסוכנים אחרים יכולים להתקשר אליו, ותגדירו אותו כך שיגיב עם התמונה שנוצרה.
Agent2Agent (A2A) הוא פרוטוקול פתוח שמאפשר לסוכן אחד להתקשר לסוכן אחר ברשת. החלק המרכזי הוא כרטיס הסוכן, מסמך JSON שהסוכן מפרסם על עצמו באמצעות כתובת URL ידועה, שכולל את השם, התיאור והיכולות שלו. המתקשר מאחזר את הכרטיס ויודע איך לתקשר עם הנציג שמאחוריו, ללא קוד מקור, יחסי תלות או שפה משותפים.

agents-cli כבר הגדיר את זה כברירת מחדל. ה-scaffold שנוצר visual_director/app_utils/a2a.py, שבונה את הכרטיס מהסוכן ומטפל במסלולים, ו-visual_director/fast_api_app.py, שמציג אותם. צריך לבצע שינוי קטן כדי לאפשר לו גם לשלוח את הארטיפקטים בחזרה ללקוח.
הארכיטקטורה תיראה כמו בתמונה הבאה. שימו לב שהסוכן עדיין לא משולב בתהליך העבודה הראשוני שלנו:

הפיגום של שרת agents-cli A2A
הקוד והמודול הבאים כבר מסופקים כשמפעילים את סוכן visual-director באמצעות agents-cli. כדי לפתוח את הקבצים האלה, מדביקים את הפקודות הבאות בטרמינל:
cloudshell edit visual_director/fast_api_app.py
cloudshell edit visual_director/app_utils/a2a.py
visual_director/fast_api_app.py מפעיל את הסוכן ומטמיע את נתיבי ה-A2A בנתיב שנבנה משם האפליקציה:
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 יעשה את כל השאר, בשלושה מהלכים.
קודם כל, המערכת יוצרת את כרטיס הסוכן מתוך הסוכן עצמו. השם, התיאור וכל כלי הופכים לרשומות שהמתקשר יכול לקרוא:
agent_card = await AgentCardBuilder(
agent=agent,
rpc_url=f"{resolved_app_url}{rpc_path}",
).build()
שנית, הוא מציב רכיב הפעלה בין הפרוטוקול לבין הנציג. בקשות A2A נכנסות מתורגמות להפעלות של ADK, והתוצאות מתורגמות בחזרה:
request_handler = DefaultRequestHandler(
agent_executor=A2aAgentExecutor(runner=runner, force_new_version=True),
task_store=task_store,
agent_card=agent_card,
)
שנית, הוא מטמיע שני מסלולים: אחד להצגת הכרטיס ואחד לקבלת שיחות.
מסלול | מטרה |
| הכרטיס. המתקשר קורא את זה קודם כדי להבין מה הסוכן יכול לעשות |
| נקודת הקצה של JSON-RPC. לאן מועברות הבקשות בפועל |
אפשרות להעביר פריטים מעבר לגבול
כברירת מחדל, תשובה לשאלה כוללת טקסט. התמונה היא ארטיפקט, וארטיפקט נשאר במאגר של סוכן ההגשה אלא אם מגדירים שהוא ייכלל.
- פותחים את קובץ ה-A2A שנוצר:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director cloudshell edit visual_director/app_utils/a2a.py - מאתרים את הייבוא
AgentCardBuilderומוסיפים את הייבואA2aAgentExecutorConfigואת הייבוא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, ) - מחפשים את השורה
agent_executor=A2aAgentExecutor(runner=runner),ומחליפים אותה בשורה הבאה (מוסיפים אתA2aAgentExecutorConfig):agent_executor=A2aAgentExecutor( runner=runner, force_new_version=True, config=A2aAgentExecutorConfig( execute_interceptors=[include_artifacts_in_a2a_event_interceptor] ), ),
הפעלת השירות וקריאת כרטיס ה-A2A שפורסם
- מפעילים את השרת ברקע. עליך להצהיר על
APP_URL, כי זו הכתובת שהכרטיס מפרסם ככתובת שלו. מדביקים את הטקסט הבא בטרמינל:APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 & - מחכים כמה שניות עד שהשירות יתחיל לפעול, ואז מאשרים שהוא פועל:
אלה הפרטים שיוצגו:sleep 10 tail -2 /tmp/visual-director.logINFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit) - קוראים את הכרטיס שהסוכן מפרסם עכשיו:
בכרטיס מופיעים השם, התיאור, כתובת ה-URL והכישורים של הסוכן, כוללcurl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.toolgenerate_key_visual. הוא נוצר ישירות מהגדרת הסוכן בקוד. אתה מוכן להטמיע את סוכן{ "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" }visual-directorבתהליך העבודה הראשי שלpitch-generator.
7. הטמעת הסוכן המרוחק של A2A בתהליך העבודה
במשימה הזו, תוסיפו את הסוכן המרוחק לגרף כענף שלישי.
סוכן מרוחק עטוף בתג RemoteA2aAgent ומוצב בתרשים כמו כל צומת אחר. הקצוות שומרים על הצורה שלהם, והמתאם לא יודע אילו מהצמתים שלו הם מקומיים.
מכוונים את הכלי לתיאום אל השירות ומעדכנים את תהליך העבודה של התרשים
- פותחים את קובץ הסוכן:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator cloudshell edit pitch_generator/agent.py - מוסיפים את שורות הייבוא האלה לחלק העליון של הקובץ, לצד שורות הייבוא הקיימות:
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 - מוסיפים את
RemoteA2aAgentההגדרה הבאה מעל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, ) - מחליפים את פונקציית Python של הצומת
packageבהגדרה הבאה: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) - מוסיפים שער אישור אנושי לפני שהיצירה ממשיכה בהמשך השרשרת. ב-
pitch_generator/agent.py, מגדירים את צמתי האישור מעל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") - בסוף, מעדכנים את הייבוא בחלק העליון כדי לכלול את
RequestInputו-nodeמ-google.adk.eventsו-google.adk.workflow: ולשנות את תהליך העבודהfrom google.adk.events import Event, RequestInput from google.adk.workflow import JoinNode, Workflow, noderoot_agentedgesכדי לנתב דרךuser_approval: כמה פרטים שחשוב לדעת:edges=[ ("START", creative_director), (creative_director, user_approval, (copywriter, visual_director)), ((creative_director, copywriter, visual_director), assemble), (assemble, package), ],- אישור של אדם שבתהליך (HITL):
approve_conceptהתוצאה היאRequestInput. כשתהליך העבודה מגיע לצומת הזה, הביצוע מושהה ומופעלת הפרעה למתקשר. הענפים הבאים (copywriterו-visual_director) לא יפעלו עד שאישור המשתמש יאושר. - הסימן
visual_directorמופיע בקצוות בדיוק כמוcopywriter, אבל אחד מהם הוא קריאה לפונקציה והשני הוא ברשת. - התמונה שמוחזרת נקראת מאירועי הסשן ולא מ-
node_input, כי בשלב הזה רק טקסט מועבר לפלט של צומת. התמונה חצתה את הגבול דרך מיירט ה-A2A, אבל היא הגיעה כמצורפת לאירועים של הסוכן המרוחק, ולכן פונקציית העזר_key_visualמאחזרת אותה משם.
- אישור של אדם שבתהליך (HITL):
בדיקת השילוב
עכשיו נבדוק שאפשר ליצור תקשורת בין הבמאי הוויזואלי (שפועל ברקע) לבין כלי יצירת הצעות המכירה באמצעות פרוטוקול A2A.
- מוודאים שהתכונה 'במאי חזותי' עדיין פועלת ברקע:
אמור להתקבל כאן סטטוסcurl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.jsonvisual director: 200.אם מתקבל סטטוס אחר, מריצים את הפקודה הבאה כדי להפעיל מחדש את השירות: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 - מתחילים את הניסיון:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator agents-cli playground --port 8080 - לוחצים על Web Preview (תצוגה מקדימה של אתר), סמל העין בפינה השמאלית העליונה של הטרמינל, ובוחרים באפשרות Preview on port 8080 (תצוגה מקדימה ביציאה 8080).

- יוצג הממשק לצ'אט עם הסוכן. נסו לשלוח את ההנחיה "Flying skateboards for cats" (סקייטבורדים מעופפים לחתולים).

תוכלו לראות את האינטראקציה המלאה שבה תהליך העבודה מתחיל וכל סוכן מופעל. אחרי ש-creative_directorמנסח את הרעיון, תהליך העבודה מושהה בשלב האישור ומבקש ממך לאשר: "יש לאשר את הרעיון לקמפיין (כן/לא)". כדי להמשיך, מזינים yes בשדה להזנת קלט.אחרי האישור, התרשים מתרחב: אפשר לראות מתי סוכןvisual-directorמתחיל לטעון את הסקיל, ובאמצע יצירת התמונות, סוכןcopywriterמסיים ליצור את הכיתוב לרשתות החברתיות – מה שמוכיח ששני הסוכנים פועלים במקביל בלי להמתין זה לזה. אחרי ששני התהליכים מסתיימים, הצומתassembleמצטרף לתוצאות עם הרעיון שאושר שנוצר על ידיcreative_director, וכך מסתיים תהליך העברת התוכן של הצעת הערך בפונקציהpackage.
8. שמירת מפתחות השורות ב-Cloud Storage
עד עכשיו כל רכיב חזותי מרכזי נשמר בזיכרון. ברגע שהתהליך מפסיק, התמונה נעלמת, וזה בסדר בשביל סביבת ניסוי אבל לא בשביל קמפיין. במשימה הזו תגדירו לדלי את היעד לכתיבה ותיצרו מערך נתונים ב-BigQuery שבו תשתמשו במשימה האחרונה.
ה-ADK מחליט לאן להעביר את הארטיפקטים בהפעלה. הקובץ app_utils/services.py שנוצר קורא משתנה סביבה יחיד:
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()
אם מגדירים את LOGS_BUCKET_NAME, כל השיחות ב-save_artifact יועברו ל-Cloud Storage.

יצירת מערך נתונים ב-BigQuery
- ב-Cloud Shell, מריצים את הפקודה הבאה כדי ליצור מערך נתונים ב-BigQuery:
bq mk --dataset --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> bwg - מפנים את מאגר הארטיפקטים של הרכז אל הקטגוריה:
export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"GcsArtifactServiceמקבל שם קטגוריה ללא הקידומתgs://. בדומה למשתנים האחרים, המשתנה הזה נמצא במעטפת, ומשימת הפריסה מעבירה אותו ל-Cloud Run. - בודקים את התוכן של הדלי. בכרטיסייה חדשה בדפדפן, פותחים את כתובת ה-URL הבאה:
לוחצים על ה-bucket. יכול להיות שהיא תכיל תמונות שנוצרו בעבר, בהתאם לבדיקות הקודמות שלכם.https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
9. פריסת סוכן Visual Director ב-Cloud Run
עד עכשיו, כל הפעולות בוצעו במחשב אחד, והיה אפשר להגיע ל-Visual Director בכתובת localhost:8801. במשימה הזו תפרסו את סוכן הבמאי הוויזואלי כשירות Cloud Run משלו, ותאפשרו לסוכן ליצירת הצעות למכירה לתקשר איתו ברשת באמצעות פרוטוקול Agent2Agent (A2A).
מנהל התוכן החזותי מקבל כתובת URL אמיתית של https://, כך ש-_cloud_run_client שכתבתם קודם מתחיל לחתום על בקשות במקום להחזיר None. בנוסף, הפריסה ב-Cloud Run היא פרטית כברירת מחדל.

פריסת Visual Director
- אם שירות הפועל ברקע המקומי וסביבת הפיתוח עדיין פועלים, צריך להפסיק אותם:
fuser -k 8801/tcp 2>/dev/null || true fuser -k 8000/tcp 2>/dev/null || true - פורסים את הסוכן. הפעולה הזו יוצרת מאגר ומעבירה אותו.⏰ התהליך יימשך כמה דקות.
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"
הפעלת מחולל הצעות הערך שמשתמש בבמאי הוויזואלי שנפרס ב-Cloud Run
- קוראים את כתובת ה-URL של הבמאי החזותי ומריצים את מחולל הצעות המחיר שמשתמש בה:
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 &
התקשרות לתהליך העבודה שנפרס
שני הנציגים תומכים בפרוטוקול A2A. המאגר כולל לקוח קטן להפעלת Pitch Generator באמצעות A2A.
צריך להגדיר את PITCH_GENERATOR_URL לכתובת ה-URL של מחולל הצעות המחיר שפועל באופן מקומי. בינתיים, הבמאי הוויזואלי פועל ב-Cloud Run, והמחולל של הצעות המכירה מתקשר איתו באמצעות A2A.
- מעתיקים את הלקוח לפרויקט ומפעילים פתרונות חכמים:
מכיוון שתהליך העבודה מכיל שער אישור אנושי, הסקריפט מושהה אחרי יצירת טיוטת הרעיון: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 מקלידים=== HUMAN APPROVAL REQUIRED === [DRAFT CONCEPT] Concept Line: "Gravity is a Suggestion." ... Please approve the campaign concept (yes/no):yesומקישים על Enter כדי לאשר ולהמשיך בתהליך העבודה. (אפשר גם להעביר את הערך-yאו--auto-approveכדי לאשר באופן אוטומטי). - פותחים את
call_agent.pyובודקים איך הוא מתקשר עם הנציג באמצעותa2a-sdk:-
ClientFactoryיוצרת טירגוט רשמי שלClientA2Ahttps://SERVICE_URL/a2a/pitch_generator. -
generate_pitch_asyncשולחSendMessageRequestראשוני. כשהתשובה חוזרת עםTaskState.TASK_STATE_INPUT_REQUIRED, המודל מזהה את החלקadk_request_input, מציג את טיוטת הרעיון, מקבל את האישור שלכם ושולח בקשה לקורות חיים עם התשובה שלכם בחזרה למשימה. - לולאת הניתוח קוראת את הקטעים
CONCEPT,COPYו-ART DIRECTIONמפריטי המידע של המשימה שהושלמה, ושומרת את תמונת הקובץ הבינארי ב-key_visual.png.
key_visual.png. -
- מעלים את התמונה המרכזית לדלי, תחת קידומת שאפשר להפנות אליה את BigQuery:
gcloud storage cp ./key_visual.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png - יוצרים הצעה שנייה כדי שיהיה משהו להשוות אליו, ומעלים גם אותה:
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. הקצאת ניקוד לרכיבים החזותיים העיקריים בהתאם להתאמה למותג ב-BigQuery
עכשיו יש לכם תמונות בקטגוריה ואת הנחיות המיתוג שאמורות לעצב אותן. במשימה הזו, נשלב בין השניים: נתייחס לתמונות מ-BigQuery, ואז נבקש ממודל Gemini לדרג כל אחת מהן בהתאם להנחיות, בלי להעביר אף בייט מ-Cloud Storage.
שתי תכונות של BigQuery מבצעות את העבודה:
-
ObjectRefהוא ערך שמצביע על אובייקט ב-Cloud Storage. OBJ.MAKE_REFיוצרת אחת מ-URI בתוספת חיבור שמעניק ל-BigQuery גישה שהוקצתה לדלי. החיבורpitch-connectionשנוצר על ידי סקריפט ההגדרה הוא החיבור הזה, וכבר יש בו את ההרשאות Storage Object Viewer ו-Agent Platform User. -
AI.SCOREמקבלת הנחיה ומחזירהFLOAT64. ההנחיה יכולה לכלול מחרוזות עם ערכים שלObjectRef, כך שהמודל יראה את קריטריון ההערכה ואת התמונה יחד.

יצירת טבלה של רכיבים חזותיים מרכזיים
- פותחים את BigQuery Studio בכרטיסייה חדשה בדפדפן:
https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID> - לוחצים על Create new query (יצירת שאילתה חדשה) (סמל +), מדביקים את השאילתה הבאה ולוחצים על Run (הפעלה) (מחליפים את
ואתבמזהה הפרויקט ובאזור שלכם). הוא יוצר שורה אחת לכל תמונה מרכזית, ומשייך את הקונספט של הקמפיין להפניה לתמונה: 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' ) ]);
OBJ.MAKE_REFיוצר את ההפניה מה-URI ומהחיבור. OBJ.FETCH_METADATAואז יוצר קשר עם Cloud Storage וממלא את הגודל, סוג התוכן והגנרציה של האובייקט. הקריאה השנייה היא גם הדרך הכי מהירה להוכיח שהחיבור פועל: אם ההרשאות שגויות, השדהdetailsיחזיר את השגיאה במקום את המטא-נתונים. - בודקים מה מאוחסן. מחליפים את השאילתה הקודמת בשאילתה הזו ומריצים אותה:
הטבלה מכילה מצביע ומטא-נתונים, ולא את התמונה. הבייטים אף פעם לא יצאו מהמאגר.SELECT campaign, concept, key_visual.uri, key_visual.details FROM bwg.key_visuals;
דירוג כל תמונה בהתאם להנחיות המותג
הבמאי הוויזואלי קיבל הוראה לציית לפלטת צבעים, לכלל תאורה ולכלל קומפוזיציה. האם זה באמת קרה או לא – זו שאלה שדורשת שיקול דעת, ולכן יש את AI.SCORE.
- מריצים את השאילתה הבאה (מחליפים את
באזור שלכם). הפרומפט הוא טאפל: הקריטריונים להערכה, הקונספט של הקמפיין וObjectRefשל התמונה: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; - הופכים את הניקוד לפסיקה שהצוות יכול לפעול לפיה:
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מחזירהFLOAT64בלבד, ולכן ערך הסף נמצא ב-SQL רגיל, שאפשר לשנות אותו בלי לגעת בהנחיה. הניקוד מחושב לכל שורה, כלומר אותם קריטריונים חלים על כל נכס שנוצר בקמפיין, ולא רק על שני הנכסים שיצרתם כאן.
11. השלבים הבאים
יצירת הערכות
הערכות מבוססות-סוכנים או "הערכות" הן בדיקות אוטומטיות שמשמשות למדידת היכולת של סוכני AI אוטונומיים להסיק מסקנות, לתכנן, להשתמש בכלים ולהשלים תהליך עבודה מרובה שלבים. הן דומות לבדיקות יחידה של סוכנים, אבל הן מתחשבות באופי הלא דטרמיניסטי שלהם.
אחד מהסוכנים שלנו כולל Human in the loop, כלומר הוא מחכה לקלט. תצטרכו לאפשר ל-Antigravity לבצע שינויים בקוד הנוכחי כדי שההתנהגות הזו לא תפריע להערכות האוטומטיות.
אחת הדרכים הטובות להתחיל היא לשאול את Antigravity CLI.
כדי להפעיל את ה-CLI, מקלידים את הפקודה הבאה ב-Cloud Shell:
cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
agy
הנה הנחיה ראשונית:
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.
במאגר המשוכפל יש גם דוגמה להערכות והוראות להפעלתן.
12. הסרת המשאבים
כדי להימנע מחיובים בחשבון Google Cloud על המשאבים שבהם השתמשתם ב-Codelab הזה, מומלץ למחוק את הפרויקט או למחוק את המשאבים הספציפיים שנוצרו במהלך ה-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. מעולה!
בשיעור Codelab הזה יצרתם מחולל הצעות לקמפיינים עם כמה סוכנים באמצעות ערכה לפיתוח סוכנים (ADK). חיברתם שלושה סוכנים לתהליך עבודה גרפי דטרמיניסטי, ארזתם הנחיות מיתוג ככישורי סוכן שנטענים לפי דרישה, והענקתם לסוכן כלי ליצירת תמונות ששומר את הפלט שלו כארטיפקט. לאחר מכן, פיצלתם את Visual Director לשירות A2A משלו, הטמעתם אותו בחזרה בתרשים כצומת מרוחק, פרסתם אותו ב-Cloud Run והפעלתם את כל תהליך העבודה באמצעות A2A ממתאם שפועל באופן מקומי. לבסוף, הפנית אל התמונות החזותיות שנוצרו מ-BigQuery באמצעות ObjectRef והשתמשת ב-AI.SCORE כדי להשוות אותן להנחיות המיתוג שלך.