יצירת סטודיו יצירתי מרובה-סוכנים באמצעות Agent Stack של Google: ‏ ADK, ‏ A2A, ‏ MCP ב-Cloud Run וב-Agent Runtime

1. סקירה כללית

בשיעור Codelab הזה נסביר איך ליצור את AI Creative Studio – מערכת מבוזרת מרובת סוכנים שממירה הנחיה יחידה לקמפיין שלם באינסטגרם.

מקלידים משפט אחד. קבלת מחקר קהלים, כתוביות, מושגים חזותיים, עותק שעבר בדיקת איכות וציר זמן מלא של הפרויקט – כל אלה נוצרים על ידי צוות של סוכני AI שמשתפים פעולה.

הסוכנים שתבנו

סוכן

תפקיד

אסטרטגיית מותג

חיפוש באינטרנט של תובנות לגבי קהלים, ניתוח של המתחרים ומגמות לשנת 2025

קופירייטר

כתיבת כיתובים לתמונות באינסטגרם עם תגי hashtag וקריאות לפעולה – מבוסס על מיומנות ADK שמעלה הנחיות לפלטפורמה ונוסחאות לכיתובים לפי דרישה

Designer

יוצרת מושגים ויזואליים ומפיקה תמונות אמיתיות באמצעות Gemini, שמאוחסנות ב-GCS

מבקר

העתקה של ביקורות ורכיבים חזותיים – מחזירה APPROVED או NEEDS_REVISION עם משוב ספציפי

Project Manager

יצירת ציר זמן של פרויקט ופירוט של המשימות, עם אפשרות לסנכרון עם Notion דרך MCP

מנהל קריאייטיב

מנהל את כל חמשת המומחים ברצף – אתם נותנים לו הנחיה אחת, והוא מתאם את השאר

5 הסוכנים נפרסים כמיקרו-שירותים עצמאיים של Cloud Run. הם מתקשרים באמצעות פרוטוקול A2A – תקן פתוח שאינו תלוי בשפה, כך שכל סוכן יכול להתקשר לכל סוכן אחר ללא קשר למסגרת. הבמאי הקריאטיבי פועל ב-Agent Runtime ומתחבר לכל מומחה מרחוק.

ארכיטקטורה

סקירה כללית של המערכת

מה תלמדו

  • יצירת סוכני LLM באמצעות Google ADKAgent, הוראות מערכת וכלים מובנים.
  • אפשר לארוז ידע של סוכנים לשימוש חוזר בקבצים מודולריים באמצעות ADK Skills (SkillToolset).
  • אפשר ליצור תמונות אמיתיות על ידי חיבור של סוכן טקסט למודל תמונות באמצעות FunctionTool.
  • שילוב של ממשקי API חיצוניים ללא קוד דבק מותאם אישית באמצעות Model Context Protocol‏ (MCP).
  • הופכים כל סוכן לשירות שאפשר להתקשר אליו ברשת באמצעות פרוטוקול סוכן לסוכן (A2A) על גבי HTTPS.
  • תזמור סוכנים מבוזרים באמצעות RemoteA2aAgent ו-AgentTool.
  • אפשר לארוז ולפרוס סוכנים עצמאיים בתור מיקרו-שירותים (microservices) של Cloud Run.
  • אירוח של כלי תזמור עם שמירת מצב ב-Agent Runtime.
  • כדי לשמור על תהליכי עבודה ארוכים עם כמה סוכנים במסגרת מגבלות ההקשר, אפשר להשתמש בדחיסת הקשר.
  • בניית לולאת בקרת איכות: פלט של ביקורות ממבקרים → תיקון אוטומטי כשצריך.

הדרישות

  • פרויקט ב-Google Cloud שהחיוב בו מופעל
  • תפקיד בעלים או עריכה ב-IAM
  • ידע בסיסי ב-Python

2. הגדרת הסביבה

בשיעור הזה נשתמש ב-Cloud Shell.

מה זה Cloud Shell?

‫Cloud Shell היא סביבת Linux מבוססת-דפדפן בחינם, שמותקן בה מראש כל מה שצריך: gcloud, ‏ git, ‏ Python, ‏ Docker ועוד. לא צריך להתקין שום דבר באופן מקומי.

כדי לפתוח את Cloud Shell, לוחצים על סמל הטרמינל בסרגל הכלים שבפינה הימנית העליונה של GCP Console:

פתיחת Cloud Shell מסרגל הכלים של מסוף GCP

כשפותחים את Cloud Shell בפעם הראשונה, מוצגת בקשה לאמת את החשבון. לוחצים על אימות:

תיבת הדו-שיח לאימות החשבון

לאחר מכן לוחצים על Authorize כדי לאפשר ל-Cloud Shell לבצע קריאות ל-Google Cloud API:

תיבת הדו-שיח של הרשאת Cloud Shell

עכשיו Cloud Shell מוכן. תופיע הודעת פתיחה בטרמינל: הטרמינל של Cloud Shell מוכן

אימות והגדרת הפרויקט

האימות של Cloud Shell כבר בוצע באמצעות חשבון Google שלכם. מאשרים שהחשבון פעיל ומאתרים את מזהה הפרויקט:

gcloud config list

אפשר לראות את מזהה הפרויקט גם בחלונית הימנית של מרכז הבקרה במסוף GCP. מעתיקים אותו – תצטרכו אותו בפקודה הבאה:

מאתרים את מזהה הפרויקט במסוף GCP ומגדירים אותו ב-Cloud Shell

עכשיו מגדירים את הפרויקט:

export PROJECT_ID=$(gcloud config get-value project)
export REGION="us-central1"        # Cloud Run deployment region
echo "Project: $PROJECT_ID"

הפלט אמור להיראות כך:

Project: my-project-123

הפעלת ממשקי ה-API הנדרשים

gcloud services enable \
    aiplatform.googleapis.com \
    apphub.googleapis.com \
    run.googleapis.com \
    cloudbuild.googleapis.com \
    artifactregistry.googleapis.com \
    generativelanguage.googleapis.com \
    iam.googleapis.com \
    cloudresourcemanager.googleapis.com \
    storage.googleapis.com \
    secretmanager.googleapis.com

הפעולה תימשך כ-2 דקות. כשזה יסתיים, יופיע הסימן Operation finished successfully.

הגדרת Application Default Credentials‏ (ADC)

הסוכנים יקראו ל-Gemini Enterprise Agent Platform באמצעות ספריית האימות של Google, שדורשת Application Default Credentials (פרטי כניסה שמוגדרים כברירת מחדל לאפליקציה) – נפרדים מאימות ה-CLI של gcloud.

מריצים את הפקודה הבאה פעם אחת:

gcloud auth application-default login

תיפתח כרטיסייה בדפדפן שבה תתבקשו לאשר את הפעולה. לוחצים על אישור. הפרטים שמוצגים הם:

Credentials saved to file: ~/.config/gcloud/application_default_credentials.json

שכפול מאגר התחלתי

ב-Codelab הזה נעשה שימוש במאגר התחלה – פרויקט שלד עם כל התשתית במקום (קבצי Dockerfile,‏ pyproject.toml, סקריפטים לפריסה), אבל ללא לוגיקת הסוכן, שאתם צריכים לכתוב.

git clone https://github.com/Saoussen-CH/mas-a2a-gcp.git ~/ai-creative-studio
cd ~/ai-creative-studio/workshop/starter

כל agent.py מכיל placeholders של # TODO שבהם תכתבו את הלוגיקה של הסוכן. הסקריפטים Dockerfile, pyproject.toml וסקריפטים לפריסה כבר הושלמו.

הגדרת משתני סביבה

מעתיפים את הדוגמה שמופיעה ומזינים את מזהה הפרויקט בפעולה אחת:

cp .env.example .env
sed -i "s|GOOGLE_CLOUD_PROJECT=your-project-id|GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)|" .env

לאחר מכן יוצרים את מאגר ה-GCS שבו הכלי לעיצוב יאחסן את התמונות שנוצרו, ומעדכנים את .env עם השם שלו:

export PROJECT_ID=$(gcloud config get-value project)
export BUCKET_NAME="${PROJECT_ID}-campaign-images"

gcloud storage buckets create gs://${BUCKET_NAME} \
    --location=us-central1 \
    --project=${PROJECT_ID}

sed -i "s|GCS_IMAGES_BUCKET=your-project-id-campaign-images|GCS_IMAGES_BUCKET=${BUCKET_NAME}|" .env

לאחר מכן מגדירים תמיכה בכתובות URL חתומות של תמונות. מנהל הקריאייטיב יוצר קישורי HTTPS שאפשר ללחוץ עליהם לכל תמונה בסיכום הקמפיין הסופי. לשם כך נדרש חשבון שירות שיחתום על כתובות ה-URL. מריצים את הפקודות הבאות כדי להגדיר אותו:

export PROJECT_NUMBER=$(gcloud projects describe $(gcloud config get-value project) --format="value(projectNumber)")
export SA_EMAIL="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
export AGENT_RUNTIME_SA="service-${PROJECT_NUMBER}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"

# Allow your user account to sign URLs locally (adk web)
gcloud iam service-accounts add-iam-policy-binding ${SA_EMAIL} \
  --member="user:$(gcloud config get-value account)" \
  --role="roles/iam.serviceAccountTokenCreator"

# Allow Agent Runtime to sign URLs when deployed
gcloud projects add-iam-policy-binding $(gcloud config get-value project) \
  --member="serviceAccount:${AGENT_RUNTIME_SA}" \
  --role="roles/iam.serviceAccountTokenCreator"

# Save SA email and project number to .env
grep -q "^SIGNING_SERVICE_ACCOUNT" .env \
  && sed -i "s|^SIGNING_SERVICE_ACCOUNT=.*|SIGNING_SERVICE_ACCOUNT=${SA_EMAIL}|" .env \
  || echo "SIGNING_SERVICE_ACCOUNT=${SA_EMAIL}" >> .env

grep -q "^GOOGLE_CLOUD_PROJECT_NUMBER" .env \
  && sed -i "s|^GOOGLE_CLOUD_PROJECT_NUMBER=.*|GOOGLE_CLOUD_PROJECT_NUMBER=${PROJECT_NUMBER}|" .env \
  || echo "GOOGLE_CLOUD_PROJECT_NUMBER=${PROJECT_NUMBER}" >> .env

פותחים את .env בעורך כדי לבדוק את כל ההגדרות:

cloudshell edit .env

הקובץ .env ייפתח ככרטיסייה ב-Cloud Shell Editor. אם חלונית העריכה לא מוצגת, לוחצים על הלחצן Open Editor בסרגל הכלים:

לוחצים על Open Editor (פתיחת העורך) בסרגל הכלים של Cloud Shell.

‫Cloud Shell Editor עם עץ קבצים של פרויקט

מוודאים שהפרויקט הוגדר בצורה נכונה:

grep GOOGLE_CLOUD_PROJECT .env

התקנת יחסי תלות

אנחנו משתמשים ב-uv – מנהל חבילות Python מהיר ומודרני שמטפל בסביבות וירטואליות ומתקין בכלי יחיד. הוא מהיר פי 10 עד 100 בערך מ-pip, והוא הדרך המומלצת לניהול פרויקטים של Python.

ב-Cloud Shell כבר מותקן uv. לכל הסוכנים יש את אותן יחסי תלות בסיסיים, לכן מתקינים פעם אחת וזה עובד לכל סוכן ב-codelab הזה:

uv sync

הפקודה uv sync קוראת את pyproject.toml ויוצרת ספרייה .venv/ עם כל התלויות. לכל מומחה יש גם pyproject.toml משלו שמשמש רק לבניית Docker – ההתקנה המשותפת שלמעלה כוללת את כל מה שצריך לבדיקות מקומיות.

3. הסבר על Google ADK

לפני שכותבים קוד, חשוב להבין מהי הערכה לפיתוח סוכנים (ADK) – המסגרת שבה נשתמש כדי ליצור כל סוכן בשיעור Codelab הזה.

מה זה ADK?

הערכה לפיתוח סוכנים (ADK) היא מסגרת גמישה ומודולרית לפיתוח ולפריסה של סוכני AI. הערכה ADK מותאמת ל-Gemini ולסביבת Google, אבל היא לא תלויה במודל מסוים או בפריסה מסוימת, והיא מתוכננת כך שתהיה תואמת למסגרות אחרות. ה-ADK נועד להפוך את פיתוח הסוכנים לתהליך שדומה יותר לפיתוח תוכנה, כדי להקל על מפתחים ליצור, לפרוס ולתזמן ארכיטקטורות מבוססות-סוכנים, ממשימות פשוטות ועד לתהליכי עבודה מורכבים.

