1. סקירה כללית
בשיעור Codelab הזה נסביר איך ליצור את AI Creative Studio – מערכת מרובת סוכנים (MAS) מבוזרת שהופכת הנחיה אחת לקמפיין שלם באינסטגרם.
מקלידים משפט אחד. קבלת מחקר קהלים, כתוביות, מושגים חזותיים, עותק שעבר בדיקת איכות וציר זמן מלא של הפרויקט – והכול נוצר על ידי צוות של סוכני AI שמשתפים פעולה.
הסוכנים שתבנו
סוכן | תפקיד |
אסטרטגיית מותג | חיפוש באינטרנט של תובנות לגבי קהלים, ניתוח מתחרים ומגמות לשנת 2025 |
קופירייטר | כתיבת כיתובים לאינסטגרם עם תגי hashtag וקריאות לפעולה – מבוסס על מיומנות ADK שמעמיסה הנחיות לפלטפורמה ונוסחאות לכיתובים לפי דרישה |
Designer | יוצרת מושגים ויזואליים ומפיקה תמונות אמיתיות באמצעות Gemini, שמאוחסנות ב-GCS |
מבקר | העתקת ביקורות ורכיבים חזותיים – מחזירה |
Project Manager | יוצר ציר זמן של פרויקט ופירוט משימות, עם אפשרות לסנכרון עם Notion דרך MCP |
מנהל קריאייטיב | מארגן את כל חמשת המומחים ברצף – אתם נותנים לו הנחיה אחת, והוא מתאם את השאר |
חמשת הסוכנים נפרסים כמיקרו-שירותים עצמאיים של 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. הגדרת הסביבה
בשיעור Codelab הזה נשתמש ב-Cloud Shell.
מה זה Cloud Shell?
Cloud Shell היא סביבת לינוקס חינמית מבוססת-דפדפן עם כל מה שצריך מותקן מראש: 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 Console בחלונית הימנית. מעתיקים אותו כי תצטרכו אותו בפקודה הבאה:

עכשיו מגדירים את הפרויקט:
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 (פרטי כניסה שמוגדרים כברירת מחדל לאפליקציה) – נפרדים מהאימות של gcloud CLI.
מריצים את הפקודה הבאה פעם אחת:
gcloud auth application-default login
תיפתח כרטיסייה בדפדפן שבה תתבקשו לאשר את הפעולה. לוחצים על אישור. הפרטים שמוצגים הם:
Credentials saved to file: ~/.config/gcloud/application_default_credentials.json
שכפול מאגר התחלתי
בשיעור Codelab הזה נעשה שימוש במאגר התחלתי – פרויקט שלד עם כל התשתית במקום (Dockerfiles, 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.
uv כבר מותקן ב-Cloud Shell. לכל הסוכנים יש את אותן תלותיות ליבה, ולכן מספיק להתקין פעם אחת והוא יעבוד לכל סוכן ב-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. פיתוח ובדיקה של סוכן אסטרטג המותג
נתחיל עם הסוכן הראשון: מומחה אסטרטגיית מותג. זהו סוכן מחקר בלבד שמחפש בחיפוש Google תובנות לגבי קהל היעד, ניתוח מתחרים ונושאים פופולריים.
פותחים את קובץ הסוכן הבסיסי ב-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): תמיד זמין, משמש לגילוי מיומנויות - L2 - instructions (body of
SKILL.md): loaded when the agent triggers the skill - רמה 3 – משאבים (קבצים מסוג
references/ו-assets/): נטענים רק כשהסוכן קורא אותם באופן מפורש
ההוראה למערכת מצטמצמת להצהרת תפקיד קצרה בתוספת "load the skill before writing" (טען את המיומנות לפני הכתיבה). פרטי הפלטפורמה נכנסים לחלון ההקשר רק כשהסוכן באמת צריך אותם.
היכולת של כותב התוכן נמצאת ב-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 עוטף אותו בפורמט שסוכני ADK מקבלים – ערכת כלים, ולא מיומנות גולמית.
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. הכלי 'מעצב' יוצר בדיוק קונספט ויזואלי אחד לכל כיתוב – עם הנחיה מפורטת, סגנון, פלטת צבעים, אווירה ופורמט לאינסטגרם – ואז מפעיל מיד את הכלי generate_image כדי ליצור את התמונה בפועל ולהעלות אותה ל-GCS.
קונספט: חיבור בין סוכן טקסט למודל תמונות באמצעות כלי
הכלי לעיצוב פועל על gemini-3-flash-preview (מודל הטקסט שמוגדר באמצעות GEMINI_MODEL ב-.env), אבל כדי ליצור תמונות נדרש מודל ייעודי (gemini-3.1-flash-image). מודל התמונות הזה לא תומך בהפעלת פונקציות, ולכן אי אפשר להשתמש בו ישירות כסוכן ADK. במקום זאת, הוא עטוף בפונקציית Python רגילה ורשום כ-FunctionTool.
זהו הדפוס לכל מודל או API ש-LLM לא יכול לקרוא להם ישירות: עוטפים אותם בכלי, מאפשרים לסוכן לתזמן את הקריאה אליהם ומקבלים בחזרה תוצאה מובנית.
Designer agent (text model)
│
│ decides visual concept, writes image prompt
▼
generate_image tool
│
│ calls gemini-3.1-flash-image
│ 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 - Call the Gemini image model
מחפשים את התגובה # 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. כדי לדלג, אפשר לעבור ישר למשימה מספר 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 - משייכים אותו לסביבת העבודה
- לוחצים על הגדרת הרשאות ומוודאים שכל האפשרויות קריאת תוכן, עדכון תוכן והוספת תוכן מסומנות.

