1. סקירה כללית
בשיעור Codelab הזה נסביר איך ליצור את AI Creative Studio – מערכת מבוזרת מרובת סוכנים שממירה הנחיה יחידה לקמפיין שלם באינסטגרם.
מקלידים משפט אחד. קבלת מחקר קהלים, כתוביות, מושגים חזותיים, עותק שעבר בדיקת איכות וציר זמן מלא של הפרויקט – כל אלה נוצרים על ידי צוות של סוכני AI שמשתפים פעולה.
הסוכנים שתבנו
סוכן | תפקיד |
אסטרטגיית מותג | חיפוש באינטרנט של תובנות לגבי קהלים, ניתוח של המתחרים ומגמות לשנת 2025 |
קופירייטר | כתיבת כיתובים לתמונות באינסטגרם עם תגי hashtag וקריאות לפעולה – מבוסס על מיומנות ADK שמעלה הנחיות לפלטפורמה ונוסחאות לכיתובים לפי דרישה |
Designer | יוצרת מושגים ויזואליים ומפיקה תמונות אמיתיות באמצעות Gemini, שמאוחסנות ב-GCS |
מבקר | העתקה של ביקורות ורכיבים חזותיים – מחזירה |
Project Manager | יצירת ציר זמן של פרויקט ופירוט של המשימות, עם אפשרות לסנכרון עם Notion דרך MCP |
מנהל קריאייטיב | מנהל את כל חמשת המומחים ברצף – אתם נותנים לו הנחיה אחת, והוא מתאם את השאר |
5 הסוכנים נפרסים כמיקרו-שירותים עצמאיים של Cloud Run. הם מתקשרים באמצעות פרוטוקול A2A – תקן פתוח שאינו תלוי בשפה, כך שכל סוכן יכול להתקשר לכל סוכן אחר ללא קשר למסגרת. הבמאי הקריאטיבי פועל ב-Agent Runtime ומתחבר לכל מומחה מרחוק.
ארכיטקטורה

מה תלמדו
- יצירת סוכני LLM באמצעות Google ADK –
Agent, הוראות מערכת וכלים מובנים. - אפשר לארוז ידע של סוכנים לשימוש חוזר בקבצים מודולריים באמצעות 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 בפעם הראשונה, מוצגת בקשה לאמת את החשבון. לוחצים על אימות:

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

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

עכשיו מגדירים את הפרויקט:
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 בסרגל הכלים:


מוודאים שהפרויקט הוגדר בצורה נכונה:
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 | שומר על הזיכרון והמצב בין תורות, מנתב קריאות לכלים ומעביר את התוצאות בחזרה למודל |
זמן ריצה | מפעיל את המערכת כשמפעילים אותו – באופן מקומי באמצעות |
הגדרת הסוכן
כל אחד מ-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
)
שדה | מטרה |
| מזהה ייחודי – משמש לניתוב שיחות על ידי כלי ניהול |
| מודל Gemini שמפעיל את הסוכן |
| הנחיה למערכת – מגדירה את התפקיד, האילוצים ופורמט הפלט של הסוכן |
| סיכום בשורה אחת – המארגן קורא את הסיכום כדי להחליט לאיזה מומחה להתקשר |
| פונקציות שה-LLM יכול להפעיל (מובנות כמו |
איך 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:
כדי לפתוח אותו בדפדפן, משתמשים בתצוגה מקדימה באינטרנט:
- מעיינים בסרגל הכלים של Cloud Shell בחלק העליון של הדף.
- לוחצים על סמל תצוגה מקדימה באינטרנט (נראה כמו תיבה עם חץ כלפי מעלה, בפינה השמאלית העליונה של סרגל הכלים של Cloud Shell).
- לוחצים על שינוי היציאה, מזינים
8000ולוחצים על שינוי ותצוגה מקדימה.
תיפתח כרטיסייה חדשה בדפדפן עם ממשק האינטרנט של ADK. לוחצים על התפריט הנפתח Select an agent (בחירת סוכן) בפינה הימנית העליונה. תוצג רשימה של כל הסוכנים שלכם:
בוחרים באפשרות brand_strategist כדי להתחיל לבדוק:
הנחיות לדוגמה
בתיבת הצ'אט של ממשק האינטרנט של ADK, מנסים:
Research the eco-friendly water bottle market for health-conscious millennialsWhat 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 הוא תוכנית קטנה ש:
- עוטף API חיצוני (Notion, GitHub, מסדי נתונים, מערכות קבצים...)
- ה-API הזה מוצג כרשימה של כלים מתועדים עם סוגים
- מתקשר עם הסוכן באמצעות פרוטוקול פשוט (stdio או HTTP)
הסוכן מתחבר לשרת ה-MCP, מגלה אוטומטית את הכלים הזמינים ויכול להפעיל אותם כמו כל כלי אחר – המודל הגדול רואה את API-post-page(...) כפונקציה שאפשר להפעיל.
מה ההבדל בין A2A לבין MCP?
זו נקודה שגורמת לבלבול אצל הרבה אנשים. ההבדל העיקרי הוא:
A2A | MCP | |
מה מתחבר | נציג ↔ נציג | נציג ↔ כלי או שירות חיצוניים |
הצד השני הוא | סוכן LLM אחר | API wrapper (ללא LLM) |
דוגמה | מנהל קריאייטיב מתקשר לאסטרטג מותג | מנהל פרויקט שולח קריאה ל-Notion API |
פרוטוקול | JSON-RPC over HTTPS | שידור stdio או HTTP |
מוגדר על ידי | 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:
כלי | תיאור |
| שליפת סכימה (שמות מאפיינים, סוגים, ערכים תקינים) |
| שאילתות בדפים קיימים |
| יצירת דף חדש |
| עדכון של דף קיים |
ה-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

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

- מעתיקים את אסימון השילוב הפנימי (
ntn_...) ומדביקים אותו בקובץ.env:
NOTION_TOKEN=ntn_your-token-here
מחברים את האינטגרציה למסדי הנתונים:
- פותחים את דף התבנית ששוכפל ולוחצים על מסד הנתונים Projects (פרויקטים).
- לוחצים על תפריט
...(בפינה השמאלית העליונה) ← חיבורים ← הוספת חיבור ← בוחרים באפשרותAI Creative Studio.


- חוזרים על הפעולה עם מסד הנתונים של Tasks.
קבלת מזהי מסד הנתונים:
- לוחצים על הקישור למסד הנתונים 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
- פועלים באותו אופן לגבי הקישור למסד הנתונים של Tasks כדי לקבל את מזהה מסד הנתונים שלו.
- מוסיפים את שלושת הערכים אל
.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. היא מגדירה:
- איך הסוכן מתאר את עצמו – כרטיס הסוכן ב-
/.well-known/agent.json - איך סוכן אחר קורא לו – JSON-RPC באמצעות HTTPS
- איך התוצאות מוחזרות – סטרימינג או תגובה יחידה
מה הופך את 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 8083–8086 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)
סביבה |
|
|
מקומי |
|
|
Cloud Run |
|
|
באופן מקומי, שניהם מצביעים על אותה מכונה. ב-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 (קישור). הבודק מאחזר את כרטיס הנציג ומציג את המטא-נתונים של המומחה.

מה אפשר לראות בכרטיס הסוכן
כרטיס הסוכן הוא יותר ממטא-נתונים – הוא כולל את חוזה היכולות המלא שהסוכן מפרסם ברשת. כדי לראות את הדוגמה המפורטת ביותר, מתחברים אל 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 ומזרים את התשובה בחזרה – בדיוק כמו שהמנהל הקריאייטיבי יקרא לסוכן הזה בסביבת הייצור.