‫ADK מטפל בחלקים המורכבים – קריאה לכלים, שיחה מרובת-תורות, ניהול הקשר, סטרימינג – כדי שתוכלו להתמקד בלוגיקה של הסוכן.

אבני הבניין של סוכן ADK

כל סוכן מורכב מארבעה אבני בניין:

חסימה

תפקיד

מודל

מודל LLM שמנתח את המטרות, קובע תוכנית ויוצר תשובות

כלים

פונקציות שמביאות נתונים או מבצעות פעולות על ידי קריאה לממשקי API או לשירותים

Orchestration

שומר על הזיכרון והמצב בין תורות, מנתב קריאות לכלים ומעביר את התוצאות בחזרה למודל

זמן ריצה

מפעיל את המערכת כשמפעילים אותו – באופן מקומי באמצעות adk web, או כשירות שפרוס

הגדרת הסוכן

כל אחד מ-5 הסוכנים ב-Codelab הזה מוגדר באותו אופן:

from google.adk.agents import Agent
from google.adk.tools.google_search_tool import google_search

root_agent = Agent(
    name="brand_strategist",                              # unique identifier
    model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"), # the LLM powering this agent
    instruction=SYSTEM_INSTRUCTION,                       # the agent's persona, constraints, and output format
    description="Brand strategist for market research, trend analysis, and competitive insights",
    tools=[google_search],                                # functions the LLM can call
)

שדה

מטרה

name

מזהה ייחודי – משמש לניתוב שיחות על ידי כלי ניהול

model

מודל Gemini שמפעיל את הסוכן

instruction

הנחיה למערכת – מגדירה את התפקיד, האילוצים ופורמט הפלט של הסוכן

description

סיכום בשורה אחת – המארגן קורא את הסיכום כדי להחליט לאיזה מומחה להתקשר

tools

פונקציות שה-LLM יכול להפעיל (מובנות כמו google_search, או פונקציות Python בהתאמה אישית)

איך ADK מריץ סוכן

User message
     
     
  Agent (LLM)   reads instruction + conversation history
     
     ├─► needs more info?  calls a tool  gets result  continues reasoning
     
     └─► done reasoning  returns final text response

מודל ה-LLM מחליט באופן אוטונומי אם להפעיל כלי, איזה כלי ועם אילו ארגומנטים. אתם כותבים את ההוראה, ו-ADK מטפל בכל השאר.

4. פיתוח ובדיקה של סוכן Brand Strategist

נתחיל עם הסוכן הראשון: מומחה אסטרטגיית מותג. זהו סוכן מחקר בלבד שמחפש תובנות לגבי קהל היעד, ניתוח מתחרים ונושאים פופולריים באמצעות חיפוש Google.

פותחים את קובץ הסוכן (agent) של השלד ב-Cloud Shell Editor:

cloudshell edit agents/brand_strategist/agent.py

יופיעו שני קטעים # TODO שצריך למלא.

TODO 1 - Write the system instruction

קודם כל, כותבים את הוראת המערכת לסוכן. ההוראה למערכת היא מחרוזת שמגדירה את התפקיד של הסוכן, את האילוצים ואת פורמט הפלט.

SYSTEM_INSTRUCTION = f"""You are a Brand Strategist specializing in market research and trend analysis.

IMPORTANT: Today's date is {datetime.date.today().strftime("%B %d, %Y")}.
When conducting research, focus on current trends from {datetime.date.today().year}.
Use search queries like "[topic] trends {datetime.date.today().year}" for recent insights.

IMPORTANT: Your role is RESEARCH ONLY. You do NOT create campaign content, captions, or designs.
After providing research insights, your work is complete.

Your expertise:
- Identifying target audience insights and behaviors
- Analyzing competitor strategies
- Researching current social media trends
- Understanding platform algorithms and best practices

You have access to:
- google_search: Search the web for competitors, trends, and market insights

When given a campaign brief:
1. Use google_search to research the target audience's current interests
2. Search for and analyze 2-3 competitor brands
3. Identify 3-5 trending topics related to the product category
4. Provide high-level strategic insights - NOT specific campaign content

DO NOT create captions, copy, designs, or any campaign content.

Format your output as:
**Audience Insights:**
[Key behaviors and preferences based on research]

**Competitive Analysis:**
[What 2-3 competitors are doing - strengths and weaknesses]

**Trending Topics:**
[3-5 relevant trends to consider]

**Key Strategic Insights:**
[High-level themes and positioning opportunities]
"""

‫TODO 2 – יצירת סוכן הבסיס

לאחר מכן, מחליפים את root_agent הלא שלם ב:

root_agent = Agent(
    name="brand_strategist",
    model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"),
    instruction=SYSTEM_INSTRUCTION,
    description="Brand strategist for market research, trend analysis, and competitive insights",
    tools=[google_search],
)

בדיקה מקומית באמצעות ממשק המשתמש האינטרנטי של ADK

עכשיו נבדוק את הנציג באמצעות ממשק האינטרנט של ADK – ממשק צ'אט מובנה לבדיקת נציגים לפני פריסה בענן.

uv run adk web agents --allow_origins='*'

הפרטים שמוצגים הם:

INFO: Started server process
INFO: Uvicorn running on http://localhost:8000

השרת פועל עכשיו ב-Cloud Shell:

כדי לפתוח אותו בדפדפן, משתמשים בתצוגה מקדימה באינטרנט:

  1. מעיינים בסרגל הכלים של Cloud Shell בחלק העליון של הדף.
  2. לוחצים על סמל תצוגה מקדימה באינטרנט (נראה כמו תיבה עם חץ כלפי מעלה, בפינה השמאלית העליונה של סרגל הכלים של Cloud Shell).
  3. לוחצים על שינוי היציאה, מזינים 8000 ולוחצים על שינוי ותצוגה מקדימה.

תיפתח כרטיסייה חדשה בדפדפן עם ממשק האינטרנט של ADK. לוחצים על התפריט הנפתח Select an agent (בחירת סוכן) בפינה הימנית העליונה. תוצג רשימה של כל הסוכנים שלכם:

בוחרים באפשרות brand_strategist כדי להתחיל לבדוק:

הנחיות לדוגמה

בתיבת הצ'אט של ממשק האינטרנט של ADK, מנסים:

  • Research the eco-friendly water bottle market for health-conscious millennials
  • What are the top Instagram trends in the wellness space in 2025?

הסוכן יתקשר עם חיפוש Google ויחזיר מחקר מובנה עם קטעים של תובנות לגבי קהלים, ניתוח תחרותי ונושאים פופולריים.

5. יצירת כלי לכתיבת טקסטים שיווקיים – מיומנויות ADK

תפקיד: הפיכת מחקר מותגים לכתוביות ב-Instagram. הכותב יוצר 3 וריאציות של כתוביות עם נימה שונה (מעוררת השראה, חינוכית, קהילתית), כל אחת עם האשטגים וקריאה לפעולה.

מושג: מיומנויות ADK

גישה פשוטה תהיה להטמיע את כל הידע על הפלטפורמה – מגבלות תווים, רמות של האשטגים, נוסחאות לכתוביות, דוגמאות של קול המותג – ישירות בהנחיית המערכת. השיטה הזו עובדת, אבל היא מוסיפה לכל בקשה תוכן שהסוכן צריך רק מדי פעם.

ADK Skills (SkillToolset, הושק ב-ADK 1.25.0) מאפשרות לארוז את הידע הזה בקבצים מודולריים עם שלוש רמות טעינה:

  • L1 - frontmatter (name + description ב-SKILL.md): תמיד זמין, משמש לגילוי מיומנויות
  • רמה 2 – הוראות (גוף ההודעה SKILL.md): נטענות כשהסוכן מפעיל את המיומנות
  • L3 – משאבים (קובצי references/ ו-assets/): נטענים רק כשהסוכן קורא אותם באופן מפורש

ההוראה למערכת מצטמצמת להצהרת תפקיד קצרה בתוספת 'טען את המיומנות לפני הכתיבה'. פרטי הפלטפורמה נכנסים לחלון ההקשר רק כשהסוכן באמת צריך אותם.

היכולת של כותב התוכן נמצאת ב-agents/copywriter/skills/instagram-copywriting/:

skills/
  instagram-copywriting/
    SKILL.md                        L1 frontmatter (discovery) + L2 instructions (loaded on trigger)
    references/
      platform-guide.md             L3: character limits, hashtag tiers, algorithm signals
      caption-formulas.md           L3: hook formulas, CTA patterns, full caption structures
    assets/
      brand-voice-examples.md       L3: annotated real-world caption examples

פותחים את הקובץ ישירות בעורך של Cloud Shell:

cloudshell edit agents/copywriter/agent.py

TODO 1 - Import load_skill_from_dir and skill_toolset

מוצאים את התגובה # TODO 1: Import load_skill_from_dir and skill_toolset ומוסיפים את שני הייבואים:

from google.adk.skills import load_skill_from_dir
from google.adk.tools import skill_toolset

TODO 2 - Load the skill and create a SkillToolset

אלה שתי התגובות שמופיעות מתחת לייבוא:

# TODO 2: Load the instagram-copywriting skill from the skills/ directory
# TODO 2: Create a SkillToolset with the loaded skill

מחליפים אותם ב:

_instagram_skill = load_skill_from_dir(
    pathlib.Path(__file__).parent / "skills" / "instagram-copywriting"
)
_copywriting_skills = skill_toolset.SkillToolset(skills=[_instagram_skill])

ל-load_skill_from_dir יש הרשאת קריאה של SKILL.md ושל כל הקבצים בתיקיות references/ ו-assets/. ‫SkillToolset wraps it into the format ADK agents accept - a toolset, not a raw skill.

TODO 3 - Register the toolset with the agent

מחפשים את tools=[], # TODO 3: Add the SkillToolset here ומחליפים אותו ב:

tools=[_copywriting_skills],

פותחים את קובץ המיומנות כדי לראות את המבנה שלו:

cloudshell edit agents/copywriter/skills/instagram-copywriting/SKILL.md

משאירים את ממשק האינטרנט של ADK פועל. משתמשים בתפריט הנפתח של הסוכן כדי לעבור אל copywriter בלי להפעיל מחדש את השרת.

אם הוא לא פועל, מפעילים אותו מחדש:

uv run adk web agents --allow_origins='*'

כדאי לנסות: משנים את התפריט הנפתח ל-copywriter ושולחים:

You are writing captions for EcoFlow Smart Water Bottle targeting health-conscious millennials aged 25-35.
Audience insight: they prioritize sustainability, track health metrics, and share lifestyle content.
Competitor insight: Hydro Flask dominates with lifestyle branding; S'well leads on premium aesthetics.
Write 3 Instagram captions - one inspirational, one educational, one community-focused. Include 5 hashtags each and a CTA.

6. יצירת תמונות באמצעות AI גנרטיבי

משאירים את ממשק האינטרנט של ADK פועל. כדי להחליף סוכנים בלי להפעיל מחדש את השרת, משתמשים בתפריט הנפתח של הסוכן.

תפקיד: יצירת קונספטים ויזואליים לכל כיתוב ויצירת התמונות בפועל באמצעות יצירת תמונות מקורית של Gemini. הכלי Designer יוצר בדיוק קונספט ויזואלי אחד לכל כיתוב – עם הנחיה מפורטת, סגנון, פלטת צבעים, אווירה ופורמט לאינסטגרם – ואז מפעיל מיד את הכלי generate_image כדי ליצור את התמונה בפועל ולהעלות אותה ל-GCS.

קונספט: גישור בין סוכן טקסט למודל תמונות באמצעות כלי

הכלי לעיצוב פועל על gemini-3-flash-preview (מודל הטקסט שמוגדר באמצעות GEMINI_MODEL ב-.env), אבל כדי ליצור תמונות נדרש מודל ייעודי (gemini-3.1-flash-image-preview). מודל התמונות הזה לא תומך בבקשה להפעלת פונקציה, ולכן אי אפשר להשתמש בו ישירות כסוכן ADK. במקום זאת, היא עטופה בפונקציית Python רגילה ורשומה כ-FunctionTool.