- מעתיקים את אסימון השילוב הפנימי (
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
מנהל הפרויקטים מתחבר ל-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 - סוכן ללא 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 – תגובה: המומחה מעביר את התשובה שלו בחלקים, בדיוק כמו בשיחה רגילה עם מודל שפה גדול.
כרטיס הנציג/ה
כל סוכן מפרסם תיאור עצמי בכתובת /.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, אבל כרטיס הסוכן חייב לפרסם את כתובת ה-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
במסוף 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
כדי לפתוח את ממשק המשתמש של הכלי לבדיקה, משתמשים באפשרות תצוגה מקדימה של אתר → שינוי יציאה → מקלידים 5001.
יצירת קשר עם היועץ האסטרטגי למותגים
מזינים http://localhost:8082 בשדה כתובת ה-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. יצירת הכלי Creative Director Orchestrator
מנהל הקריאייטיב הוא המנצח הראשי. הוא קורא כתובות URL של מומחים ממשתני סביבה, עוטף כל אחת מהן בתג RemoteA2aAgent וחושף אותן בתור AgentTool שה-LLM יכול להפעיל.
מוודאים ש-5 הסוכנים המומחים עדיין פועלים (מסופים 1 עד 5 משלב 10).
ב-Terminal 6 (מסוף 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 – סיווג הבקשה: פשוטה לעומת מורכבת
לא כל בקשה צריכה את כל חמשת הסוכנים. ההנחיה מורה ל-orchestrator לסווג את הבקשה לפני התכנון:
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 – סיום תהליך העבודה: אף פעם לא מפסיקים מוקדם מדי
מצב כשל עדין אבל קריטי: רכיב ה-Orchestrator מכריז על תוכנית בת 5 שלבים, משלים 3 שלבים ואז מציג את התוצאות כאילו התוכנית הושלמה. ההנחיה מונעת את זה באמצעות רשימת משימות מפורטת שצריך לסמן בה וי לפני שרכיב ה-Orchestrator יכול לסיים:
✓ 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 שמבצע את הניתוח הזה – אין ביטוי רגולרי, אין התאמת מחרוזות. מנהל הקריאייטיב הוא מודל שפה גדול שקורא את ההוראות שלו. ההוראות האלה אומרות:
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))
משימה לביצוע 3 – עטיפה באפליקציה עם דחיסת הקשר
למה צריך דחיסה
כל הודעה בשיחה – ההנחיה של המשתמש, כל קריאה לכלים וכל תגובה של כלים – מצורפת לחלון ההקשר ש-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) לפני שממשיכים – הכלי לבדיקת מודעות מאפליקציה לאפליקציה משתמש גם ביציאה 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 כבר מותקן משלב 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, שמספק ניהול של מצב הסשן, התאמה אוטומטית לעומס ומעקב מובנה.
למה כדאי להשתמש ב-Agent Runtime ככלי תזמור?
חמשת המומחים נפרסים ב-Cloud Run – קליל, בלי שמירת מצב, כל אחד מטפל במשימה אחת. למנהל הקריאייטיב יש דרישות שונות:
דרישה | למה זה חשוב |
מצב הסשן | תהליך עבודה עם כמה שלבים נמשך 45 שניות ומעלה. ה-Agent Runtime שומר על מצב השיחה בין קריאות הכלים של האורקסטרטור, כך שלא יאבד מידע באמצע הצינור. |
טעינת משתנים | לפעמים קמפיין אחד בשעה, לפעמים הרבה במקביל. זמן הריצה של הסוכן מצטמצם לאפס כשהוא לא פעיל, ומתרחב אוטומטית – אתם לא משלמים על קיבולת לא פעילה. |
ניראות (observability) | Cloud Logging, Cloud Monitoring ו-Cloud Trace מובנים ב-Google Cloud. אתם יכולים לראות כל שיחה מ-A2A, כל טוקן שנעשה בו שימוש, כל עלייה חדה בזמן האחזור – בלי להוסיף שום מכשור. |
תהליכי עבודה ארוכים | ל-Cloud Run יש פסק זמן (timeout) של 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 (פריסות).
- בחירת Agent Runtime שנפרס (
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 פוסטים באינסטגרם עם כיתובים, תגי האשטאג וקריאות לפעולה
- מעצב ← קונספטים ויזואליים + תמונות אמיתיות שנוצרו באמצעות 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 | brand-strategist, copywriter, designer, critic, project-manager |
זמן ריצה של סוכן | מנוע הנימוקים של מנהל הקריאייטיב + כל הסשנים |
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 – פריסת סוכנים בקונטיינרים בקנה מידה גדול
- Agent Runtime – תזמור מארחים עם סשנים מנוהלים ומעקב
השלבים הבאים
- הוספת עריכת תמונות רב-שלבית ל-Designer באמצעות יכולת העריכה של
gemini-3.1-flash-image - הוספת אימות IAM לשירותי Cloud Run (הסרת
--allow-unauthenticated) - החלפת מומחה אחד בסוכן LangGraph או CrewAI – A2A לא תלוי במסגרת
- הוספת משוב ממשתמשים ככלי כדי שהמשתתפים יוכלו לדרג את הפלט ולשפר אותו
- הכרת מעקב אחר זמן ריצה של סוכן ב-Cloud Console