מפנים את הכלי לבדיקה אל כל יציאה מקומית (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 כדי לוודא שהפריסה בענן פועלת:

תהליך העבודה זהה – מדביקים את כתובת ה-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 של זמן הריצה של הסוכן
- עוברים לכתובת https://console.cloud.google.com/agent-platform/runtimes. אפשר גם לעבור אל Agent Runtime מתוך Agent Platform (פלטפורמת סוכנים) > Agents (סוכנים) > Deployments (פריסות).
- בחירת זמן הריצה של הסוכן שנפרס (
creative-director) - בסרגל הצד השמאלי, לוחצים על Playground (ארגז חול).
- לוחצים על סבב חדש כדי לפתוח שיחה חדשה
הפעלת קמפיין מלא
מדביקים את התקציר הזה בצ'אט ושולחים אותו:
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 הסוכנים ברצף:
- אסטרטגיית מותג → מחקר שוק, ניתוח מתחרים, תובנות לגבי קהלים
- קופירייטר → 3 פוסטים באינסטגרם עם כיתובים, תגי hashtag וקריאות לפעולה
- מעצב ← קונספטים ויזואליים + תמונות אמיתיות שנוצרו באמצעות Gemini (מזהי URI של GCS) לכל פוסט
- מבקר/ת → בדיקת איכות עם הציונים APPROVED / NEEDS_REVISION
- (Revision if needed) → Copywriter or Designer called again with feedback
- מנהל פרויקטים ← לוח זמנים לשבועיים, פירוט משימות, הקצאת תקציב

בדיקת ניתוב של סוכן יחיד
אפשר לשלוח את הבקשה הקצרה הזו בסשן חדש:
Research the luxury skincare market - top brands and trends in 2025
שימו לב שהפנייה של מנהל הקריאייטיב מועברת רק למומחה לאסטרטגיית מותג – לא מתבצעת שיחה עם נציגים אחרים. זהו הלוגיקה של סיווג הבקשות מההוראה למערכת שפועלת בצורה תקינה.
בדיקת נתוני מעקב אחר הביצוע
עדיין במסוף:
- בסרגל הצד הימני, לוחצים על Traces (לצד Playground).
- בקטע Trace View (תצוגת מעקב), בוחרים את המעקב של הסשן שהפעלתם.
- מרחיבים את עץ ה-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 | מאגר |
קטגוריות GCS |
|
Secret Manager | |
אימות ההסרה
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 | זמן הריצה של הסוכן |
דפוסים מרכזיים שלמדתם
- ADK
Agent– הגדרת סוכן LLM עם הוראה + כלים אופציונליים -
adk web– הפעלה ובדיקה של כל סוכן ADK באופן מקומי באמצעות ממשק משתמש מובנה לצ'אט -
SkillToolset– אריזת ידע לשימוש חוזר בקבצים מודולריים שנטענים לפי דרישה -
FunctionTool– עוטף כל פונקציית Python (או מודל חיצוני) ככלי של סוכן שאפשר להפעיל -
to_a2a()– חשיפת כל סוכן ADK כשירות HTTPS שתואם ל-A2A -
RemoteA2aAgent+AgentTool– הפעלה של סוכנים מרוחקים ככלים שאפשר להתקשר אליהם -
McpToolset– התחברות לשירותים חיצוניים דרך שרתי MCP stdio -
EventsCompactionConfig– טיפול במגבלות על טוקנים בתהליכי עבודה ארוכים עם כמה סוכנים - פלט מובנה של ביקורת – בקרת איכות שניתנת לקריאה על ידי מכונה עם תיקון אוטומטי
- Cloud Run – פריסת סוכנים בקונטיינרים בקנה מידה גדול
- זמן הריצה של הסוכן – מארח תזמורים עם סשנים מנוהלים ומעקב
השלבים הבאים
- הוספת עריכת תמונות בשיחה מרובת תפניות ל-Designer באמצעות יכולת העריכה של
gemini-3.1-flash-image-preview - הוספת אימות IAM לשירותי Cloud Run (הסרת
--allow-unauthenticated) - החלפת מומחה אחד בסוכן LangGraph או CrewAI – A2A לא תלוי במסגרת
- הוספת משוב מהמשתמשים ככלי כדי שהמשתתפים יוכלו לדרג את הפלט ולשפר אותו
- הכרת מעקב אחר זמן הריצה של הסוכן ב-Cloud Console