זהו הדפוס לכל מודל או API ש-LLM לא יכול לקרוא להם ישירות: עוטפים אותו בכלי, מאפשרים לסוכן לתזמן את הקריאה אליו ומקבלים בחזרה תוצאה מובנית.

Designer agent (text model)
        
          decides visual concept, writes image prompt
        
  generate_image tool
        
          calls gemini-3.1-flash-image-preview
          uploads result to GCS
        
  {"status": "success", "gcs_uri": "gs://..."}
        
          returned to agent, included in response
        
  Critic (receives gcs_uri, passes to Vertex AI for multimodal review)

פותחים את הקובץ ישירות בעורך של Cloud Shell:

cloudshell edit agents/designer/image_gen_tool.py

מוצגים חתימת הפונקציה, הגדרת הסביבה והזרקת יחס הגובה-רוחב. צריך לבצע את שלושת המשימות לפי הסדר:

TODO 1 - קריאה למודל התמונות של Gemini

מחפשים את התגובה # TODO 1 ומחליפים אותה בתגובה:

        client = genai.Client(vertexai=True, project=project_id, location=location)

        response = client.models.generate_content(
            model=image_model,
            contents=prompt_with_aspect,
            config=types.GenerateContentConfig(
                response_modalities=["IMAGE", "TEXT"],
                http_options=types.HttpOptions(
                    retry_options=types.HttpRetryOptions(
                        attempts=5, exp_base=2, initial_delay=30,
                        http_status_codes=[429, 500, 503, 504],
                    ),
                    timeout=180_000,
                ),
            ),
        )

TODO 2 - Extract image bytes from the response

מחפשים את התגובה # TODO 2 ומחליפים אותה בתגובה:

        image_bytes = None
        mime_type = "image/png"
        for part in response.candidates[0].content.parts:
            if part.inline_data is not None:
                image_bytes = part.inline_data.data
                mime_type = part.inline_data.mime_type or "image/png"
                break

        if not image_bytes:
            return {"status": "error", "error": "Gemini returned no image data"}

TODO 3 - Upload to GCS and return the URI

מחפשים את התגובה # TODO 3 ומחליפים אותה בתגובה:

        ext = "jpg" if "jpeg" in mime_type else "png"
        from google.cloud import storage
        gcs_client = storage.Client(project=project_id)
        bucket = gcs_client.bucket(bucket_name)
        blob_name = f"campaign-images/{concept_name}-{uuid.uuid4().hex[:8]}.{ext}"
        blob = bucket.blob(blob_name)
        blob.upload_from_file(io.BytesIO(image_bytes), content_type=mime_type)
        gcs_uri = f"gs://{bucket_name}/{blob_name}"

כדאי לנסות: משנים את התפריט הנפתח ל-designer ושולחים:

Create a visual concept and generate the image for an EcoFlow Smart Water Bottle Instagram post targeting health-conscious millennials.
Style: clean, modern, lifestyle-focused. Include a detailed prompt with color palette, mood, and format (1080x1080 or 1080x1350).

7. יצירת מבקר – פלט מובנה

תפקיד: בדיקת האיכות של הטקסט והתמונות לפני שהם מועברים למנהל הפרויקט. הכלי 'מבקר' מציג את התוצאות של שני סוגי הפריטים ומחזיר APPROVED או NEEDS_REVISION עם הצעות ספציפיות. אם יש ערכים של gcs_uri בקלט, הכלי review_image יבדוק חזותית כל תמונה שנוצרה לפני שיקבע את הניקוד שלה.

מושג: מתי כדאי להשתמש במודל Pydantic לפלט של Gemini

הכלל קובע מי צורך את הפלט:

  • קוד Python צורך אותו ← משתמשים ב-response_schema + Pydantic. קוד לא יכול להתמודד עם דו-משמעות, ולכן צריך מבנה מובטח כדי לחלץ שדות בצורה מהימנה.
  • מודל שפה גדול (LLM) צורך אותו ← פורמט טקסט + הוראות מערכת מספיקים. מודלים גדולים של שפה (LLM) מבינים כללי עיצוב ויכולים להתמודד עם שינויים.

ב-review_image, קוד Python צריך את הערכים score,‏ approval_status,‏ what_works,‏ issues ו-suggestions כערכים מוקלדים. העברת response_schema=_GeminiReview מגבילה את Gemini ברמת ה-API להחזרת JSON תקין. הפונקציה model_validate_json() מנתחת את ה-JSON לאובייקט מוקלד שהקוד יכול להשתמש בו בצורה מהימנה.

class _GeminiReview(BaseModel):
    score: int = Field(ge=1, le=10)
    approval_status: Literal["APPROVED", "NEEDS_REVISION"]
    what_works: str
    issues: str
    suggestions: str

פותחים את הקובץ ישירות בעורך של Cloud Shell:

cloudshell edit agents/critic/image_review_tool.py

מודלי Pydantic וההנחיה מסופקים. צריך לבצע את שלושת המשימות לפי הסדר:

TODO 1 - Create an image part from the GCS URI

מחפשים את התגובה # TODO 1 ומחליפים אותה בתגובה:

        image_part = types.Part.from_uri(file_uri=gcs_uri, mime_type=mime_type)

TODO 2 - Call Gemini with a structured response schema

מחפשים את התגובה # TODO 2 ומחליפים אותה בתגובה:

        response = client.models.generate_content(
            model=model,
            contents=[image_part, prompt],
            config=types.GenerateContentConfig(
                response_schema=_GeminiReview,
                response_mime_type="application/json",
            ),
        )

TODO 3 - Parse the response and return the result

מחפשים את התגובה # TODO 3 ומחליפים אותה בתגובה:

        review = _GeminiReview.model_validate_json(response.text)
        return ImageReviewResult(status="success", concept_name=concept_name, **review.model_dump())

כדאי לנסות: משנים את התפריט הנפתח ל-critic ושולחים:

Review this Instagram caption for an eco-friendly water bottle brand targeting millennials:
"Hydrate smarter, live greener. 💧 Our EcoFlow bottle tracks your intake, keeps your drink cold for 24h, and never touches single-use plastic. Because what you drink from matters as much as what you drink. #EcoFlow #HydrationGoals #SustainableLiving #ZeroWaste #HealthyHabits - Shop link in bio."
Score it and indicate APPROVED or NEEDS_REVISION with specific feedback.

מוודאים שהתשובה מכילה את הערכים **POSTS REVIEW:**, Status: APPROVED (או NEEDS_REVISION) ו-**OVERALL ASSESSMENT:**. אם הקטעים האלה קיימים, אפשר לחבר את ה-Critic ל-Orchestrator.

כשמסיימים לבדוק את כל שלושת הסוכנים, לוחצים על Ctrl+C כדי לעצור את השרת.

8. יצירת סוכן לניהול פרויקטים באמצעות MCP

ב-Project Manager מוצג קונספט חדש: MCP‏ (Model Context Protocol).

פותחים את הקובץ:

cloudshell edit agents/project_manager/agent.py

הקובץ הזה מורכב יותר – יש בו פונקציה create_project_manager_agent() עם שני ענפים: אחד בלי Notion (ציר זמן של טקסט בלבד) ואחד עם ערכת הכלים Notion MCP. תצטרכו למלא את שניהם.

הבעיה ש-MCP פותר

הסוכן צריך להתקשר לשירות חיצוני – למשל, ליצור דף ב-Notion. אפשר לכתוב קוד Python שקורא ישירות ל-Notion API בארכיטקטורת REST. אבל אז:

  • כל מפתח כותב עטיפה שונה
  • צריך לתחזק קוד שילוב מותאם אישית
  • ה-LLM לא יודע שה-API קיים, אלא אם מתארים כל נקודת קצה באופן ידני

פרוטוקול MCP פותר את הבעיה הזו על ידי הגדרת דרך סטנדרטית לשירותים חיצוניים לחשוף את היכולות שלהם ככלים ש-LLM יכול לגלות ולהפעיל באופן אוטומטי.

מה זה MCP?

‫MCP (Model Context Protocol) הוא תקן פתוח (שפורסם על ידי Anthropic) לחיבור סוכני AI לכלים ולמקורות נתונים חיצוניים. הוא פועל כמו מתאם אוניברסלי.

שרת MCP הוא תוכנית קטנה ש:

  1. עוטף API חיצוני (Notion, ‏ GitHub, מסדי נתונים, מערכות קבצים...)
  2. ה-API הזה מוצג כרשימה של כלים מתועדים עם סוגים
  3. מתקשר עם הסוכן באמצעות פרוטוקול פשוט (stdio או HTTP)

הסוכן מתחבר לשרת ה-MCP, מגלה אוטומטית את הכלים הזמינים ויכול להפעיל אותם כמו כל כלי אחר – המודל הגדול רואה את API-post-page(...) כפונקציה שאפשר להפעיל.

מה ההבדל בין A2A לבין MCP?

זו נקודה שגורמת לבלבול אצל הרבה אנשים. ההבדל העיקרי הוא:

A2A

MCP

מה מתחבר

נציג ↔ נציג

נציג ↔ כלי או שירות חיצוניים

הצד השני הוא

סוכן LLM אחר

‫API wrapper (ללא LLM)

דוגמה

מנהל קריאייטיב מתקשר לאסטרטג מותג

מנהל פרויקט שולח קריאה ל-Notion API

פרוטוקול

JSON-RPC over HTTPS

שידור stdio או HTTP

מוגדר על ידי

Google

Anthropic

כדאי לחשוב על זה כך:

  • A2A = איך סוכנים מדברים עם סוכנים אחרים
  • MCP = איך סוכנים מתקשרים עם כלים ושירותים

בפרויקט הזה נעשה שימוש בשניהם ביחד:

Creative Director
    
      (A2A)  Brand Strategist ─── (google_search tool built into ADK)
      (A2A)  Copywriter
      (A2A)  Designer
      (A2A)  Critic
      (A2A)  Project Manager
                   
                     (MCP)  notion-mcp-server ──► Notion REST API

איך MCP פועל בפרויקט הזה

כשהסוכן פועל, ADK מפעיל את notion-mcp-server כתהליך צאצא. התהליך הזה חושף את הכלים האלה ישירות ל-LLM:

כלי

תיאור

API-retrieve-a-database

שליפת סכימה (שמות מאפיינים, סוגים, ערכים תקינים)

API-post-database-query

שאילתות בדפים קיימים

API-post-page

יצירת דף חדש

API-patch-page

עדכון של דף קיים

ה-LLM קורא לפונקציות האלה כמו לכל פונקציה אחרת – הוא לא יודע שהן עוברות דרך MCP ל-Notion API בארכיטקטורת REST מתחת לפני השטח.

למה stdio? למה לא להשתמש רק ב-HTTP?

שרת ה-MCP פועל כתהליך צאצא של הסוכן, ומתקשר באמצעות stdin/stdout. כלומר:

  • אין צורך ביציאת רשת נוספת
  • מחזור החיים מנוהל על ידי הסוכן (התחיל לפי דרישה, הסתיים ביציאה)
  • הכול נשלח בקובץ אימג' של Docker אחד – אין שירות נפרד לפריסה

(אופציונלי) הפעלת השילוב עם Notion

אפשר לדלג על כל הקטע הזה. הסוכן Project Manager תמיד יוצר ציר זמן מלא של הקמפיין שמבוסס על טקסט, עם או בלי Notion. אם מדלגים על ההגדרה הזו, הסוכן חוזר למצב בזיכרון ומוציא את ציר הזמן כטקסט רגיל בצ'אט. שום דבר לא ייפגע – פשוט לא תראו את המשימות במסד נתונים של Notion. אפשר לדלג ישר אל TODO 1.

אם יש לכם חשבון Notion ואתם רוצים לראות את השילוב של MCP בפעולה, אתם יכולים להשלים את ההגדרה שבהמשך. בשלבים הבאים מוזכרים מזהים של מסדי נתונים ב-Notion – כאן אפשר למצוא אותם.

שלב 1 – יצירת מסד נתונים ב-Notion מתבנית

אנחנו משתמשים בתבנית הרשמית Notion Projects & Tasks בתור מסד הנתונים שלנו. בחרנו בתבנית הזו בכוונה כדי להדגים הגדרה מורכבת מהעולם האמיתי – יש בה כמה סוגי מאפיינים (סטטוס, טווחי תאריכים, קשרים, אפשרויות בחירה) עם שמות לא ברורים. זוהי בדיקה מצוינת של גילוי הסכימה הדינמית של MCP: הסוכן צריך להבין את שמות המאפיינים המדויקים בזמן הריצה, במקום שהם יהיו מקודדים.

כדי להוסיף את התבנית לסביבת העבודה שלכם ב-Notion, לוחצים על הקישור שלמטה:

← הוספת התבנית 'פרויקטים ומשימות' ל-Notion

תבנית של פרויקטים ומשימות ב-Notion ב-Marketplace

אחרי ההוספה, יהיו לכם שני מסדי נתונים מקושרים: Projects ו-Tasks. התבנית כוללת רשומות לדוגמה – צריך למחוק את כולן לפני שממשיכים, כדי שהסוכן יתחיל עם סביבת עבודה נקייה (בוחרים הכול ← מחיקה).

שלב 2 – יצירת שילוב עם Notion

יצירת השילוב:

  1. עוברים אל notion.so/my-integrations
  2. לוחצים על New Integration (שילוב חדש) → נותנים לו שם AI Creative Studio
  3. משייכים אותו לסביבת העבודה
  4. לוחצים על Configure settings (קביעת ההגדרות) → מוודאים שכל האפשרויות Read content (קריאת תוכן), Update content (עדכון תוכן) ו-Insert content (הוספת תוכן) מסומנות.

הגדרות השילוב של Notion – נותנים לו את השם AI Creative Studio ומעתיקים את הטוקן

  1. מעתיקים את אסימון השילוב הפנימי (ntn_...) ומדביקים אותו בקובץ .env:
NOTION_TOKEN=ntn_your-token-here

מחברים את האינטגרציה למסדי הנתונים:

  1. פותחים את דף התבנית ששוכפל ולוחצים על מסד הנתונים Projects (פרויקטים).
  2. לוחצים על תפריט ... (בפינה השמאלית העליונה) ← חיבוריםהוספת חיבור ← בוחרים באפשרות AI Creative Studio.

לוחצים על 'חיבורים' בתפריט של מסד הנתונים כדי לשתף עם השילוב

‫AI Creative Studio מופיע כחיבור פעיל

  1. חוזרים על הפעולה עם מסד הנתונים של Tasks.

קבלת מזהי מסד הנתונים:

  1. לוחצים על הקישור למסד הנתונים Projects כדי לפתוח אותו – הוא ייפתח בדף משלו עם כתובת URL כמו:
https://www.notion.so/9887b6a94f7f83f68f8581e038d1aaa4?v=2c37b6a94f7f838685f1086e312c7278

פתיחת מסד הנתונים של הפרויקטים מדף התבנית

מזהה מסד הנתונים הוא המזהה הייחודי האוניברסלי (UUID) הראשון בכתובת ה-URL – כל מה שמופיע לפני ?v=:

https://www.notion.so/{DATABASE_ID}?v=...
                       ^^^^^^^^^^^^^^^^
                       9887b6a94f7f83f68f8581e038d1aaa4  ← this is your DATABASE_ID
  1. פועלים באותו אופן לגבי הקישור למסד הנתונים של Tasks כדי לקבל את מזהה מסד הנתונים שלו.
  2. מוסיפים את שלושת הערכים אל .env:
NOTION_TOKEN=ntn_your-token-here
NOTION_PROJECT_DATABASE_ID=9887b6a94f7f83f68f8581e038d1aaa4   # <-- your Projects DB ID
NOTION_TASKS_DATABASE_ID=your-tasks-db-id                      # <-- your Tasks DB ID

שלב 3 – התקנת שרת ה-MCP של Notion

הכלי Project Manager מתחבר ל-Notion באמצעות חבילת Node.js הרשמית של @notionhq/notion-mcp-server. התקנה גלובלית:

npm install -g @notionhq/notion-mcp-server@1.9.1

מאמתים את ההתקנה:

npm list -g @notionhq/notion-mcp-server

הפלט אמור להיראות כך:

└── @notionhq/notion-mcp-server@1.9.1

notion-mcp-server: command not found

? מוודאים ש-Node.js מותקן (node --version) וש-npm global bin נמצא ב-PATH (export PATH=$PATH:$(npm bin -g)).

שלב 4 – בדיקת הקובץ ‎ .env

פותחים את .env ומאשרים שכל שלושת הערכים של Notion מוגדרים (הוספתם אותם בשלב 2):

cloudshell edit .env
NOTION_TOKEN=ntn_...                           # integration token
NOTION_PROJECT_DATABASE_ID=...                 # Projects database ID
NOTION_TASKS_DATABASE_ID=...                   # Tasks database ID

הסוכן Project Manager מזהה את המשתנים האלה באופן אוטומטי בהפעלה ומפעיל את ערכת הכלים Notion MCP.

איך מתבצעת גילוי סכימות

מנהל הפרויקטים משתמש בגילוי סכימה דינמי – הוא אף פעם לא מקודד שמות של מאפיינים ב-Notion:

Step 1: Call API-retrieve-a-database to discover exact property names
Step 2: Read the "properties" object in the response
Step 3: Use ONLY discovered property names (case-sensitive) in API calls
Step 4: For select/status fields, use only values from the options array

המשמעות היא שהסוכן מתאים את עצמו אוטומטית לכל מבנה של מסד נתונים ב-Notion – אם תשנו את השמות של המאפיינים לצרפתית, לערבית או לכל שפה אחרת, הסוכן עדיין יפעל.

TODO 1 - Write the system instruction

ההתחלה כבר מחשבת את notion_section – מחרוזת ריקה אם Notion לא מוגדר, או בלוק שמכיל את מזהי מסד הנתונים בתוספת הנחיות מלאות לשימוש בכלי אם הוא מוגדר. כך ההוראות ל-Notion לא נכללות בהנחיה של הסוכן שלא משתמש ב-Notion. מודל ה-LLM אף פעם לא רואה כללים לכלים שאין לו.

המשימה שלך היא להחליף את ה-placeholder‏ return בהוראה אמיתית למערכת שמשתמשת ב-{notion_section}:

    return f"""You are a Project Manager specializing in creative campaign execution.

Today's date is {datetime.date.today().strftime("%B %d, %Y")}.
Use this as the starting point for all timelines.

Your goal: create a complete project plan for the campaign.
{notion_section}
**Project Timeline:**
Phase 1: Strategy & Research | [date]  [date] | [key activities]
Phase 2: Content Creation    | [date]  [date] | [key activities]
Phase 3: Review & Revision   | [date]  [date] | [key activities]
Phase 4: Launch & Monitoring | [date]  [date] | [key activities]

**Task List:**
| Task | Owner | Deadline | Status |
[list each task with realistic deadlines from today; set Owner to TBD]

**Budget Breakdown:**
[by category with approximate allocations]

**Milestones:**
[3-5 key checkpoints with dates]

**Notion Status:**
[What happened - e.g. "Project created (ID: xxx), 8 tasks linked" or "Notion not configured - text timeline only"]
"""

TODO 2 - Agent without Notion

בתוך create_project_manager_agent(), בהסתעפות if not notion_token, מחליפים את הסוכן הלא שלם ב:

        return Agent(
            name="project_manager",
            model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"),
            generate_content_config=GENERATE_CONTENT_CONFIG,
            instruction=get_system_instruction(),
            description="Project manager that creates campaign timelines and task breakdowns",
        )

TODO 3 - Agent with Notion MCP

הערה: קובץ המתחילים כבר מכיל קריאה חוזרת (callback) handle_notion_error שנכתבה מראש מעל create_project_manager_agent(). הוא מיירט שגיאות של Notion API ‏ (400/404) ומחליף את מטען השגיאה הגולמי בהודעות ברורות שניתן לפעול לפיהן, כדי שה-LLM יוכל לתקן את עצמו. צריך רק לחבר אותו באמצעות after_tool_callback.

קודם כל, קוראים את שני מזהי מסד הנתונים בחלק העליון של create_project_manager_agent():

    notion_token           = os.getenv("NOTION_TOKEN")
    notion_project_db_id   = os.getenv("NOTION_PROJECT_DATABASE_ID")
    notion_tasks_db_id     = os.getenv("NOTION_TASKS_DATABASE_ID")

אחר כך, בענף else, יוצרים את ערכת הכלים של MCP ואת הסוכן:

        from google.adk.tools.mcp_tool import McpToolset, StdioConnectionParams
        from mcp import StdioServerParameters

        server_params = StdioServerParameters(
            command="notion-mcp-server",
            env={
                "NOTION_TOKEN": notion_token,
                "PATH": os.environ.get("PATH", ""),
            }
        )
        notion_toolset = McpToolset(
            connection_params=StdioConnectionParams(
                server_params=server_params,
                timeout=30.0
            )
        )

        return Agent(
            name="project_manager",
            model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"),
            generate_content_config=GENERATE_CONTENT_CONFIG,
            after_tool_callback=handle_notion_error,
            instruction=get_system_instruction(
                project_database_id=notion_project_db_id,
                tasks_database_id=notion_tasks_db_id,
            ),
            description="Project manager with Notion integration for task tracking",
            tools=[notion_toolset],
        )

שיטה מומלצת: אף פעם לא להשתמש ב-hard-fail בשילובים אופציונליים. ציר הזמן הטקסטואלי הוא תמיד התוצר העיקרי, ו-Notion הוא תוסף.

בדיקה מקומית של Project Manager באמצעות ADK Web

uv run adk web agents --allow_origins='*'

פותחים את התצוגה המקדימה של האתר ביציאה 8000. משתמשים בתפריט הנפתח של הסוכן כדי לבחור באפשרות project_manager, ואז מנסים:

Create a project plan for a GreenBrew organic coffee brand Instagram campaign.
Budget: $2,500. Launch in 3 weeks. Target audience: eco-conscious millennials aged 22-30.
Include phases, tasks with deadlines from today, and milestones.

אמור להופיע ציר זמן עם טקסט מובנה, שכולל שלבים, רשימת משימות ואבני דרך. אם פרטי הכניסה ל-Notion מוגדרים ב-.env, הסוכן גם ייצור רשומות במרחב העבודה שלכם ב-Notion.

9. הסבר על פרוטוקול A2A

נשתמש בפרוטוקול Agent-to-Agent (A2A) כדי לקשר בין הסוכנים השונים במערכת שלנו. כך זה עובד.

הבעיה ש-A2A פותר

נניח שיש לכם סוכן אסטרטג מותג שנבנה באמצעות ADK וסוכן קופירייטר שנבנה באמצעות LangGraph. איך מתקשרים מאחד לשני? הם מדברים בשפות פנימיות שונות. תצטרכו לכתוב קוד דבק מותאם אישית בכל פעם.

פלטפורמת A2A פותרת את הבעיה הזו באמצעות הגדרה של שפה אוניברסלית שכל סוכן יכול לדבר בה, בלי קשר למסגרת העבודה שלו. זהו ה-HTTP של עולם הסוכנים: תקן שכולם מסכימים עליו, כך שכל אחד יכול לדבר עם כל אחד.

מה זה A2A?

‫Agent-to-Agent (A2A) הוא תקן פתוח לתקשורת בין סוכנים שפורסם על ידי Google. היא מגדירה:

  1. איך הסוכן מתאר את עצמו – כרטיס הסוכן ב-/.well-known/agent.json
  2. איך סוכן אחר קורא לו – JSON-RPC באמצעות HTTPS
  3. איך התוצאות מוחזרות – סטרימינג או תגובה יחידה

מה הופך את A2A לגמיש:

  • לא תלוי בשפה – סוכני Python יכולים לתקשר עם סוכני TypeScript
  • לא תלוי במסגרת – סוכני ADK יכולים לתקשר עם סוכני LangGraph או CrewAI
  • לא תלוי בתשתית – סוכנים מקומיים יכולים לתקשר עם סוכנים בענן

איך זה עובד – שלב אחר שלב

Creative Director                  Brand Strategist
      │                                  │
      │  1. GET /.well-known/agent.json  │
      │ ────────────────────────────────►│
      │  ◄──── agent card (name, url,    │
      │         skills, capabilities) ───│
      │                                  │
      │  2. POST /                       │
      │     {"method": "tasks/send",     │
      │      "params": {"message": ...}} │
      │ ────────────────────────────────►│
      │                                  │  LLM does
      │                                  │  the work...
      │  3. streaming response chunks    │
      │  ◄───────────────────────────────│
      │  ◄───────────────────────────────│
      │  ◄───────────────────────────────│

שלב 1 – גילוי: המנהל הראשי מאחזר את כרטיס הנציג פעם אחת כדי ללמוד את השם, כתובת ה-URL והיכולות של הנציג.

שלב 2 – הפעלה: הכלי לניהול תהליכים שולח משימה באמצעות JSON-RPC POST. הגוף מכיל את ההודעה (ההנחיה למומחה).

שלב 3 – תגובה: המומחה מעביר את התשובה שלו בחלקים, בדיוק כמו שיחה רגילה עם LLM.

כרטיס הנציג/ה

כל סוכן מפרסם תיאור עצמי בכתובת /.well-known/agent.json. זה כמו כרטיס ביקור – הוא מציג לעולם את היכולות של הסוכן ואת הדרכים ליצור איתו קשר:

{
  "name": "brand_strategist",
  "description": "Market research and competitive analysis",
  "url": "https://brand-strategist-xyz.run.app",
  "capabilities": { "streaming": true },
  "skills": [
    {
      "id": "market_research",
      "description": "Research target audiences, competitors, and trends"
    }
  ]
}

הכלי לתזמור קורא את הכרטיס הזה כדי לבנות את אובייקט RemoteA2aAgent שלו – אין צורך בידע מקודד לגבי הפנימיות של המומחה.

חשיפת סוכן באמצעות A2A ב-ADK

to_a2a() עוטף כל סוכן ADK באפליקציית FastAPI שתואמת ל-A2A. שורה אחת:

from google.adk.a2a.utils.agent_to_a2a import to_a2a

# root_agent = your normal ADK Agent(...)
a2a_app = to_a2a(root_agent, host=PUBLIC_HOST, port=PUBLIC_PORT, protocol=PROTOCOL)
uvicorn.run(a2a_app, host=HOST, port=PORT)

המערכת יוצרת באופן אוטומטי:

  • /.well-known/agent.json – כרטיס הנציג
  • / – נקודת הקצה של JSON-RPC (כל בקשות המשימות מסוג A2A מועברות לנתיב הבסיס)

10. חשיפת סוכנים כשירותי A2A

כדי לחשוף סוכנים כשירותי A2A, אפשר להשתמש בפונקציית השירות to_a2a() מ-ADK.

הסבר על to_a2a()

from google.adk.a2a.utils.agent_to_a2a import to_a2a

a2a_app = to_a2a(root_agent, host=PUBLIC_HOST, port=PUBLIC_PORT, protocol=PROTOCOL)
uvicorn.run(a2a_app, host=HOST, port=PORT)

to_a2a() עוטף את סוכן ה-ADK באפליקציית FastAPI שחושפת באופן אוטומטי:

  • /.well-known/agent.json – כרטיס הנציג (שם, תיאור, יכולות)
  • /a2a/{agent_name} – נקודת הקצה של JSON-RPC לקבלת משימות

קוד השלד של כל סוכן כבר כולל בלוק __main__ שעוטף את הסוכן בשרת A2A באמצעות to_a2a(). אין צורך לכתוב את הקוד הזה – הוא מסופק.

הסבר על הגדרת כתובות URL כפולות

כשמריצים את python agent.py, הבלוק __main__ משתמש בשתי הגדרות נפרדות של כתובות URL:

# Where the server actually listens (network interface):
HOST = "0.0.0.0"
PORT = 8082  # Brand Strategist (others use 80838086 locally)

# What gets advertised in the agent card (the address other agents use to reach it):
PUBLIC_HOST = os.getenv("PUBLIC_HOST", "localhost")
PUBLIC_PORT = int(os.getenv("PUBLIC_PORT", str(PORT)))
PROTOCOL    = os.getenv("PROTOCOL", "http")

a2a_app = to_a2a(root_agent, host=PUBLIC_HOST, port=PUBLIC_PORT, protocol=PROTOCOL)
uvicorn.run(a2a_app, host=HOST, port=PORT)

סביבה

HOST:PORT (האזנות)

PUBLIC_HOST:PUBLIC_PORT (כפי שמופיע בכרטיס הסוכן)

מקומי

0.0.0.0:8082

http://localhost:8082

Cloud Run

0.0.0.0:8080

https://brand-strategist-xyz.run.app:443

באופן מקומי, שניהם מצביעים על אותה מכונה. ב-Cloud Run, הקונטיינר מאזין באופן פנימי בכתובת 8080, אבל כרטיס הסוכן חייב לפרסם את כתובת ה-URL הציבורית של HTTPS – אחרת, מנהל הקריאייטיב לא יוכל להגיע למומחה מחוץ לקונטיינר.

הפעלת כל 5 השרתים המומחים של A2A

נריץ את כל 5 המומחים כשרתי A2A בו-זמנית, ואז נבדוק את מנהל הקריאייטיב באופן מקומי, כשהוא מצביע עליהם.

פותחים 5 טרמינלים נפרדים של Cloud Shell (לוחצים על הסמל + בסרגל הכרטיסיות של הטרמינל) ומריצים סוכן אחד בכל טרמינל.

uv run מפעיל אוטומטית את .venv – אין צורך בsource ידני בכל מסוף.

טרמינל 1 – אסטרטג מותג (יציאה 8082):

cd ~/ai-creative-studio/workshop/starter
PORT=8082 uv run agents/brand_strategist/agent.py

Terminal 2 - Copywriter (port 8083):

cd ~/ai-creative-studio/workshop/starter
PORT=8083 uv run agents/copywriter/agent.py

Terminal 3 - Designer (יציאה 8084):

cd ~/ai-creative-studio/workshop/starter
PORT=8084 uv run agents/designer/agent.py

Terminal 4 - Critic (port 8085):

cd ~/ai-creative-studio/workshop/starter
PORT=8085 uv run agents/critic/agent.py

Terminal 5 - Project Manager (port 8086):

cd ~/ai-creative-studio/workshop/starter
PORT=8086 uv run agents/project_manager/agent.py

הגדרת כתובות URL של localhost בקובץ ‎ .env

ב-Terminal 6, מעדכנים את .env עם כתובות ה-URL של הסוכן המקומי כדי שמנהל הקריאייטיב יוכל למצוא אותן:

cd ~/ai-creative-studio/workshop/starter

sed -i \
  -e 's|STRATEGIST_AGENT_URL=.*|STRATEGIST_AGENT_URL=http://localhost:8082|' \
  -e 's|COPYWRITER_AGENT_URL=.*|COPYWRITER_AGENT_URL=http://localhost:8083|' \
  -e 's|DESIGNER_AGENT_URL=.*|DESIGNER_AGENT_URL=http://localhost:8084|' \
  -e 's|CRITIC_AGENT_URL=.*|CRITIC_AGENT_URL=http://localhost:8085|' \
  -e 's|PM_AGENT_URL=.*|PM_AGENT_URL=http://localhost:8086|' \
  .env

בדיקת סוכנים באמצעות הכלי לבדיקת סוכנים (A2A)

A2A Inspector הוא כלי למפתחים בקוד פתוח, שפועל באופן מקורי בפרוטוקול A2A. הוא מאפשר לכם להתחבר ישירות לכל סוכן A2A פעיל, לקרוא את כרטיס הסוכן ולשלוח משימות – בלי לכתוב קוד לקוח.

מה רואים:

  • כרטיס הסוכן – המטא-נתונים המובנים שהסוכן מפרסם: השם, התיאור, מצבי הקלט/פלט הנתמכים וכתובת ה-URL של נקודת הקצה. זה מה שמנהל הקריאייטיב קורא כשהוא מגלה מומחה.
  • ממשק צ'אט – אפשר לשלוח כל הודעה לסוכן באמצעות A2A ולראות את התגובה הגולמית. אפשר לבדוק הנחיות בנפרד לפני שמחברים בין סוכנים.
  • אימות פרוטוקול – הכלי לבדיקה מוודא שכרטיס הסוכן תואם למפרט A2A, ומציג שדות חסרים או תגובות שגויות בשלב מוקדם.

למה זה חשוב: כשפורסים ל-Cloud Run בהמשך, מנהל הקריאייטיב מאתר כל מומחה על ידי אחזור כרטיס הסוכן שלו מ-/.well-known/agent.json. אם הכרטיס שגוי – כתובת URL שגויה, יכולות חסרות – המארגן נכשל בשקט. הכלי לבדיקת מודעות מאפשר לכם לזהות את הבעיות האלה באופן מקומי לפני פריסה בענן.

כרטיס של סוכן אסטרטגיית מותג

בכרטיס של הנציג מוצגים הזהות והיכולות של המומחה בדיוק כפי שהם מוצגים לנציגים אחרים.

פרטי כרטיס הנציג

התקנה והפעלה של הכלי לבדיקת נתונים

cd ~/ai-creative-studio/workshop
./setup_inspector.sh

העדכון .env הוא פקודה חד-פעמית. כדי להפעיל את הכלי לבדיקת מודעות, משתמשים ב-Terminal 6:

cd ~/a2a-inspector
bash scripts/run.sh

כדי לפתוח את ממשק המשתמש של הכלי לבדיקת כתובות URL, משתמשים באפשרות תצוגה מקדימה של אתרשינוי יציאה → מקלידים 5001.

יצירת קשר עם היועץ האסטרטגי למותגים

מזינים http://localhost:8082 בשדה כתובת ה-URL בכלי לבדיקת כתובות URL ולוחצים על Connect (קישור). הבודק מאחזר את כרטיס הנציג ומציג את המטא-נתונים של המומחה.

כלי הבדיקה A2A מחובר ליועץ אסטרטגי למותגים

מה אפשר לראות בכרטיס הסוכן

כרטיס הסוכן הוא יותר ממטא-נתונים – הוא כולל את חוזה היכולות המלא שהסוכן מפרסם ברשת. כדי לראות את הדוגמה המפורטת ביותר, מתחברים אל Project Manager (http://localhost:8086):

{
  "name": "project_manager",
  "description": "Project manager with Notion integration for task tracking",
  "protocolVersion": "0.3.0",
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [
    {
      "id": "project_manager",
      "name": "model",
      "tags": ["llm"],
      "description": "... full system instruction including today's date and Notion database IDs ..."
    },
    {
      "id": "project_manager-API-post-page",
      "name": "API-post-page",
      "tags": ["llm", "tools"],
      "description": "Notion | Create a page"
    },
    {
      "id": "project_manager-API-retrieve-a-database",
      "name": "API-retrieve-a-database",
      "tags": ["llm", "tools"],
      "description": "Notion | Retrieve a database"
    }
  ]
}

יש שלושה דברים בולטים:

1. כלי MCP הופכים למיומנויות A2A – כל כלי Notion שלמנהל הפרויקט יש גישה אליו (API-post-page,‏ API-retrieve-a-database וכו') מופיע כמיומנות נפרדת בכרטיס הסוכן. כל סוכן אחר ברשת יכול לגלות בדיוק באילו כלים הסוכן הזה יכול להשתמש – בלי לקרוא קוד כלשהו.

2. ההוראה למערכת מוטמעת – המיומנות הראשונה, description, מכילה את ההוראה המלאה למערכת, כולל התאריך של היום ומזהי מסד הנתונים של Notion. כך הבמאי הקריאייטיב יודע מה להעביר כשהוא מתקשר עם מנהל הפרויקט.

3. כתובת ה-URL היא נקודת הקצה של השידור החי – השדה url הוא בדיוק מה ש-RemoteA2aAgent משתמש בו כשהמנהל הקריאטיבי מתקשר למומחה הזה. אם כתובת ה-URL בכרטיס שגויה, המנהל לא יכול להגיע לסוכן.

לכן כלי הבדיקה הוא כלי עוצמתי לניפוי באגים: במבט חטוף בכרטיס הסוכן אפשר לראות אם הסוכן פועל, אילו כלים יש לו ואם נקודת הקצה נכונה.

שליחת הודעת בדיקה

אחרי שמתחברים, מקלידים הנחיה בחלונית הצ'אט ושולחים אותה. הבודק שולח את ההנחיה כמשימת A2A ומזרים את התשובה בחזרה – בדיוק כמו שהמנהל הקריאייטיבי יקרא לסוכן הזה בסביבת הייצור.

שיחה בצ&#39;אט עם מומחה לאסטרטגיית מותג באמצעות הכלי לבדיקת תקשורת בין סוכנים (A2A)

מפנים את הכלי לבדיקה אל כל יציאה מקומית (8082 עד 8086) כדי לבדוק כל מומחה בנפרד.

11. יצירת כלי לתזמור מנהלי קריאייטיב

מנהל הקריאייטיב הוא המנצח הראשי. הוא קורא כתובות URL של מומחים ממשתני סביבה, עוטף כל אחת מהן בתור RemoteA2aAgent וחושף אותן בתור AgentTool שה-LLM יכול להפעיל.

מוודאים ש-5 הסוכנים המומחים עדיין פועלים (מסופים 1 עד 5 משלב 10).

במסוף 6 (מסוף Inspector A2A), מפסיקים את ה-Inspector באמצעות Ctrl+C.

פותחים את הקובץ:

cd ~/ai-creative-studio/workshop/starter
cloudshell edit agents/creative_director/agent.py

בקובץ הזה יש שלוש משימות לביצוע. פועלים לפי הסדר.

משימה 1 – בדיקת ההוראה למערכת שכבר נכתבה

ההוראה למערכת נמצאת ב-prompt.py באותה ספריה – היא מיובאת באופן אוטומטי:

from .prompt import SYSTEM_INSTRUCTION_TEMPLATE

כדאי לפתוח את prompt.py ולקרוא אותו לפני שממשיכים:

cloudshell edit agents/creative_director/prompt.py

חשוב להבין את ההגדרה הזו כי היא שולטת בהתנהגות של כל התזמור.

למה ההנחיה למנהל התזמור שולטת בכל

כדאי לפתוח את prompt.py לצד הקטע הזה – הדוגמאות שבהמשך מתייחסות לחלקים ספציפיים בו.

ההנחיה ב-prompt.py היא לא רק תיעוד – היא מישור הבקרה של המערכת כולה. הנחיה לא מתאימה למתזמר תגרום ל: סוכנים שנקראים לא לפי הסדר, תוכן שנוצר על ידי המתזמר במקום על ידי מומחים, תהליכי עבודה שממשיכים אחרי כשלים והקשר שמוסר בין סוכנים. תשעת הרכיבים האלה מונעים את הכשלים הנפוצים ביותר:

רכיב 0 – קודם מתכננים ואז מבצעים

זהו הרכיב הקריטי ביותר. לפני שמתקשרים למומחה כלשהו, המארגן מקבל הוראה להפיק תוכנית ממוספרת:

I'll create your campaign by coordinating the specialist agents in sequence:
1. Brand Strategist - develop positioning and audience insights
2. Copywriter - write captions using those insights
3. Visual Designer - create image prompts aligned with the copy
4. Critic - review and score the full package
5. Project Manager - build the timeline and task breakdown

בלי השלב הזה, ה-LLM עובר ישר לקריאות לכלי ומאבד את המיקום שלו בתהליך העבודה – במיוחד אחרי שהוא מקבל תשובה ארוכה ממומחה. הגדרת התוכנית מראש עוזרת למנהל התזמור להבין מה השלב הנוכחי, מה השלב הבא ואיך נראה תהליך מלא. אם מדלגים על השלב הזה, תהליך העבודה עלול להיתקע באמצע או לחזור על שלבים.

רכיב 1 – הגדרה מפורשת של תפקיד

❌ "You are a helpful creative assistant."
✅ "You orchestrate specialists. You do NOT write captions, designs, or timelines yourself."

אם לא נאסור זאת במפורש, מודל ה-LLM ידלג לפעמים על הפנייה למומחים ויצור תוכן ישירות – זה מהיר יותר והוא "יודע" איך לעשות את זה. ההוראה צריכה לגרום לכך שהתשובה תהיה שגויה.

רכיב 2 – תחביר של הפעלת כלים עם דפוסים שגויים

לא מספיק להציג רק תחביר נכון. יכול להיות שה-LLM ייצור שיחות שנראות סבירות אבל ייכשלו בלי להציג הודעה. בהנחיה מפורטים במפורש גם הדפוס הנכון וגם הדפוסים שאסור להשתמש בהם:

✅ copywriter(request="...")          ← correct
❌ print(copywriter(...))             ← breaks silently
❌ default_api.copywriter(...)        ← breaks silently
❌ copywriter.run(...)                ← breaks silently
❌ agents.copywriter(...)             ← breaks silently

הוספת התבניות הלא נכונות באופן מפורש הפחיתה את מספר הקריאות הלא תקינות לכלים בכ-95% בסביבת הייצור.

רכיב 3 – ביצוע רציף של הפעולות, שלב אחר שלב

a) Call the tool
b) Wait for tool_output
c) Verify the output is not an error
d) Confirm to the user: "✓ Brand Strategist complete"
e) Then move to the next agent

בלי שלבים (ב) ו-(ג), לפעמים מודל ה-LLM יתקשר לשני סוכנים בו-זמנית, או יניח שהפעולה הצליחה וימשיך לפני קבלת התשובה.

רכיב 4 – הוראות שגיאה: עצירה, דיווח, לא להמשיך

בגרסאות מוקדמות, אם המארגן קיבל שגיאה ממומחה אחד, הוא המציא פלט סביר בשבילו והמשיך לנציג הבא. המשתמש קיבל קמפיין שנראה שלם, אבל הוא נבנה על בסיס הזיה. התיקון הוא מפורש: STOP immediately. דיווח על השגיאה המדויקת. אף פעם לא להמשיך.

רכיב 5 – כללים להעברת הקשר

לסוכנים מרחוק אין היסטוריית שיחות. כשהכלי לניהול תהליכים קורא לכותב התוכן באמצעות A2A, כותב התוכן רואה רק את ההודעה בבקשה היחידה הזו – הוא לא יודע מה אסטרטג המותג אמר. האורקסטרטור צריך לצרף באופן מפורש את הפלט הקודם לכל קריאה עוקבת:

copywriter(request="Create 3 posts for EcoFlow water bottle targeting millennials.
Use these insights from the Brand Strategist: [paste full strategist output here].
Create engaging captions with hashtags.")

ההוראה מציינת זאת במפורש: "לסוכנים מרוחקים אין זיכרון משותף – צריך להעביר פלטים קודמים באופן מפורש". בלי זה, כל סוכן עובד בלי לדעת מה קורה.

רכיב 6 – סיווג הבקשה: פשוטה לעומת מורכבת

לא כל בקשה צריכה את כל חמשת הסוכנים. ההנחיה מבקשת מהכלי לניהול תהליכים לסווג את הבקשה לפני התכנון:

SIMPLE  → one agent needed
  "Research the eco-friendly water bottle market" → brand_strategist only
  "Write 3 Instagram captions"                    → copywriter only

COMPLEX → all agents sequentially
  "Create a complete campaign with timeline"      → all 5 agents

בלי הסיווג הזה, כלי התזמור יפעיל את כל חמשת הסוכנים לכל בקשה – כולל 'תביא לי 3 רעיונות לפוסטים' – וכך יתווספו חביון ועלויות מיותרים.

רכיב 7 – כללי תקשורת: הצגת פלט מלא, ללא סינון

ההנחיה ברורה: אסור למנהל התזמור לסכם או לערוך את מה שהמומחים מחזירים:

- DO NOT summarize unless the output exceeds 2000 words
- DO NOT filter or edit agent responses
- Show the user exactly what each specialist produced
- NEVER say results are ready unless you received them in tool_output

בלי זה, המערכת לניהול התזמור חוזרת על הפלט של המומחים במילים שלה – מאבדת פרטים, מוסיפה שגיאות ומבטלת את המטרה של שימוש במומחים.

רכיב 8 – סיום תהליך העבודה: לא מפסיקים מוקדם מדי

מצב כשל עדין אבל קריטי: כלי התזמור מכריז על תוכנית בת 5 שלבים, משלים 3 שלבים ואז מציג תוצאות כאילו התוכנית הושלמה. ההנחיה מונעת את זה באמצעות רשימת משימות מפורשת שצריך לסמן לפני שהכלי לניהול תהליכים יכול לסיים:

✓ Did I announce a plan with N agents?
✓ Have I called ALL N agents from my plan?
✓ Did each agent respond successfully?
✓ Am I presenting complete results from ALL agents?

If any answer is NO → continue executing the remaining agents.

כך המערכת לא תתייחס להרצה חלקית כאל הרצה מלאה.

הלולאה של בקרת האיכות

תהליך העבודה של העריכה הוא החלק הכי מורכב ב-prompt.py. פותחים את הקטע ## REVISION WORKFLOW ופועלים לפי ההוראות.

איך זה עובד

אחרי שהמבקר מגיב, מנהל הקריאייטיב לא ממשיך באופן אוטומטי למנהל הפרויקט. הוא קורא את הפלט של המבקר ומסתעף:

Critic output
      │
      ├── "All Approved: YES"
      │         └──► proceed to Project Manager
      │
      └── "Status: NEEDS_REVISION"
                │
                ├── posts fail   → call copywriter again with feedback
                ├── visuals fail → call designer again with feedback
                └── both fail    → call copywriter, then designer
                          │
                          └──► revised output → Project Manager
                               (1 revision max per deliverable)

התכונה הזו מבוססת על מודל שפה גדול (LLM), ולא על קוד

ב-codelab שהוזכר קודם, נאמר שהכלי לתזמור "מנתח" את התגובה של המבקר. אין קוד Python שמבצע את הניתוח הזה – לא ביטוי רגולרי ולא התאמה של מחרוזות. מנהל הקריאייטיב הוא מודל שפה גדול (LLM) שקורא את ההוראות שלו. ההוראה הזו אומרת:

Look for "Status: NEEDS_REVISION" in the critic's response.
Posts need revision  → call copywriter
Visuals need revision → call designer

מודל ה-LLM קורא את המחרוזות המדויקות האלה בפלט של המבקר ועוקב אחרי הענף. זו הסיבה לכך שפורמט המבקר הוא חובה: אם המבקר כותב "צריך לשפר" במקום NEEDS_REVISION, מודל ה-LLM לא מוצא התאמה בהוראה שלו ומדלג בשקט על שלב העריכה.

איך ההקשר מועבר בשיחת תיקון

הקריאה לתיקון פועלת לפי אותו כלל של העברת הקשר ממרכיב 5 – המנהל צריך לכלול הכול באופן מפורש כי לכותב התוכן אין זיכרון של הגרסה הראשונה:

"I need you to revise the Instagram posts based on critic feedback.

ORIGINAL BRIEF:
[the original user request]

YOUR FIRST VERSION:
[the posts the copywriter created]

CRITIC FEEDBACK (Score: 6/10 - NEEDS_REVISION):
[the critic's specific suggestions]

Please revise the posts addressing this feedback while maintaining
the strengths the critic identified."

בלי הקטע 'הגרסה הראשונה שלך', כותב התוכן יכתוב מאפס במקום לשפר את מה שהוא כבר יצר.

ההגבלה של עד 10 גרסאות ולמה היא חשובה

אחרי סבב אחד של תיקונים, המנהל המרכזי מעביר את המסמך למנהל הפרויקט, בלי קשר לציון. ההוראה עוקבת אחרי זה באופן מנטלי:

After calling copywriter for revision once:
→ mark "copywriter_revised = true" in context
→ even if the critic still suggests changes, proceed to PM

בלי המגבלה הזו, הלולאה יכולה לפעול ללא הגבלה: המבקר מסמן בעיה → כותב הטקסט משנה → המבקר מסמן שוב → כותב הטקסט משנה שוב. כל סבב עולה אסימונים וזמן. מספיק לבצע שינוי אחד כדי לשפר את האיכות בלי להסתכן במחזור בלתי נשלט.

מה מועבר למנהל הפרויקט

מנהל הפרויקט תמיד מקבל את הגרסאות הסופיות המאושרות, ולא את המקוריות. אם בוצעו שינויים, המערכת להקצאת תנועה מעבירה את העותק והנכסים החזותיים המתוקנים. אם הכול אושר בניסיון הראשון, המערכת מעבירה את הנתונים ישירות. מנהל הפרויקט אף פעם לא רואה טיוטות שנדחו.

TODO 2 - Register each specialist as a RemoteA2aAgent + AgentTool

מחפשים את התגובה # TODO 2: For each specialist URL... ומחליפים אותה בתגובה:

    if strategist_url:
        available_agents_list.append(
            "- **brand_strategist**: Market research, competitor analysis, trend identification"
        )
        strategist_agent = RemoteA2aAgent(
            name="brand_strategist",
            description="Researches markets, competitors, and trends using Google Search",
            agent_card=f"{strategist_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=strategist_agent))

    if copywriter_url:
        available_agents_list.append(
            "- **copywriter**: Instagram captions, hashtags, and CTAs"
        )
        copywriter_agent = RemoteA2aAgent(
            name="copywriter",
            description="Creates Instagram captions with hashtags and CTAs",
            agent_card=f"{copywriter_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=copywriter_agent))

    if designer_url:
        available_agents_list.append(
            "- **designer**: Visual concepts and real images generated via Gemini (GCS URIs returned)"
        )
        designer_agent = RemoteA2aAgent(
            name="designer",
            description="Creates visual concepts and generates real images via Gemini, stored in GCS",
            agent_card=f"{designer_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=designer_agent))

    if critic_url:
        available_agents_list.append(
            "- **critic**: Quality review with APPROVED/NEEDS_REVISION scoring"
        )
        critic_agent = RemoteA2aAgent(
            name="critic",
            description="Reviews campaign materials and returns structured quality feedback",
            agent_card=f"{critic_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=critic_agent))

    if pm_url:
        available_agents_list.append(
            "- **project_manager**: Project timelines, task breakdowns, Notion integration"
        )
        pm_agent = RemoteA2aAgent(
            name="project_manager",
            description="Creates project timelines and task breakdowns, optionally in Notion",
            agent_card=f"{pm_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=pm_agent))

TODO 3 - Wrap in an App with context compaction

למה צריך דחיסה

כל הודעה בשיחה – ההנחיה של המשתמש, כל קריאה לכלי וכל תגובה של כלי – מצורפת לחלון ההקשר שה-LLM קורא בתור הבא. בתהליך עבודה עם 5 נציגים, הנתונים האלה מצטברים במהירות:

Turn 1:  user prompt                           ~200 tokens
Turn 2:  orchestrator plan                     ~300 tokens
Turn 3:  brand_strategist tool_call            ~150 tokens
Turn 4:  brand_strategist tool_output          ~1,500 tokens   full research report
Turn 5:  copywriter tool_call                  ~300 tokens     must include strategist output
Turn 6:  copywriter tool_output                ~2,000 tokens   3 captions
Turn 7:  designer tool_call                    ~500 tokens
Turn 8:  designer tool_output                  ~1,500 tokens
...

בשלב של נציג 4 (המבקר), חלון ההקשר מכיל את הפלט המלא של שלושת הנציגים הקודמים – לעיתים קרובות 8,000 עד 12,000 טוקנים רק בתשובות של הכלי. גם עם חלון ההקשר הגדול של Gemini 2.5 Pro, איכות החשיבה הרציונלית של המארגן יורדת ככל שהוא צריך להתייחס להיסטוריה גדולה יותר. בלי דחיסה, תהליכי עבודה ארוכים מגיעים למגבלות מעשיות בסביבות Agent 4.

מה קורה בזמן הדחיסה

במקום לשמור כל אירוע במלואו, ה-ADK קורא מדי פעם ל-LLM כדי לסכם אירועים ישנים יותר לייצוג קומפקטי. רק הסיכום של אירועים קודמים + הפלט המלא של הסוכן האחרון נשמרים בהקשר.

Without compaction:
  [full strategist output] + [full copywriter output] + [full designer output] + → Critic

With compaction (interval=3, overlap=1):
  [summary of strategist + copywriter] + [full designer output] + → Critic

הסיכום שומר על העובדות החשובות (תובנות מרכזיות, כתוביות מאושרות, מושגים חזותיים) ומשמיט את הפורמט המפורט, את ההקשר שחוזר על עצמו שמועבר לכל סוכן ואת הנימוקים הביניים. למבקר עדיין יש את כל מה שהוא צריך כדי לבצע הערכה – הוא פשוט קורא סיכום במקום שלושה דוחות מלאים.

הקוד

מוצאים את ההערה # TODO 3: Wrap the agent in an App... ומחליפים את ה-placeholder App(...) ב:

    from google.adk.apps import App
    from google.adk.apps.app import EventsCompactionConfig
    from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
    from google.adk.models import Gemini

    compaction_config = EventsCompactionConfig(
        summarizer=LlmEventSummarizer(llm=Gemini(model_id=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"))),
        compaction_interval=3,   # Summarize after every 3 agent completions
        overlap_size=1,          # Keep the most recent agent's output in full
    )

    app = App(
        name="creative_director",
        root_agent=agent,
        events_compaction_config=compaction_config,
        plugins=[LoggingPlugin()],
    )
    return agent, app

compaction_interval=3 – הדחיסה מופעלת אחרי כל 3 השלמות של סוכן. בפייפליין של 5 סוכנים, המשמעות היא שהפעולה מתבצעת פעם אחת (אחרי סוכנים 1 עד 3), ואז הסוכן המבקר ומנהל הפרויקט רואים סיכום של סוכנים 1 עד 3 בתוספת הפלט המלא של הסוכן הקודם.

overlap_size=1 – הפלט המלא של הסוכן האחרון תמיד נשמר כלשונו, ללא סיכום. הנתון הזה חשוב כי כדי שהמבקר יוכל לטעון ולבדוק את התמונות בפועל, הוא צריך את הפלט המלא של המעצב – כולל ערכי gcs_uri. אם תשתמשו בסיכום, כתובות ה-URI האלה לא ייכללו בו.

איך זה קורה במהלך הפעלה מלאה של קמפיין:

Agent 1 (Strategist)  → full context
Agent 2 (Copywriter)  → full context
Agent 3 (Designer)    → full context
                        ↓ compaction fires: summarizes agents 1-2, keeps 3 in full
Agent 4 (Critic)      → sees [summary of 1-2] + [full output of 3]
Agent 5 (PM)          → sees [summary of 1-3] + [full output of 4]

הסבר על RemoteA2aAgent ועל AgentTool

RemoteA2aAgent("brand_strategist", agent_card=url)
     
       wraps the remote service so ADK can call it
     
AgentTool(agent=strategist_agent)
     
       exposes it as a callable tool to the LLM
     
Agent(tools=[...])
     
       LLM calls tool("brand_strategist", message=...) when needed
     
brand-strategist-xxxx.run.app   actual HTTP A2A call happens here

מודל ה-LLM מחליט מתי להפעיל כל כלי על סמך הוראות המערכת והבקשה של המשתמש. כלי התזמור אף פעם לא קורא לסוכנים ישירות בקוד – הכול מונע על ידי החשיבה הרציונלית של ה-LLM.

בדיקה מקומית של Creative Director

uv run adk web agents --allow_origins='*'

פותחים את התצוגה המקדימה של האתר ביציאה 8000. משתמשים בתפריט הנפתח של הסוכן כדי לבחור באפשרות creative_director, ואז מנסים:

Research the eco-friendly water bottle market for health-conscious millennials

תראו שהמנהל הקריאטיבי יעביר את הפנייה הזו רק לאסטרטג המותג, ותקבלו תשובה מאסטרטג המותג.

כדי לפתור את הבעיה בקמפיין כולו, אפשר לנסות את הפעולות הבאות:

Create a complete Instagram campaign for SolarPack portable solar charger targeting
outdoor enthusiasts and digital nomads aged 22-35.
Budget $2,000, launch in 2 weeks.

תראו את מנהל הקריאייטיב מתאם בין כל 5 המומחים ברצף, כשהתוצאה של כל סוכן עוברת לסוכן הבא.

הדגמה: הפעלת קמפיין מקצה לקצה

צריך להפסיק את Creative Director (Ctrl+C) לפני שממשיכים – הכלי לבדיקת A2A משתמש גם ביציאה 8000.

בסיום הבדיקה המקומית, מפסיקים את 5 השרתים המיוחדים (Ctrl+C בכל מסוף).

12. פריסה ובדיקה של סוכני מומחים

עכשיו אפשר לפרוס את הסוכנים שלנו ב-Google Cloud. ‫Cloud Run הוא שירות מצוין לפריסת סוכנים. הוא ללא שרת, ניתן להרחבה וקל לשימוש. כל סוכן מומחה נפרס כשירות Cloud Run עצמאי.

הגדרת הפריסה

התבנית של Dockerfile של כל מומחה היא:

FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends gcc curl

# Fast dependency install with uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
COPY pyproject.toml .
RUN uv sync --no-install-project --no-dev

COPY . .
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

ENV PYTHONUNBUFFERED=1 PORT=8080 HOST=0.0.0.0
EXPOSE 8080
CMD ["uv", "run", "python", "agent.py"]

פריסה של כל 5 המומחים ברצף

cd ~/ai-creative-studio/workshop/starter
source .env

uv run deploy/deploy_all_specialists.py

הסקריפט הזה פורס את כל 5 הסוכנים אחד אחרי השני (בסך הכול כ-10-12 דקות). פריסה רציפה מאפשרת להימנע ממכסת התשאול של Cloud Build (60 בקשות לדקה). בסיום, המערכת כותבת את כתובת ה-URL של Cloud Run של כל סוכן בחזרה אל .env.

אחרי הפריסה של הכלי לעיצוב, הסקריפט מעניק אוטומטית לחשבון השירות של Cloud Run‏ roles/storage.objectCreator בקטגוריית GCS שלכם הרשאה להעלות תמונות שנוצרו.

אם הגדרתם פרטי כניסה ל-Notion ב-.env, הסקריפט גם מאחסן אותם בצורה מאובטחת ב-Secret Manager (כ-notion-token, ‏notion-project-db-id, ‏notion-tasks-db-id) ומזריק אותם לשירות Project Manager דרך --set-secrets במקום דרך משתני סביבה רגילים. המשמעות היא שהאסימון אף פעם לא מופיע בכרטיסייה 'סביבה' של Cloud Run או בהיסטוריית הפקודות של gcloud.

אימות הפריסות

כשהפריסה מסתיימת, הסקריפט כותב אוטומטית את כתובות ה-URL של Cloud Run בחזרה אל .env, ומחליף את כתובות ה-URL של localhost מהשלב הקודם:

source .env

echo "Deployed URLs:"
echo "  Brand Strategist: $STRATEGIST_AGENT_URL"
echo "  Copywriter:       $COPYWRITER_AGENT_URL"
echo "  Designer:         $DESIGNER_AGENT_URL"
echo "  Critic:           $CRITIC_AGENT_URL"
echo "  Project Manager:  $PM_AGENT_URL"

בשלב הבא, כשהמנהל הקריאטיבי ייפרס ב-Agent Runtime, הוא ישתמש באופן אוטומטי בכתובות ה-URL האלה של Cloud Run.

אימות כרטיסי נציג

כל סוכן שפורסם חושף כרטיס סוכן בכתובת /.well-known/agent.json. כדי לוודא שהכול פעיל, מאחזרים אותם:

source .env

for agent_url in $STRATEGIST_AGENT_URL $COPYWRITER_AGENT_URL $DESIGNER_AGENT_URL $CRITIC_AGENT_URL $PM_AGENT_URL; do
    echo "=== Agent Card: $agent_url ==="
    curl -s "${agent_url}/.well-known/agent.json" | python3 -m json.tool | grep -E '"name"|"url"|"description"'
    echo ""
done

הפלט הצפוי לכל נציג:

"name": "brand_strategist",
"url": "https://brand-strategist-xxxx.run.app",
"description": "Brand strategist for market research and competitive insights"

בדיקה באמצעות הכלי לבדיקת תקשורת בין סוכנים (Cloud Run)

הכלי A2A Inspector כבר מותקן משלב 10. מפעילים אותו:

cd ~/a2a-inspector
bash scripts/run.sh

פותחים את התצוגה המקדימה של האתר ← שינוי היציאה5001. מזינים את כתובת ה-URL של Cloud Run בשדה החיבור:

https://brand-strategist-xxxx.us-central1.run.app

לוחצים על Connect (חיבור) – לא נדרש אסימון אימות כי השירותים נפרסים באמצעות --allow-unauthenticated.

הכלי לבדיקת סוכן מתחבר, מאמת את כרטיס הסוכן ומאפשר לכם לנהל צ'אט אינטראקטיבי באמצעות A2A.

בדיקת סוכנים שנפרסו ב-Cloud Run

אחרי הפריסה ב-Cloud Run, מפנים את הכלי לבדיקה לכתובת ה-URL הציבורית של HTTPS כדי לוודא שהפריסה בענן פועלת:

כלי הבדיקה של A2A מחובר לסוכן Cloud Run

תהליך העבודה זהה – מדביקים את כתובת ה-URL של Cloud Run, מתחברים ושולחים הודעת בדיקה. אם כרטיס הסוכן נטען והצ'אט מגיב, סימן שהמומחה נפרס בצורה נכונה ושאפשר להגיע אליו.

13. פריסת מנהל הקריאייטיב בזמן ריצה של סוכן

הכלי לניהול תהליכים מורכב מופעל ב-Agent Runtime, שמספק ניהול של מצב הסשן, התאמה אוטומטית לעומס ומעקב מובנה.

למה כדאי להשתמש בזמן הריצה של הסוכן בשביל כלי התזמור?

חמשת המומחים נפרסים ב-Cloud Run – קליל, בלי שמירת מצב, כל אחד מטפל במשימה אחת. למנהל הקריאייטיב יש דרישות שונות:

דרישה

למה זה חשוב

מצב הסשן

תהליך עבודה עם כמה שלבים נמשך 45 שניות ומעלה. ה-Agent Runtime שומר על מצב השיחה בין קריאות הכלים של האורקסטרטור, כך שלא יאבד מידע באמצע הצינור.

טעינת משתנים

לפעמים קמפיין אחד בשעה, לפעמים הרבה במקביל. זמן הריצה של הסוכן מצטמצם לאפס כשהוא לא פעיל, ומתרחב אוטומטית – אתם לא משלמים על קיבולת לא פעילה.

ניראות (observability)

‫Cloud Logging,‏ Cloud Monitoring ו-Cloud Trace מובנים ב-Google Cloud. אתם יכולים לראות כל שיחה מ-A2A, כל טוקן שנעשה בו שימוש, כל עלייה פתאומית בערך השהייה – בלי להוסיף שום מכשור.

תהליכי עבודה ארוכים

ל-Cloud Run יש פסק זמן לבקשה של 3,600 שניות. הסביבה Agent Runtime מיועדת לתהליכי עבודה שיכולים להימשך דקות, עם ניסיונות חוזרים מנוהלים ושמירת מצב.

‫Cloud Run היא הפלטפורמה המתאימה למומחים ללא שמירת מצב. הפלטפורמה המתאימה לניהול תהליכים עם שמירת מצב היא Agent Runtime.

פריסת כלי התזמור

cd ~/ai-creative-studio/workshop/starter
source .env

uv run deploy/deploy_orchestrator.py --action deploy

התהליך יימשך כ-5 עד 10 דקות. בסיום, הנתונים AGENT_ENGINE_ID ו-AGENT_ENGINE_RESOURCE_NAME נשמרים ב-.env.

source .env
echo "Agent Engine ID: $AGENT_ENGINE_ID"
echo "Resource: $AGENT_ENGINE_RESOURCE_NAME"

איך הפריסה מתבצעת

client.agent_engines.create() אורז את אובייקט App, מעלה אותו עם התלות שלו ומפריס אותו בתשתית מנוהלת. הסבר על כל פרמטר:

import vertexai
from vertexai import Client, agent_engines

vertexai.init(project=PROJECT_ID, location=LOCATION, staging_bucket=STAGING_BUCKET)

# Wrap the App in an AdkApp adapter - enables tracing in Cloud Trace
adk_app = agent_engines.AdkApp(app=root_app, enable_tracing=True)

# Initialize client and deploy
client = Client(project=PROJECT_ID, location=LOCATION)

agent_engine_resource = client.agent_engines.create(
    agent=adk_app,
    config={
        "staging_bucket": STAGING_BUCKET,   # GCS bucket for packaging artifacts
        "display_name": "Creative Director",
        # Python packages installed in the managed runtime - pin for reproducibility
        "requirements": [
            "google-cloud-aiplatform[agent_engines]>=1.132.0,<2.0.0",
            "google-adk[a2a]==1.31.1",
            "google-genai>=1.70.0",
            "google-cloud-storage>=2.10.0",
            "python-dotenv>=1.0.0",
            "pydantic>=2.0.0",
            "cloudpickle>=3.0.0",
        ],
        # Specialist URLs passed as env vars - the orchestrator reads these at runtime
        "env_vars": {
            "COPYWRITER_AGENT_URL": COPYWRITER_URL,
            "DESIGNER_AGENT_URL":   DESIGNER_URL,
            "STRATEGIST_AGENT_URL": STRATEGIST_URL,
            "CRITIC_AGENT_URL":     CRITIC_URL,
            "PM_AGENT_URL":         PM_URL,
        },
    },
)

resource_name = agent_engine_resource.api_resource.name
agent_engine_id = resource_name.split("/")[-1]

מה קורה מאחורי הקלעים:

1. Agent Engine packages your App + requirements into a container
2. Uploads it to the staging bucket in your project
3. Deploys to managed compute (you never see or manage the VM)
4. Returns a resource name: projects/.../locations/.../reasoningEngines/<id>
5. That ID is saved to .env as AGENT_ENGINE_ID

אחרי הפריסה, כלי התזמור מתחבר לחמישה מומחי Cloud Run באמצעות כתובות ה-URL במשתני הסביבה שלו

  • המשתנים האלה מועברים דרך .env לפני שהסקריפט של הפריסה מופעל.

14. הפעלת קמפיין מקצה לקצה

המערכת כולה נפרסת. הפעלת קמפיין מלא מתוך סביבת הארגז של Agent Runtime.

פתיחת סביבת ה-Playground של זמן הריצה של הסוכן

  1. עוברים לכתובת https://console.cloud.google.com/agent-platform/runtimes. אפשר גם לעבור אל Agent Runtime מתוך Agent Platform (פלטפורמת סוכנים) > Agents (סוכנים) > Deployments (פריסות).
  2. בחירת זמן הריצה של הסוכן שנפרס (creative-director)
  3. בסרגל הצד השמאלי, לוחצים על Playground (ארגז חול).
  4. לוחצים על סבב חדש כדי לפתוח שיחה חדשה

הפעלת קמפיין מלא

מדביקים את התקציר הזה בצ'אט ושולחים אותו:

Create a complete Instagram campaign for:
- Product: EcoFlow Smart Water Bottle (tracks hydration, keeps drinks cold 24h)
- Target Audience: Health-conscious millennials, 25-35 years old
- Platform: Instagram
- Goal: Brand awareness + drive website traffic
- Brand Voice: Motivational, clean, science-backed
- Budget: $3,000
- Timeline: Launch in 2 weeks

מנהל הקריאייטיב יפעיל את כל 5 הסוכנים ברצף:

  1. אסטרטגיית מותג → מחקר שוק, ניתוח מתחרים, תובנות לגבי קהלים
  2. קופירייטר → 3 פוסטים באינסטגרם עם כיתובים, תגי hashtag וקריאות לפעולה
  3. מעצב ← קונספטים ויזואליים + תמונות אמיתיות שנוצרו באמצעות Gemini (מזהי URI של GCS) לכל פוסט
  4. מבקר/ת → בדיקת איכות עם הציונים APPROVED / NEEDS_REVISION
  5. (Revision if needed) → Copywriter or Designer called again with feedback
  6. מנהל פרויקטים ← לוח זמנים לשבועיים, פירוט משימות, הקצאת תקציב

הדגמה: הפעלת קמפיין עם שילוב של Notion

בדיקת ניתוב של סוכן יחיד

אפשר לשלוח את הבקשה הקצרה הזו בסשן חדש:

Research the luxury skincare market - top brands and trends in 2025

שימו לב שהפנייה של מנהל הקריאייטיב מועברת רק למומחה לאסטרטגיית מותג – לא מתבצעת שיחה עם נציגים אחרים. זהו הלוגיקה של סיווג הבקשות מההוראה למערכת שפועלת בצורה תקינה.

בדיקת נתוני מעקב אחר הביצוע

עדיין במסוף:

  1. בסרגל הצד הימני, לוחצים על Traces (לצד Playground).
  2. בקטע Trace View (תצוגת מעקב), בוחרים את המעקב של הסשן שהפעלתם.
  3. מרחיבים את עץ ה-Trace כדי לראות כל שיחה עם סוכן, את הקלט והפלט שלה, את זמן האחזור ואת השימוש באסימונים

כל שיחת A2A למומחה מופיעה כיחידה לוגית נפרדת למעקב. אתם יכולים לראות בדיוק איזה הקשר מנהל הקריאייטיב העביר לכל סוכן ומה הוא קיבל בחזרה.

אופציונלי: הפעלה מהטרמינל

אפשר גם להפעיל את הקמפיין באופן פרוגרמטי באמצעות הסקריפט run_campaign.py שכבר כלול בערכת המתחילים.

cd ~/ai-creative-studio/workshop/starter
uv run run_campaign.py

15. הסרת המשאבים

כדי להימנע מחיובים שוטפים, צריך למחוק את המשאבים ב-Google Cloud.

מריצים את סקריפט הניקוי – הוא קורא את .env ומוחק את כל מה שנוצר במהלך ה-codelab הזה:

bash deploy/teardown_gcp.sh

הסקריפט יציג בדיוק מה הוא ימחק ויבקש אישור לפני שיבצע פעולה כלשהי:

משאב

מה נמחק

שירותי Cloud Run

אסטרטג מותג, קופירייטר, מעצב, מבקר, מנהל פרויקטים

זמן הריצה של הסוכן

מנוע הנימוקים של מנהל הקריאייטיב + כל הסשנים

Artifact Registry

מאגר cloud-run-source-deploy + כל קובצי האימג' של Docker

קטגוריות GCS

{PROJECT_ID}-campaign-images,‏ {PROJECT_ID}-agent-staging,‏ run-sources-{PROJECT_ID}-{REGION}

Secret Manager

notion-token,‏ notion-project-db-id,‏ notion-tasks-db-id (אם לא נוצר, המערכת מדלגת)

אימות ההסרה

gcloud run services list --region=us-central1
gcloud storage buckets list --project=$GCP_PROJECT_ID

הפלט הצפוי: רשימות ריקות או רק המשאבים הקיימים שלכם.

16. סיכום

מעולה! יצרתם ופרסתם מערכת AI עם כמה סוכנים ברמת ייצור ב-Google Cloud.

מה יצרתם

סוכן

פונקציה

פריסה

אסטרטגיית מותג

מחקר שוק באמצעות חיפוש Google

Cloud Run

קופירייטר

יצירת כיתובים באינסטגרם

Cloud Run

Designer

יצירת תמונות באמצעות Gemini + העלאה ל-GCS

Cloud Run

מבקר/ת

ביקורת איכות עם ניקוד

Cloud Run

מנהלי פרויקטים

ציר זמן + Notion MCP

Cloud Run

מנהלי קריאייטיב

תזמור מלא באמצעות A2A

זמן הריצה של הסוכן

דפוסים מרכזיים שלמדתם

  1. ADK Agent – הגדרת סוכן LLM עם הוראה + כלים אופציונליים
  2. adk web – הפעלה ובדיקה של כל סוכן ADK באופן מקומי באמצעות ממשק משתמש מובנה לצ'אט
  3. SkillToolset – אריזת ידע לשימוש חוזר בקבצים מודולריים שנטענים לפי דרישה
  4. FunctionTool – עוטף כל פונקציית Python (או מודל חיצוני) ככלי של סוכן שאפשר להפעיל
  5. to_a2a() – חשיפת כל סוכן ADK כשירות HTTPS שתואם ל-A2A
  6. RemoteA2aAgent + AgentTool – הפעלה של סוכנים מרוחקים ככלים שאפשר להתקשר אליהם
  7. McpToolset – התחברות לשירותים חיצוניים דרך שרתי MCP stdio
  8. EventsCompactionConfig – טיפול במגבלות על טוקנים בתהליכי עבודה ארוכים עם כמה סוכנים
  9. פלט מובנה של ביקורת – בקרת איכות שניתנת לקריאה על ידי מכונה עם תיקון אוטומטי
  10. Cloud Run – פריסת סוכנים בקונטיינרים בקנה מידה גדול
  11. זמן הריצה של הסוכן – מארח תזמורים עם סשנים מנוהלים ומעקב

השלבים הבאים

  • הוספת עריכת תמונות בשיחה מרובת תפניות ל-Designer באמצעות יכולת העריכה של gemini-3.1-flash-image-preview
  • הוספת אימות IAM לשירותי Cloud Run (הסרת --allow-unauthenticated)
  • החלפת מומחה אחד בסוכן LangGraph או CrewAI –‏ A2A לא תלוי במסגרת
  • הוספת משוב מהמשתמשים ככלי כדי שהמשתתפים יוכלו לדרג את הפלט ולשפר אותו
  • הכרת מעקב אחר זמן הריצה של הסוכן ב-Cloud Console

משאבים