Agent Stack של Google בפעולה: ADK,‏ A2A ו-MCP ב-Google Cloud

1. מה תלמדו

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

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

זה הרעיון שיוצג לכם:

דף הכותרת

יסודות עם ADK של Google: מדריך שיעזור לכם לשלוט ביסודות של בניית הסוכן החכם הראשון שלכם באמצעות Agent Development Kit ‏ (ADK) של Google. הסבר על הרכיבים החיוניים, מחזור החיים של הסוכן ואיך להשתמש ביעילות בכלים המובנים של המסגרת.

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

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

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

העברת סוכנים לסביבת ייצור ב-Google Cloud: מעבר מאפליקציות סוכנים מסביבות פיתוח לענן. שיטות מומלצות לתכנון ולפריסה של מערכות מרובות סוכנים (MAS) ב-Google Cloud Platform ‏(GCP). לקבל תובנות לגבי השימוש בשירותי GCP כמו Cloud Run, ולבדוק את היכולות של Google Agent Engine העדכני לאירוח ולניהול של הסוכנים שלכם.

2. ארכיטקטורה

תכנון פוסטים לרשתות החברתיות בעזרת AI עם InstaVibe

מה זה Social Listening?

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

אתם בצוות של InstaVibe

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

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

פתרון מבוסס-סוכן (קונספט אב-טיפוס)

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

תרחיש לדוגמה

  • נציג ליצירת פרופיל חברתי: הנציג הזה משתמש בטכניקות של האזנה לרשתות החברתיות כדי לנתח את הקשרים והאינטראקציות של המשתמשים, ואולי גם מגמות ציבוריות רחבות יותר שקשורות להעדפות המשתמשים. המטרה היא לזהות תחומי עניין משותפים ומאפיינים מתאימים של פעילויות (למשל, העדפות למפגשים שקטים יותר, תחביבים ספציפיים).
  • סוכן לתכנון אירועים: הסוכן הזה משתמש בתובנות מסוכן יצירת הפרופיל ברשתות החברתיות כדי לחפש במקורות אונליין אירועים, מקומות או רעיונות ספציפיים שתואמים לקריטריונים שזוהו (כמו מיקום ותחומי עניין).
  • סוכן אינטראקציה עם הפלטפורמה (באמצעות MCP): הסוכן הזה מקבל את התוכנית הסופית מסוכן תכנון הפעילות. הפונקציה העיקרית שלו היא אינטראקציה ישירה עם פלטפורמת InstaVibe באמצעות כלי MCP (פרוטוקול הקשר של המודל) שהוגדר מראש. הכלי הזה מספק לסוכן את היכולת הספציפית לנסח הצעה לאירוע וליצור פוסט עם תיאור התוכנית.
  • Orchestrator Agent: הנציג הזה פועל כמתאם מרכזי. הוא מקבל את בקשת המשתמש הראשונית מפלטפורמת InstaVibe, מבין את המטרה הכוללת (למשל, תכנן אירוע בשבילי ובשביל החברים שלי"), ואז מעביר משימות ספציפיות לסוכנים המתאימים ברצף הגיוני. הוא מנהל את זרימת המידע בין הסוכנים ומוודא שהתוצאה הסופית מועברת בחזרה למשתמש.

אלמנטים וטכנולוגיות ארכיטקטוניים מרכזיים

ארכיטקטורה

‫Google Cloud Platform ‏ (GCP):

  • Vertex AI:
    • מודלים של Gemini: גישה למודלים גדולים של שפה (LLM) מתקדמים מבית Google, כמו Gemini, שמבוססים על יכולות ההסקה וקבלת ההחלטות של הסוכנים שלנו.
    • ‫Vertex AI Agent Engine: שירות מנוהל שמשמש לפריסה, לאירוח ולשינוי גודל של סוכן התזמור שלנו, ומפשט את תהליך ההעברה לייצור ומפשט את המורכבויות של התשתית.
  • Cloud Run: פלטפורמה ללא שרת (serverless) לפריסת אפליקציות בקונטיינרים. אנחנו משתמשים בו כדי:
    • אירוח אפליקציית האינטרנט הראשית של InstaVibe.
    • פריסה של סוכנים נפרדים עם תכונת A2A (מתכנן, פרופיל ברשתות החברתיות, אינטראקציה עם הפלטפורמה) בתור מיקרו-שירותים עצמאיים.
    • מריצים את שרת הכלי MCP כדי להפוך את ממשקי ה-API הפנימיים של InstaVibe לזמינים לסוכנים.
  • Spanner: מסד נתונים רלציוני מנוהל באופן מלא, מבוזר גלובלית ועקבי מאוד. בסדנה הזו נשתמש ביכולות של BigQuery כמאגר נתונים גרפי באמצעות תכונות השאילתות וה-DDL של GRAPH כדי:
    • מודל לאחסון של קשרים חברתיים מורכבים (משתמשים, חברויות, השתתפות באירועים, פוסטים).
    • לאפשר לשאול שאילתות יעילות לגבי הקשרים האלה לסוכני פרופילים ברשתות חברתיות.
  • Artifact Registry: שירות שמנוהל במלואו לאחסון, לניהול ולאבטחה של קובצי אימג' של קונטיינרים.
  • Cloud Build: שירות להפעלת גרסאות ה-build שפיתחתם ב-Google Cloud. אנחנו משתמשים בו כדי ליצור באופן אוטומטי קובצי אימג' של קונטיינרים של Docker מקוד המקור של הסוכן והאפליקציה שלנו.
  • Cloud Storage: משמש שירותים כמו Cloud Build לאחסון של ארטיפקטים של בנייה, ומשמש את Agent Engine לצרכים התפעוליים שלו.
  • פרוטוקולים ומסגרות של סוכנים מרכזיים:
    • Google's Agent Development Kit (ADK): ה-framework העיקרי ל:
      • הגדרת הלוגיקה, ההתנהגות והוראות הליבה של סוכנים חכמים ספציפיים.
      • ניהול מחזורי החיים, המצב והזיכרון של הסוכן (מצב הפעלה לטווח קצר וידע לטווח ארוך).
      • שילוב כלים (כמו חיפוש Google או כלים בהתאמה אישית) שהסוכנים יכולים להשתמש בהם כדי ליצור אינטראקציה עם העולם.
      • תזמור תהליכי עבודה של כמה סוכנים, כולל ביצוע רציף, לולאה ומקביל של סוכני משנה.
    • פרוטוקול תקשורת Agent-to-Agent‏ (A2A): תקן פתוח שמאפשר:
      • תקשורת ושיתוף פעולה ישירים ותקניים בין סוכני AI שונים, גם אם הם פועלים כשירותים נפרדים או במכונות שונות.
      • סוכנים שיכולים לגלות את היכולות של סוכנים אחרים (באמצעות כרטיסי סוכנים) ולהעביר משימות לטיפול של סוכנים אחרים. השלב הזה חיוני כדי שהנציג שלנו לניהול תהליכים יוכל ליצור אינטראקציה עם הנציגים המומחים לתכנון, לרשתות חברתיות ולפלטפורמות.
    • A2A Python Library ‏ (a2a-python): הספרייה הקונקרטית שמשמשת את סוכני ה-ADK שלנו כדי לתקשר בפרוטוקול A2A. הוא מספק את הרכיבים בצד השרת שנדרשים כדי:
      • חשיפת הסוכנים שלנו כשרתים שתואמים ל-A2A.
      • הטיפול בהצגת 'כרטיס הסוכן' לצורך גילוי מתבצע באופן אוטומטי.
      • לקבל ולנהל בקשות נכנסות למשימות מסוכנים אחרים (כמו Orchestrator).
    • Model Context Protocol (פרוטוקול הקשר של מודל, MCP): תקן פתוח שמאפשר לסוכנים:
      • חיבור לכלים, למקורות נתונים ולמערכות חיצוניים ושימוש בהם בצורה סטנדרטית.
      • הסוכן שלנו לאינטראקציה עם הפלטפורמה משתמש בלקוח MCP כדי לתקשר עם שרת MCP, שבתורו חושף כלים לאינטראקציה עם ממשקי ה-API הקיימים של פלטפורמת InstaVibe.
  • כלי ניפוי באגים:
    • A2A Inspector: הכלי A2A Inspector הוא כלי לניפוי באגים מבוסס-אינטרנט שמשמש לאורך הסדנה הזו כדי להתחבר לסוכנים שלנו עם תמיכה ב-A2A, לבדוק אותם ולקיים איתם אינטראקציה. הוא לא חלק מארכיטקטורת הייצור הסופית, אבל הוא חלק חיוני בתהליך העבודה שלנו לפיתוח. היא כוללת:
      • כלי לצפייה בכרטיס של סוכן: כדי לאחזר ולאמת את היכולות הציבוריות של סוכן.
      • ממשק הצ'אט בשידור חי: כדי לשלוח הודעות ישירות לסוכן שהופעל לצורך בדיקה מיידית.
      • מסוף ניפוי הבאגים: כדי לראות את ההודעות הגולמיות של JSON-RPC שמועברות בין כלי הבדיקה לבין הסוכן.
  • מודלים של שפה (LLM): ה "מוח" של המערכת:
    • מודלים של Gemini מבית Google: אנחנו משתמשים בגרסאות כמו gemini-2.0-flash. המודלים האלה נבחרו לשימוש ב:
      • הסקת מסקנות מתקדמת וביצוע הוראות: היכולת שלהם להבין הנחיות מורכבות, לבצע הוראות מפורטות ולהסיק מסקנות לגבי משימות הופכת אותם למתאימים להפעלת תהליכי קבלת החלטות של סוכנים.
      • שימוש בכלים (הפעלת פונקציות): מודלים של Gemini מצטיינים בקביעה מתי ואיך להשתמש בכלים שסופקו באמצעות ADK, מה שמאפשר לסוכנים לאסוף מידע או לבצע פעולות.
      • יעילות (מודלים מסוג Flash): מודלים מסוג Flash מציעים איזון טוב בין ביצועים לבין עלות-תועלת, והם מתאימים למשימות רבות של סוכנים אינטראקטיביים שדורשות תשובות מהירות.

צריכים קרדיטים ב-Google Cloud?

3. לפני שמתחילים

‫👈 לוחצים על Activate Cloud Shell בחלק העליון של מסוף Google Cloud (זהו סמל הטרמינל בחלק העליון של חלונית Cloud Shell), Cloud Shell

‫👈 לוחצים על הלחצן 'פתיחת הכלי לעריכה' (הוא נראה כמו תיקייה פתוחה עם עיפרון). חלון Cloud Shell Code Editor ייפתח. בצד ימין יופיע סייר הקבצים. Cloud Shell

‫👈 לוחצים על הלחצן Cloud Code Sign-in (כניסה באמצעות קוד בענן) בשורת הסטטוס התחתונה, כמו שמוצג. נותנים הרשאה לפלאגין לפי ההוראות. אם בשורת הסטטוס מופיע Cloud Code - no project, בוחרים באפשרות הזו, ואז בתפריט הנפתח 'Select a Google Cloud Project' בוחרים את הפרויקט הספציפי ב-Google Cloud מתוך רשימת הפרויקטים שיצרתם. Cloud Shell

‫👈 כך מוצאים את מזהה הפרויקט ב-Google Cloud:

  • פותחים את מסוף Google Cloud: https://console.cloud.google.com
  • בוחרים את הפרויקט שבו רוצים להשתמש בסדנה הזו מהתפריט הנפתח של הפרויקט בחלק העליון של הדף.
  • מזהה הפרויקט מוצג בכרטיס Project info בלוח הבקרה

Cloud Shell

‫👈פותחים את הטרמינל בסביבת הפיתוח המשולבת (IDE) בענן, Cloud Shell

‫👈💻 בטרמינל, מוודאים שכבר עברתם אימות ושהפרויקט מוגדר למזהה הפרויקט שלכם באמצעות הפקודה הבאה:

gcloud auth list

‫👈💻 משכפלים את הפרויקט instavibe-bootstrap מ-GitHub:

git clone -b adk-1.2.1-a2a-0.2.7 https://github.com/weimeilin79/instavibe-bootstrap.git
chmod +x ~/instavibe-bootstrap/init.sh
chmod +x ~/instavibe-bootstrap/set_env.sh

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

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

instavibe-bootstrap/
├── agents/
   ├── orchestrate/
   ├── planner/
   ├── platform_mcp_client/
   └── social/
├── instavibe/
   ├── static/
   └── templates/
├── tools/
   └── instavibe/
├── utils/
├── init.sh
└── set_env.sh

ריכזנו כאן את הספריות העיקריות:

  • agents/: זהו הלב של מערכת ה-AI שלנו. כל ספריית משנה (planner/, social/ וכו') מכילה את קוד המקור של סוכן חכם ספציפי.
    • agent.py: בתוך התיקייה של כל סוכן, זהו הקובץ הראשי שבו נמצאת הלוגיקה של הסוכן.
    • a2a_server.py: הקובץ הזה עוטף את סוכן ה-ADK בשרת Agent-to-Agent ‏ (A2A).
    • Dockerfile: מגדיר איך לבנות את קובץ האימג' של הקונטיינר לפריסת הסוכן ב-Cloud Run או ב-Agent Engine.
  • instavibe/: בספרייה הזו נמצא קוד המקור המלא של אפליקציית האינטרנט InstaVibe.
  • tools/: המאגר הזה מיועד לבניית כלים חיצוניים שהסוכנים שלנו יכולים להשתמש בהם.
    • instavibe/ מכיל את שרת Model Context Protocol‏ (MCP).

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

‫👈💻 מריצים את סקריפט האתחול:

הסקריפט יבקש מכם להזין את מזהה הפרויקט ב-Google Cloud.

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

cd ~/instavibe-bootstrap
./init.sh

‫👈💻 מגדירים את מזהה הפרויקט הנדרש:

gcloud config set project $(cat ~/project_id.txt) --quiet

‫👈💻 מריצים את הפקודה הבאה כדי להפעיל את ממשקי ה-API הנדרשים של Google Cloud:

gcloud services enable  run.googleapis.com \
                        cloudfunctions.googleapis.com \
                        cloudbuild.googleapis.com \
                        artifactregistry.googleapis.com \
                        spanner.googleapis.com \
                        apikeys.googleapis.com \
                        iam.googleapis.com \
                        compute.googleapis.com \
                        aiplatform.googleapis.com \
                        cloudresourcemanager.googleapis.com \
                        maps-backend.googleapis.com

‫👈💻 מגדירים את כל משתני הסביבה הנדרשים:

export PROJECT_ID=$(gcloud config get project)
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
export SPANNER_INSTANCE_ID="instavibe-graph-instance"
export SPANNER_DATABASE_ID="graphdb"
export GOOGLE_CLOUD_PROJECT=$(gcloud config get project)
export GOOGLE_GENAI_USE_VERTEXAI=TRUE
export GOOGLE_CLOUD_LOCATION="us-central1"

הגדרת הרשאות

‫👈💻 מתן הרשאות. בטרמינל, מריצים את הפקודה :

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/spanner.admin"

# Spanner Database User
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/spanner.databaseUser"

# Artifact Registry Admin
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/artifactregistry.admin"

# Cloud Build Editor
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/cloudbuild.builds.editor"

# Cloud Run Admin
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/run.admin"

# IAM Service Account User
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/iam.serviceAccountUser"

# Vertex AI User
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/aiplatform.user"

# Logging Writer (to allow writing logs)
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/logging.logWriter"


gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/logging.viewer"


‫👈 מאמתים את התוצאה במסוף IAMCloud Shell

‫👈💻 מריצים את הפקודות הבאות בטרמינל כדי ליצור מאגר Artifact Registry. כל תמונות ה-Docker של הסוכנים שלנו, של שרת ה-MCP ושל אפליקציית InstaVibe מאוחסנות כאן לפני הפריסה ב-Cloud Run או ב-Agent Engine.

export REPO_NAME="introveally-repo"
gcloud artifacts repositories create $REPO_NAME \
  --repository-format=docker \
  --location=us-central1 \
  --description="Docker repository for InstaVibe workshop"

הגדרת Map Platform למפתחות API

כדי להשתמש בשירותים של מפות Google באפליקציית InstaVibe, צריך ליצור מפתח API ולהגביל אותו בהתאם.

‫👈 בכרטיסייה חדשה, עוברים אל APIs & Services > Credentials. בדף 'פרטי התחברות', לוחצים על הלחצן + יצירת פרטי התחברות בחלק העליון. בתפריט הנפתח, בוחרים באפשרות 'מפתח API'. טקסט חלופי

‫👈 תופיע תיבת דו-שיח עם מפתח ה-API החדש שיצרתם. תצטרכו אותו מאוחר יותר כדי להגדיר את האפליקציה.

‫👈 לוחצים על סגירה בתיבת הדו-שיח 'נוצר מפתח API'.

‫👈 מפתח ה-API החדש יופיע ברשימה (לדוגמה, ‫'API key 1'). לוחצים על שלוש הנקודות בצד שמאל ובוחרים באפשרות עריכת מפתח API כדי לפתוח את הדף 'הגבלת מפתח API ושינוי השם שלו'. טקסט חלופי

‫👈 בשדה Name (שם) בחלק העליון, משנים את שם ברירת המחדל ל: Maps Platform API Key (🚨🚨חשוב מאוד🚨🚨 צריך להשתמש בשם הזה!).

Maps Platform API Key

‫👉 בקטע 'הגבלות על אפליקציות', מוודאים שהאפשרות ללא נבחרה.

‫👈 בקטע 'API restrictions' (הגבלות על API), בוחרים בלחצן הבחירה Restrict key (הגבלת המפתח).

‫👈 לוחצים על התפריט הנפתח Select APIs (בחירת ממשקי API). בתיבת החיפוש שמופיעה, מקלידים Maps JavaScript API ובוחרים אותה מהרשימה. טקסט חלופי

‫👈 לוחצים על אישור.

👈 לוחצים על הלחצן 'שמירה' בתחתית הדף.

תוצאת מפתח

יצרתם בהצלחה מפתח API בשם Maps Platform API Key (מפתח API של הפלטפורמה של מפות Google), הגבלתם אותו כך שיתאפשר רק שימוש ב-Maps JavaScript API, ודאגתם שממשק ה-API יופעל בפרויקט שלכם.

4. הגדרת מסד נתונים של גרף

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

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

כך אנחנו נהנים מהיתרונות המשולבים של יכולת ההתאמה לגודל של Spanner, העקביות של העסקאות וממשק ה-SQL המוכר, וגם מהיכולת של שאילתות גרף לנתח את הדינמיקה החברתית המורכבת שחיונית לתכונות מבוססות-AI שלנו.

‫👈💻 בטרמינל של Cloud Shell IDE. הקצאת התשתית הנדרשת ב-Google Cloud. נתחיל ביצירת מופע Spanner, שמשמש כמאגר ייעודי למסדי הנתונים שלנו. אחרי שהמופע יהיה מוכן, ניצור בתוכו את מסד הנתונים בפועל של Spanner, שבו יאוחסנו כל הטבלאות ונתוני הגרף של InstaVibe:

. ~/instavibe-bootstrap/set_env.sh

gcloud spanner instances create $SPANNER_INSTANCE_ID \
  --config=regional-us-central1 \
  --description="GraphDB Instance InstaVibe" \
  --processing-units=100 \
  --edition=ENTERPRISE

gcloud spanner databases create $SPANNER_DATABASE_ID \
  --instance=$SPANNER_INSTANCE_ID \
  --database-dialect=GOOGLE_STANDARD_SQL

‫👈💻 הענקת גישת קריאה/כתיבה ל-Spanner לחשבון השירות שמוגדר כברירת מחדל

echo "Granting Spanner read/write access to ${SERVICE_ACCOUNT_NAME} for database ${SPANNER_DATABASE_ID}..."

gcloud spanner databases add-iam-policy-binding ${SPANNER_DATABASE_ID} \
  --instance=${SPANNER_INSTANCE_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_NAME}" \
  --role="roles/spanner.databaseUser" \
  --project=${PROJECT_ID}

‫👉💻 עכשיו. אנחנו נגדיר סביבה וירטואלית של Python, נתקין את חבילות Python הנדרשות, ואז נגדיר את סכמת מסד הנתונים הגרפי ב-Spanner, נטען אותה עם נתונים ראשוניים ונריץ את הסקריפט setup.py.

. ~/instavibe-bootstrap/set_env.sh
cd ~/instavibe-bootstrap
python -m venv env
source env/bin/activate
pip install -r requirements.txt
cd instavibe
python setup.py

‫👈 בכרטיסייה חדשה בדפדפן, עוברים אל מסוף Google Cloud, אל Spanner, ורואים רשימה של מופעי Spanner. לוחצים על instavibe-graph-instance. מכונת Spanner 👉 בדף הסקירה הכללית של המופע, תוצג רשימה של מסדי נתונים במופע הזה. לוחצים על graphdbמסד נתונים של Spanner

‫👈 בחלונית הניווט הימנית של מסד הנתונים, לוחצים על Spanner Studio spanner studio

‫👉 בעורך השאילתות (כרטיסיית שאילתה ללא שם), מדביקים את שאילתת ה-Graph SQL הבאה. השאילתה הזו תמצא את כל הצמתים מסוג Person ואת קשרי החברות הישירים שלהם עם צמתים אחרים מסוג Person. לוחצים על הפעלה כדי לראות את התוצאה.

Graph SocialGraph
MATCH result_paths = ((p:Person)-[f:Friendship]-(friend:Person))
RETURN SAFE_TO_JSON(result_paths) AS result_paths

spanner graph

‫👈 באותו כלי לעריכת שאילתות, מחליפים את ה-DDL הקודם כדי למצוא אנשים שהשתתפו באותו אירוע, מה שמצביע על קשר עקיף באמצעות פעילות משותפת.

Graph SocialGraph
MATCH result_paths =  (p1:Person)-[:Attended]->(e:Event)<-[:Attended]-(p2:Person)
WHERE p1.person_id < p2.person_id
RETURN SAFE_TO_JSON(result_paths) AS result_paths

spanner graph

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

Graph SocialGraph
MATCH result_paths =  (user:Person {name: "Alice"})-[:Friendship]-(friend:Person)-[:Wrote]->(post:Post)-[:Mentioned]->(mentioned_person:Person)
WHERE user <> mentioned_person AND friend <> mentioned_person -- Avoid self-mentions or friend mentioning themselves in their own post if not intended
RETURN SAFE_TO_JSON(result_paths) AS result_paths

spanner graph

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

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

5. המצב הנוכחי של InstaVibe

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

דף הבית

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

דף האירוע

‫👈💻 חוזרים אל סביבת הפיתוח המשולבת (IDE) של Cloud Shell. מריצים את הסקריפט שלמטה. לאחר מכן, בודקים בקפידה את הפלט כדי לוודא שמפתח GOOGLE_MAPS_API_KEY שמוצג תואם למפתח שיצרתם והעתקתם מ-Google Cloud Console קודם לכן.

. ~/instavibe-bootstrap/set_env.sh
export KEY_DISPLAY_NAME="Maps Platform API Key"

GOOGLE_MAPS_KEY_ID=$(gcloud services api-keys list \
  --project="${PROJECT_ID}" \
  --filter="displayName='${KEY_DISPLAY_NAME}'" \
  --format="value(uid)" \
  --limit=1)

GOOGLE_MAPS_API_KEY=$(gcloud services api-keys get-key-string "${GOOGLE_MAPS_KEY_ID}" \
    --project="${PROJECT_ID}" \
    --format="value(keyString)")

echo "${GOOGLE_MAPS_API_KEY}" > ~/mapkey.txt

echo "Retrieved GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY}"

תוצאה מרכזית

‫👈💻 עכשיו ניצור את קובץ האימג' בקונטיינר לאפליקציית האינטרנט InstaVibe ונעביר אותו בדחיפה למאגר Artifact Registry.

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/instavibe/
export IMAGE_TAG="latest"
export APP_FOLDER_NAME="instavibe"
export IMAGE_NAME="instavibe-webapp"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="instavibe"

gcloud builds submit . \
  --tag=${IMAGE_PATH} \
  --project=${PROJECT_ID}

‫👈💻 פריסת קובץ האימג' החדש של אפליקציית האינטרנט InstaVibe ב-Cloud Run

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/instavibe/
export IMAGE_TAG="latest"
export APP_FOLDER_NAME="instavibe"
export IMAGE_NAME="instavibe-webapp"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="instavibe"

gcloud run deploy ${SERVICE_NAME} \
  --image=${IMAGE_PATH} \
  --platform=managed \
  --region=${REGION} \
  --allow-unauthenticated \
  --set-env-vars="SPANNER_INSTANCE_ID=${SPANNER_INSTANCE_ID}" \
  --set-env-vars="SPANNER_DATABASE_ID=${SPANNER_DATABASE_ID}" \
  --set-env-vars="APP_HOST=0.0.0.0" \
  --set-env-vars="APP_PORT=8080" \
  --set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}" \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
  --set-env-vars="GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}" \
  --project=${PROJECT_ID} \
  --min-instances=1

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

כתובת URL

אפשר גם למצוא את כתובת ה-URL הזו בקטע Cloud Run במסוף Google Cloud, אחרי שבוחרים את שירות instavibe. רשימהכתובת URL

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

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

6. Basic Agent,Event Planner with ADK

ADK Framework

מבוא ל-ADK Framework של Google אחרי שהגדרנו את הבסיס (אפליקציית InstaVibe ומסד הנתונים), אפשר להתחיל לבנות את הסוכן החכם הראשון באמצעות Agent Development Kit (ADK) של Google.

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

בבסיס, ערכת ה-ADK מתבססת על הרעיון של Agent, שמכיל הוראות, הגדרות (כמו מודל השפה שנבחר, למשל ‫Gemini), וקבוצה של Tools שהוא יכול להשתמש בהם כדי לבצע פעולות או לאסוף מידע.

06-agent.png

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

‫👈📝 חוזרים אל סביבת הפיתוח המשולבת של Cloud Shell, ובתיבה ~/instavibe-bootstrap/agents/planner/agent.py מוסיפים את ההנחיה וההוראה הבאות ליצירת הסוכן

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

root_agent = Agent(
    name="planner_agent",
    model="gemini-2.0-flash",
    description="Agent tasked with generating creative and fun dating plan suggestions",
    instruction="""

        You are a specialized AI assistant tasked with generating creative and fun plan suggestions.

        Request:
        For the upcoming weekend, specifically from **[START_DATE_YYYY-MM-DD]** to **[END_DATE_YYYY-MM-DD]**, in the location specified as **[TARGET_LOCATION_NAME_OR_CITY_STATE]** (if latitude/longitude are provided, use these: Lat: **[TARGET_LATITUDE]**, Lon: **[TARGET_LONGITUDE]**), please generate a distinct dating plan suggestions.

        Constraints and Guidelines for Suggestions:
        1.  Creativity & Fun: Plans should be engaging, memorable, and offer a good experience for a date.
        2.  Budget: All generated plans should aim for a moderate budget (conceptually "$$"), meaning they should be affordable yet offer good value, without being overly cheap or extravagant. This budget level should be *reflected in the choice of activities and venues*, but **do not** explicitly state "Budget: $$" in the `plan_description`.
        3.  Interest Alignment:
               Consider the following user interests: **[COMMA_SEPARATED_LIST_OF_INTERESTS, e.g., outdoors, arts & culture, foodie, nightlife, unique local events, live music, active/sports]**. Tailor suggestions specifically to these where possible. The plan should *embody* these interests.
               Fallback: If specific events or venues perfectly matching all listed user interests cannot be found for the specified weekend, you should create a creative and fun generic dating plan that is still appealing, suitable for the location, and adheres to the moderate budget. This plan should still sound exciting and fun, even if it's more general.
        4.  Current & Specific: Prioritize finding specific, current events, festivals, pop-ups, or unique local venues operating or happening during the specified weekend dates. If exact current events cannot be found, suggest appealing evergreen options or implement the fallback generic plan.
        5.  Location Details: For each place or event mentioned within a plan, you MUST provide its name, precise latitude, precise longitude, and a brief, helpful description.
        6.  Maximum Activities: The plan must contain a maximum of 3 distinct activities.

        RETURN PLAN in MARKDOWN FORMAT 
    """,
    tools=[google_search]
)

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

‫👉💻 בואו נתחיל. הפקודות הבאות יפעילו את ממשק המשתמש של ADK DEV:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd  ~/instavibe-bootstrap/agents
sed -i "s|^\(O\?GOOGLE_CLOUD_PROJECT\)=.*|GOOGLE_CLOUD_PROJECT=${PROJECT_ID}|" ~/instavibe-bootstrap/agents/planner/.env
adk web

אחרי שמריצים את הפקודות, אמור להופיע פלט במסוף שמציין שהשרת של ADK Web התחיל לפעול, בדומה לזה:

+-----------------------------------------------------------------------------+
| ADK Web Server started                                                      |
|                                                                             |
| For local testing, access at http://localhost:8000.                         |
+-----------------------------------------------------------------------------+

INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

‫👈 כדי לגשת לממשק המשתמש של ADK Dev מהדפדפן:

בסרגל הכלים של Cloud Shell (בדרך כלל בפינה השמאלית העליונה), לוחצים על סמל התצוגה המקדימה בדפדפן (לרוב נראה כמו עין או ריבוע עם חץ) ובוחרים באפשרות שינוי היציאה. בחלון הקופץ, מגדירים את היציאה ל-8000 ולוחצים על 'שינוי ותצוגה מקדימה'. לאחר מכן, ייפתחו ב-Cloud Shell כרטיסייה או חלון חדשים בדפדפן עם ממשק המשתמש של ADK Dev.

תצוגה מקדימה של אתר

אחרי שפותחים את ממשק המשתמש של ADK Dev בדפדפן: בתפריט הנפתח בפינה השמאלית העליונה של ממשק המשתמש, בוחרים באפשרות planner (מתכנן) כסוכן שאיתו רוצים ליצור אינטראקציה. עכשיו, בתיבת הדו-שיח של הצ'אט בצד שמאל, מנסים לתת לנציג משימה. לדוגמה, לנהל שיחה עם הנציג:

Search and plan something in Seattle for me this weekend
This weekend and I enjoy food and anime

הצעת תאריך (ההעדפה שלכם)

July 12 2025

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

ממשק משתמש לפיתוח של ADK

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

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

Eval

‫👈 בממשק המשתמש של ADK Dev, לוחצים על הכרטיסייה Eval (הערכה) בתפריט הניווט הימני. אמור להופיע קובץ בדיקה שנטען מראש בשם plan_eval. הקובץ הזה מכיל קלט וקריטריונים מוגדרים מראש לבדיקה של סוכן התכנון שלנו.

‫👈 בוחרים תרחיש, כמו boston, ולוחצים על הלחצן Run Evaluation (הפעלת הערכה). בחלון הקופץ שמופיע, מורידים את ציון ההתאמה ל-0.3 ולוחצים על 'התחלה'.

ניקוד התאמה

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

adk dev ui evaluation

‫👈 עכשיו נראה מה קורה עם סף מחמיר יותר. בוחרים בתרחיש 'nyc' ולוחצים שוב על Run Evaluation (הפעלת הערכה). הפעם, משאירים את ערך ההתאמה כערך ברירת המחדל (ציון ההתאמה של התגובה: 0.7) ולוחצים על 'התחלה'. התוצאה תהיה Fail (נכשל). זה צפוי, כי התוצאה היצירתית של הסוכן לא תואמת באופן מושלם לתשובה ה"מושלמת" שהוגדרה מראש.

adk dev ui evaluation fail

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

אחרי שמסיימים לבדוק את ממשק המשתמש ואת ההערכה, חוזרים לטרמינל של Cloud Shell Editor ומקישים על Ctrl+C כדי לעצור את ממשק המשתמש של ADK Dev.

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

‫👉📝 ב-~/instavibe-bootstrap/agents/planner/agent.py, מחפשים את השורה שכתוב בה כרגע RETURN PLAN in MARKDOWN FORMAT במחרוזת ההוראות של הסוכן. מחליפים את השורה הזו במבנה JSON המפורט הבא:

Return your response *exclusively* as a single JSON object. This object should contain a top-level key, "fun_plans", which holds a plan objects. Each plan object in the list must strictly adhere to the following structure:

        --json--
        {
          "plan_description": "A summary of the overall plan, consisting of **exactly three sentences**. Craft these sentences in a friendly, enthusiastic, and conversational tone, as if you're suggesting this awesome idea to a close friend. Make it sound exciting and personal, highlighting the positive aspects and appeal of the plan without explicitly mentioning budget or listing interest categories.",
          "locations_and_activities": [
              {
              "name": "Name of the specific place or event",
              "latitude": 0.000000,  // Replace with actual latitude
              "longitude": 0.000000, // Replace with actual longitude
              "description": "A brief description of this place/event, why it's suitable for the date, and any specific details for the weekend (e.g., opening hours, event time)."
              }
              // Add more location/activity objects here if the plan involves multiple stops/parts
          ]
        }

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

‫👉💻 Relaunch (הפעלה מחדש) של ממשק המשתמש למפתחים של ADK באמצעות אותה פקודה כמו קודם:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd  ~/instavibe-bootstrap/agents
adk web

אם הכרטיסייה כבר פתוחה, מרעננים אותה. לחלופין, אפשר לפעול לפי אותם השלבים שצוינו קודם כדי לפתוח את ממשק המשתמש של ADK Dev בדפדפן (באמצעות תצוגה מקדימה באינטרנט של Cloud Shell ביציאה 8000). אחרי שממשק המשתמש נטען, מוודאים שהסוכן לתכנון נבחר.

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

Plan an event Boston this weekend with art and coffee

בודקים את התשובה של הסוכן בקפידה. במקום תשובה טקסטואלית שהיא שיחה בלבד, עכשיו אמורה להתקבל תשובה שמפורמטת כאובייקט JSON, בהתאם למבנה שהגדרנו בהוראות (כולל fun_plans,‏ plan_description,‏ locations_and_activities וכו'). האישור הזה מעיד שהסוכן יכול עכשיו להפיק פלט מובנה שמתאים לשימוש תוכניתי באפליקציית InstaVibe שלנו.

adk dev ui json

אחרי שמאשרים את פלט ה-JSON, חוזרים לטרמינל של Cloud Shell ומקישים על Ctrl+C כדי לעצור את ממשק המשתמש של ADK Dev.

רכיבים של ADK

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

שיחות משמעותיות עם כמה תפניות דורשות מהסוכנים להבין את ההקשר – לזכור מה נאמר ומה נעשה כדי לשמור על רצף. ‫ADK מספק דרכים מובנות לניהול ההקשר הזה באמצעות Session,‏ State ו-Memory:

  • סשן: כשמשתמש מתחיל אינטראקציה עם סוכן, נוצר סשן. אפשר לחשוב על זה כעל מאגר של שרשור צ'אט ספציפי. הוא מכיל מזהה ייחודי, את היסטוריית האינטראקציות (אירועים), את נתוני העבודה הנוכחיים (מצב) ומטא-נתונים כמו שעת העדכון האחרון.
  • מצב: הזיכרון לטווח קצר של הסוכן, שפועל במסגרת סשן יחיד. זהו מילון שניתן לשינוי, שבו הסוכן יכול לאחסן מידע זמני שנדרש להשלמת המשימה הנוכחית (לדוגמה, העדפות משתמש שנאספו עד עכשיו, תוצאות ביניים מקריאות לכלים).
  • זיכרון: מייצג את הפוטנציאל של הסוכן לזכור מידע לטווח ארוך בין סשנים שונים או גישה למאגרי ידע חיצוניים. ההקשר המיידי של השיחה מטופל על ידי Session ו-State, אבל Memory (שמנוהל לעיתים קרובות על ידי MemoryService) מאפשר לסוכן לאחזר מידע מאינטראקציות קודמות או ממקורות נתונים מובְנים, וכך מספק לו הקשר ידע רחב יותר. (הערה: הלקוח הפשוט שלנו משתמש בשירותים בזיכרון כדי לפשט את התהליך, כלומר הזיכרון או המצב נשמרים רק בזמן שהסקריפט פועל).
  • אירוע: כל אינטראקציה במהלך סשן (הודעת משתמש, תגובת נציג, בקשה לשימוש בכלי, תוצאת כלי, שינוי מצב, שגיאה) מתועדת כאירוע שלא ניתן לשנות. כך נוצר יומן כרונולוגי, שהוא בעצם התמליל והיסטוריית הפעולות של השיחה.

אז איך המדיניות הזו מנוהלת כשסוכן פועל? זו המשימה של הרץ.

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

אנחנו יכולים להשתמש ב-Runner כדי להריץ את הסוכן שלנו כאפליקציית Python עצמאית, בלי קשר לממשק המשתמש למפתחים.

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

‫👈📝 בקובץ ~/instavibe-bootstrap/agents/planner/planner_client.py, מוסיפים את קוד Python הבא מתחת לייבוא הקיים. ב-planner_client.py, בקטע הייבוא, מוסיפים את השורות הבאות:

async def async_main():
  session_service = InMemorySessionService()

  session = await session_service.create_session(
      state={}, app_name='planner_app', user_id='user_dc'
  )

  query = "Plan Something for me in San Francisco this weekend on wine and fashion "
  print(f"User Query: '{query}'")
  content = types.Content(role='user', parts=[types.Part(text=query)])

  root_agent = agent.root_agent
  runner = Runner(
        app_name='planner_app',
        agent=root_agent,
        session_service=session_service,
  )
  print("Running agent...")
  events_async =  runner.run_async(
    session_id=session.id, user_id=session.user_id, new_message=content
  )

  async for event in events_async:
    print(f"Event received: {event}")


if __name__ == '__main__':
  try:
    asyncio.run(async_main())
  except Exception as e:
    print(f"An error occurred: {e}")

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

‫👈💻 עכשיו, מריצים את הסקריפט בצד הלקוח מהטרמינל:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd  ~/instavibe-bootstrap/agents
python -m planner.planner_client

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

Running agent...
Event received: content=Content(parts=[Part(video_metadata=None, thought=None, code_execution_result=None, executable_code=None, file_data=None, function_call=None, function_response=None, inline_data=None, text='```json\n{\n "fun_plans": [\n  {\n   "plan_description": "Embark on a stylish adventure through Hayes Valley, 
...(turncated)
, offering a variety of fashion styles to browse and enjoy."\n    }\n   ]\n  }\n ]\n}\n```')], role='model') grounding_metadata=GroundingMetadata(grounding_chunks=[GroundingChunk(retrieved_context=None, web=GroundingChunkWeb(domain='islands.com', title='islands.com', uri='http
...(turncated)
QyTpPV7jS6wUt-Ix7GuP2mC9J4eY_8Km6Vv44liF9cb2VSs='))], grounding_supports=[GroundingSupport(confide
...(turncated)
>\n', sdk_blob=None), web_search_queries=['..e']) partial=None turn_complete=None error_code=None error_message=None interrupted=None custom_metadata=None invocation_id='e-04d97b8b-9021-47a5-ab41-17b5cbb4bf03' author='location_search_agent' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}) long_running_tool_ids=None branch=None id='CInHdkKw' timestamp=1746978846.232674

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

7. Platform Interaction Agent - interact with MCP Server

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

Model Context Protocol‏ (MCP)

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

איך יוצרים ופורסים את שרת ה-MCP של InstaVibe

07-mcp-server.png

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

נקודת קצה (endpoint)

כתובת URL

שיטת HTTP

תיאור

יצירת פוסט

api/posts

פרסם

נקודת קצה (endpoint) בשביל ה-API להוספת פוסט חדש. צפוי תוכן JSON:
{"author_name": "...", "text": "...", "sentiment": "..." (optional)}

צור אירוע

api/events

פרסם

נקודת קצה (endpoint) בשביל ה-API להוספת אירוע חדש והמשתתפים בו (סכימה פשוטה).
תוכן JSON צפוי: { "event_name": "...", "description": "...", "event_date": "YYYY-MM-DDTHH:MM:SSZ", "locations": [ {"name": "...", "description": "...", "latitude": 0.0, "longitude": 0.0, "address": "..."} ], "attendee_names": ["...", "..."] }

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

‫👈 קודם נטמיע את פונקציית ה-wrapper ליצירת פוסט. פותחים את הקובץ ~/instavibe-bootstrap/tools/instavibe/instavibe.py ומחליפים את ההערה #REPLACE ME CREATE POST בקוד Python הבא:

def create_post(author_name: str, text: str, sentiment: str, base_url: str = BASE_URL):
    """
    Sends a POST request to the /posts endpoint to create a new post.

    Args:
        author_name (str): The name of the post's author.
        text (str): The content of the post.
        sentiment (str): The sentiment associated with the post (e.g., 'positive', 'negative', 'neutral').
        base_url (str, optional): The base URL of the API. Defaults to BASE_URL.

    Returns:
        dict: The JSON response from the API if the request is successful.
              Returns None if an error occurs.

    Raises:
        requests.exceptions.RequestException: If there's an issue with the network request (e.g., connection error, timeout).
    """
    url = f"{base_url}/posts"
    headers = {"Content-Type": "application/json"}
    payload = {
        "author_name": author_name,
        "text": text,
        "sentiment": sentiment
    }

    try:
        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)
        print(f"Successfully created post. Status Code: {response.status_code}")
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error creating post: {e}")
        # Optionally re-raise the exception if the caller needs to handle it
        # raise e
        return None
    except json.JSONDecodeError:
        print(f"Error decoding JSON response from {url}. Response text: {response.text}")
        return None

‫👈📝 בשלב הבא ניצור את פונקציית ה-wrapper עבור API ליצירת אירועים. באותו קובץ ~/instavibe-bootstrap/tools/instavibe/instavibe.py, מחליפים את התגובה #REPLACE ME CREATE EVENTS בקוד הבא:

def create_event(event_name: str, description: str, event_date: str, locations: list, attendee_names: list[str], base_url: str = BASE_URL):
    """
    Sends a POST request to the /events endpoint to create a new event registration.

    Args:
        event_name (str): The name of the event.
        description (str): The detailed description of the event.
        event_date (str): The date and time of the event (ISO 8601 format recommended, e.g., "2025-06-10T09:00:00Z").
        locations (list): A list of location dictionaries. Each dictionary should contain:
                          'name' (str), 'description' (str, optional),
                          'latitude' (float), 'longitude' (float),
                          'address' (str, optional).
        attendee_names (list[str]): A list of names of the people attending the event.
        base_url (str, optional): The base URL of the API. Defaults to BASE_URL.

    Returns:
        dict: The JSON response from the API if the request is successful.
              Returns None if an error occurs.

    Raises:
        requests.exceptions.RequestException: If there's an issue with the network request (e.g., connection error, timeout).
    """
    url = f"{base_url}/events"
    headers = {"Content-Type": "application/json"}
    payload = {
        "event_name": event_name,
        "description": description,
        "event_date": event_date,
        "locations": locations,
        "attendee_names": attendee_names,
    }

    try:
        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)
        print(f"Successfully created event registration. Status Code: {response.status_code}")
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error creating event registration: {e}")
        # Optionally re-raise the exception if the caller needs to handle it
        # raise e
        return None
    except json.JSONDecodeError:
        print(f"Error decoding JSON response from {url}. Response text: {response.text}")
        return None

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

הטמעה של שרת MCP

עכשיו, אחרי שיש לנו את פונקציות Python שמבצעות את הפעולות (הפעלת ממשקי ה-API של InstaVibe), אנחנו צריכים לבנות את רכיב השרת של MCP. השרת הזה יחשוף את הפונקציות האלה כ'כלים' בהתאם לתקן MCP, ויאפשר ללקוחות MCP (כמו הסוכנים שלנו) לגלות אותן ולהפעיל אותן.

בדרך כלל, שרת MCP מטמיע שתי פונקציות מרכזיות:

  • list_tools: אחראי לאפשר ללקוח לגלות את הכלים הזמינים בשרת, לספק מטא-נתונים כמו השמות, התיאורים והפרמטרים הנדרשים שלהם, שלרוב מוגדרים באמצעות סכימת JSON
  • call_tool: מטפל בהפעלה של כלי ספציפי שהלקוח ביקש, מקבל את שם הכלי ואת הארגומנטים שלו ומבצע את הפעולה המתאימה, כמו במקרה שלנו – אינטראקציה עם API

שרתי MCP משמשים כדי לספק למודלים של AI גישה לנתונים ולפעולות בעולם האמיתי, וכך מאפשרים לבצע משימות כמו שליחת אימיילים, יצירת משימות במערכות לניהול פרויקטים, חיפוש במסדי נתונים או אינטראקציה עם תוכנות ושירותי אינטרנט שונים. בזמן שההטמעות הראשוניות התמקדו לעיתים קרובות בשרתים מקומיים שמתקשרים באמצעות קלט/פלט (stdio) סטנדרטיים כדי לפשט את התהליך, במיוחד בסביבות פיתוח או בסביבות 'סטודיו', המעבר לשרתים מרוחקים שמשתמשים בפרוטוקולים כמו HTTP עם אירועים שנשלחים מהשרת (SSE) הגיוני יותר לאימוץ רחב יותר ולתרחישי שימוש ארגוניים.

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

07-mcp-server.png

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

‫👉📝 קודם מטמיעים את נקודת הקצה list_tools. פותחים את הקובץ ~/instavibe-bootstrap/tools/instavibe/mcp_server.py ומחליפים את התגובה #REPLACE ME - LIST TOOLS בקוד הבא. :

@app.list_tools()
async def list_tools() -> list[mcp_types.Tool]:
  """MCP handler to list available tools."""
  # Convert the ADK tool's definition to MCP format
  mcp_tool_schema_event = adk_to_mcp_tool_type(event_tool)
  mcp_tool_schema_post = adk_to_mcp_tool_type(post_tool)
  print(f"MCP Server: Received list_tools request. \n MCP Server: Advertising tool: {mcp_tool_schema_event.name} and {mcp_tool_schema_post}")
  return [mcp_tool_schema_event,mcp_tool_schema_post]

הפונקציה הזו מגדירה את הכלים (create_event, ‏ create_post) ומספרת ללקוחות המתחברים עליהם.

‫👈📝 בשלב הבא, מטמיעים את נקודת הקצה call_tool שמטפלת בבקשות הביצוע בפועל מהלקוחות. באותו קובץ ~/instavibe-bootstrap/tools/instavibe/mcp_server.py, מחליפים את התגובה #REPLACE ME - CALL TOOLS בקוד הזה.

@app.call_tool()
async def call_tool(
    name: str, arguments: dict
) -> list[mcp_types.TextContent | mcp_types.ImageContent | mcp_types.EmbeddedResource]:
  """MCP handler to execute a tool call."""
  print(f"MCP Server: Received call_tool request for '{name}' with args: {arguments}")

  # Look up the tool by name in our dictionary
  tool_to_call = available_tools.get(name)
  if tool_to_call:
    try:
      adk_response = await tool_to_call.run_async(
          args=arguments,
          tool_context=None, # No ADK context available here
      )
      print(f"MCP Server: ADK tool '{name}' executed successfully.")
      
      response_text = json.dumps(adk_response, indent=2)
      return [mcp_types.TextContent(type="text", text=response_text)]

    except Exception as e:
      print(f"MCP Server: Error executing ADK tool '{name}': {e}")
      # Creating a proper MCP error response might be more robust
      error_text = json.dumps({"error": f"Failed to execute tool '{name}': {str(e)}"})
      return [mcp_types.TextContent(type="text", text=error_text)]
  else:
      # Handle calls to unknown tools
      print(f"MCP Server: Tool '{name}' not found.")
      error_text = json.dumps({"error": f"Tool '{name}' not implemented."})
      return [mcp_types.TextContent(type="text", text=error_text)]

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

‫👈💻 אחרי שהגדרנו את הלוגיקה של שרת ה-MCP, צריך לארוז אותה כקונטיינר. במסוף מריצים את הסקריפט הבא כדי ליצור את קובץ האימג' של Docker באמצעות Cloud Build:

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/tools/instavibe

export IMAGE_TAG="latest"
export MCP_IMAGE_NAME="mcp-tool-server"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${MCP_IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="mcp-tool-server"
export INSTAVIBE_BASE_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep instavibe)/api

gcloud builds submit . \
  --tag=${IMAGE_PATH} \
  --project=${PROJECT_ID}

‫👈💻 ופורסים את האימג' כשירות ב-Google Cloud Run.

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/tools/instavibe

export IMAGE_TAG="latest"
export MCP_IMAGE_NAME="mcp-tool-server"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${MCP_IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="mcp-tool-server"
export INSTAVIBE_BASE_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep instavibe)/api

gcloud run deploy ${SERVICE_NAME} \
  --image=${IMAGE_PATH} \
  --platform=managed \
  --region=${REGION} \
  --allow-unauthenticated \
  --set-env-vars="INSTAVIBE_BASE_URL=${INSTAVIBE_BASE_URL}" \
  --set-env-vars="APP_HOST=0.0.0.0" \
  --set-env-vars="APP_PORT=8080" \
  --set-env-vars="GOOGLE_GENAI_USE_VERTEXAI=TRUE" \
  --set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}" \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
  --project=${PROJECT_ID} \
  --min-instances=1

‫👈💻 אחרי שהפריסה תסתיים בהצלחה, שרת ה-MCP יפעל ותהיה אליו גישה דרך כתובת URL ציבורית. אנחנו צריכים לתעד את כתובת ה-URL הזו כדי שהנציג שלנו (שפועל כלקוח של MCP) יידע לאן להתחבר.

export MCP_SERVER_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep mcp-tool-server)/sse

בנוסף, השירות mcp-tool-server אמור להופיע עכשיו כ'פועל' בקטע Cloud Run במסוף Google Cloud.

Cloud Run

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

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

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

לקוח MCP

עכשיו נבנה את הסוכן שפועל כלקוח MCP. הנציג הזה, שפועל במסגרת ADK, יהיה אחראי על התקשורת עם mcp-tool-server שפרסנו זה עתה.

👈 קודם כול, צריך לשנות את הגדרת הסוכן כדי לאחזר באופן דינמי את הכלים משרת ה-MCP הפועל שלנו. ב-agents/platform_mcp_client/agent.py, מחליפים את #REPLACE ME - FETCH TOOLS בטקסט הבא:

"""Gets tools from the File System MCP Server."""
  tools =  MCPToolset(
      connection_params=SseServerParams(url=MCP_SERVER_URL, headers={})
  )

הקוד הזה משתמש בשיטה MCPToolset.from_server כדי להתחבר ל-MCP_SERVER_URL (שהגדרנו כמשתנה סביבה קודם לכן) ולאחזר את רשימת הכלים הזמינים.

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

‫👈 ב-agents/platform_mcp_client/agent.py, מחליפים את #REPLACE ME - SET TOOLs בערך הבא:

  tools=[tools],

‫👉💻 עכשיו נבדוק את הסוכן הזה באופן מקומי באמצעות ממשק המשתמש של ADK Dev כדי לראות אם הוא יכול להתחבר לשרת MCP ולהשתמש בכלים כדי ליצור אינטראקציה עם אפליקציית InstaVibe הפועלת שלנו.

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
export MCP_SERVER_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep mcp-tool-server)/sse

cd  ~/instavibe-bootstrap/agents
sed -i "s|^\(O\?GOOGLE_CLOUD_PROJECT\)=.*|GOOGLE_CLOUD_PROJECT=${PROJECT_ID}|" ~/instavibe-bootstrap/agents/platform_mcp_client/.env
sed -i "s|^\(O\?MCP_SERVER_URL\)=.*|MCP_SERVER_URL=${MCP_SERVER_URL}|" ~/instavibe-bootstrap/agents/platform_mcp_client/.env
adk web

פותחים שוב את ממשק המשתמש של ADK Dev בדפדפן (באמצעות תצוגה מקדימה באינטרנט של Cloud Shell ביציאה 8000). הפעם, בתפריט הנפתח בפינה השמאלית העליונה, בוחרים את platform_mcp_clientהסוכן.

נבדוק את הכלי create_post. בתיבת הדו-שיח של הצ'אט, מזינים את הבקשה הבאה:

Create a post saying "Y'all I just got the cutest lil void baby 😭✨ Naming him Abyss bc he's deep, mysterious, and lowkey chaotic 🔥🖤 #VoidCat #NewRoomie" I'm Julia

ADK Dev UI Post

הנציג צריך לעבד את זה, לזהות את הצורך להשתמש בכלי create_post, לתקשר עם שרת ה-MCP, שבתורו קורא ל-InstaVibe API.

‫👉 שלב האימות: אחרי שהנציג מאשר את הפעולה, פותחים את הכרטיסייה שבה פועלת אפליקציית InstaVibe (או מרעננים אותה). הפוסט החדש של 'יוליה' יופיע בפיד הראשי.

פוסט InstaVibe

‫👈💻 אם צריך לקבל קישור ל-Instavibe, מריצים את הסקריפט הזה בטרמינל נפרד:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep instavibe

‫👉📝 עכשיו נבדוק את הכלי create_event. מזינים את הבקשה הבאה בתבנית של כמה שורות בתיבת הדו-שיח:

Hey, can you set up an event for Hannah and George and me, and I'm Julia? Let's call it 'Mexico City Culinary & Art Day'.
here are more info
  {"event_name": "Mexico City Culinary & Art Day",
  "description": "A vibrant day in Mexico City for Hannah and George, starting with lunch at one of the city's best taco spots in the hip Condesa neighborhood, followed by an inspiring afternoon exploring the Museo Soumaya's stunning art collection.",
  "event_date": "2025-10-17T12:00:00-06:00",
  "locations": [
    {
      "name": "El Tizoncito",
      "description": "Considered one of the original creators of tacos al pastor, El Tizoncito offers a legendary taco experience in the heart of Condesa. Their flavorful meats, house salsas, and casual vibe make it a must-visit for foodies.",
      "latitude": 19.412179,
      "longitude": -99.171308,
      "address": "Av. Tamaulipas 122, Hipódromo, Cuauhtémoc, 06100 Ciudad de México, CDMX, Mexico"
    },
    {
      "name": "Museo Soumaya",
      "description": "An architectural icon in Mexico City, Museo Soumaya houses over 66,000 works of art, including pieces by Rodin, Dalí, and Rivera. The striking silver structure is a cultural landmark and a visual feast inside and out.",
      "latitude": 19.440056,
      "longitude": -99.204281,
      "address": "Plaza Carso, Blvd. Miguel de Cervantes Saavedra 303, Granada, Miguel Hidalgo, 11529 Ciudad de México, CDMX, Mexico"
    }
  ],
  "attendee_names": ["Hannah", "George", Julia],
}

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

אירוע בממשק המשתמש של ADK Dev

‫👈 שלב האימות: חוזרים לאפליקציית InstaVibe הפועלת ועוברים לקטע Events (אירועים) או לקטע מקביל. עכשיו אמור להופיע האירוע החדש שיצרתם, 'Mexico City Culinary & Art Day'.

אירוע InstaVibe

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

אחרי שמוודאים ששתי הפעולות בוצעו, חוזרים לטרמינל של Cloud Shell ומקישים על Ctrl+C כדי לעצור את ממשק המשתמש של ADK Dev.

9. Workflow Agent ו-Multi-Agents ב-ADK

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

סוכן ליצירת פרופילים ברשתות חברתיות

קודם כל, אנחנו צריכים כלים כדי שהסוכן יוכל לגשת לנתוני הגרף.

‫👉📝 מוסיפים את פונקציות Python הבאות לסוף הקובץ ~/instavibe-bootstrap/agents/social/instavibe.py:

def get_person_attended_events(person_id: str)-> list[dict]:
    """
    Fetches events attended by a specific person using Graph Query.
    Args:
       person_id (str): The ID of the person whose posts to fetch.
    Returns: list[dict] or None.
    """
    if not db_instance: return None

    graph_sql = """
        Graph SocialGraph
        MATCH (p:Person)-[att:Attended]->(e:Event)
        WHERE p.person_id = @person_id
        RETURN e.event_id, e.name, e.event_date, att.attendance_time
        ORDER BY e.event_date DESC
    """
    params = {"person_id": person_id}
    param_types_map = {"person_id": param_types.STRING}
    fields = ["event_id", "name", "event_date", "attendance_time"]

    results = run_graph_query( graph_sql, params=params, param_types=param_types_map, expected_fields=fields)

    if results is None: return None

    for event in results:
        if isinstance(event.get('event_date'), datetime):
            event['event_date'] = event['event_date'].isoformat()
        if isinstance(event.get('attendance_time'), datetime):
            event['attendance_time'] = event['attendance_time'].isoformat()
    return results

def get_person_id_by_name( name: str) -> str:
    """
    Fetches the person_id for a given name using SQL.

    Args:
       name (str): The name of the person to search for.

    Returns:
        str or None: The person_id if found, otherwise None.
                     Returns the ID of the *first* match if names are duplicated.
    """
    if not db_instance: return None

    sql = """
        SELECT person_id
        FROM Person
        WHERE name = @name
        LIMIT 1 -- Return only the first match in case of duplicate names
    """
    params = {"name": name}
    param_types_map = {"name": param_types.STRING}
    fields = ["person_id"]

    # Use the standard SQL query helper
    results = run_sql_query( sql, params=params, param_types=param_types_map, expected_fields=fields)

    if results: # Check if the list is not empty
        return results[0].get('person_id') # Return the ID from the first dictionary
    else:
        return None # Name not found


def get_person_posts( person_id: str)-> list[dict]:
    """
    Fetches posts written by a specific person using Graph Query.

    Args:
        person_id (str): The ID of the person whose posts to fetch.


    Returns:
        list[dict] or None: List of post dictionaries with ISO date strings,
                           or None if an error occurs.
    """
    if not db_instance: return None

    # Graph Query: Find the specific Person node, follow 'Wrote' edge to Post nodes
    graph_sql = """
        Graph SocialGraph
        MATCH (author:Person)-[w:Wrote]->(post:Post)
        WHERE author.person_id = @person_id
        RETURN post.post_id, post.author_id, post.text, post.sentiment, post.post_timestamp, author.name AS author_name
        ORDER BY post.post_timestamp DESC
    """
    # Parameters now include person_id and limit
    params = {
        "person_id": person_id
    }
    param_types_map = {
        "person_id": param_types.STRING
    }
    # Fields returned remain the same
    fields = ["post_id", "author_id", "text", "sentiment", "post_timestamp", "author_name"]

    results = run_graph_query(graph_sql, params=params, param_types=param_types_map, expected_fields=fields)

    if results is None:
        return None

    # Convert datetime objects to ISO format strings
    for post in results:
        if isinstance(post.get('post_timestamp'), datetime):
            post['post_timestamp'] = post['post_timestamp'].isoformat()

    return results


def get_person_friends( person_id: str)-> list[dict]:
    """
    Fetches friends for a specific person using Graph Query.
    Args:
        person_id (str): The ID of the person whose posts to fetch.
    Returns: list[dict] or None.
    """
    if not db_instance: return None

    graph_sql = """
        Graph SocialGraph
        MATCH (p:Person {person_id: @person_id})-[f:Friendship]-(friend:Person)
        RETURN DISTINCT friend.person_id, friend.name
        ORDER BY friend.name
    """
    params = {"person_id": person_id}
    param_types_map = {"person_id": param_types.STRING}
    fields = ["person_id", "name"]

    results = run_graph_query( graph_sql, params=params, param_types=param_types_map, expected_fields=fields)

    return results

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

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

  • עוקב (שלב אחר שלב)
  • מקביל (ביצוע בו-זמני)
  • ו-Loop (ביצוע חוזר)

סוכן ליצירת פרופילים ברשתות חברתיות

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

בואו נגדיר את הסוכנים המשניים שנדרשים לתהליך העבודה הזה.

‫👉📝 ב-~/instavibe-bootstrap/agents/social/agent.py, מחליפים את #REPLACE FOR profile_agent בטקסט הבא:

profile_agent = LlmAgent(
    name="profile_agent",
    model="gemini-2.5-flash",
    description=(
        "Agent to answer questions about the this person social profile. Provide the person's profile using their name, make sure to fetch the id before getting other data."
    ),
    instruction=(
        "You are a helpful agent to answer questions about the this person social profile. You'll be given a list of names, provide the person's profile using their name, make sure to fetch the id before getting other data. Get one person at a time, start with the first one on the list, and skip if already provided. return this person's result"
    ),
    tools=[get_person_posts,get_person_friends,get_person_id_by_name,get_person_attended_events],
)

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

‫👉📝 באותו ~/instavibe-bootstrap/agents/social/agent.py, מחליפים את #REPLACE FOR summary_agent בערך הבא:

summary_agent = LlmAgent(
    name="summary_agent",
    model="gemini-2.5-flash",
    description=(
        "Generate a comprehensive social summary as a single, cohesive paragraph. This summary should cover the activities, posts, friend networks, and event participation of one or more individuals. If multiple profiles are analyzed, the paragraph must also identify and integrate any common ground found between them."
    ),
    instruction=(
        """
        Your primary task is to synthesize social profile information into a single, comprehensive paragraph.

            **Input Scope & Default Behavior:**
            *   If specific individuals are named by the user, focus your analysis on them.
            *   **If no individuals are specified, or if the request is general, assume the user wants an analysis of *all relevant profiles available in the current dataset/context*.**

            **For each profile (whether specified or determined by default), you must analyze:**

            1.  **Post Analysis:**
                *   Systematically review their posts (e.g., content, topics, frequency, engagement).
                *   Identify recurring themes, primary interests, and expressed sentiments.

            2.  **Friendship Relationship Analysis:**
                *   Examine their connections/friends list.
                *   Identify key relationships, mutual friends (especially if comparing multiple profiles), and the general structure of their social network.

            3.  **Event Participation Analysis:**
                *   Investigate their past (and if available, upcoming) event participation.
                *   Note the types of events, frequency of attendance, and any notable roles (e.g., organizer, speaker).

            **Output Generation (Single Paragraph):**

            *   **Your entire output must be a single, cohesive summary paragraph.**
                *   **If analyzing a single profile:** This paragraph will detail their activities, interests, and social connections based on the post, friend, and event analysis.
                *   **If analyzing multiple profiles:** This paragraph will synthesize the key findings regarding posts, friends, and events for each individual. Crucially, it must then seamlessly integrate or conclude with an identification and description of the common ground found between them (e.g., shared interests from posts, overlapping event attendance, mutual friends). The aim is a unified narrative within this single paragraph.

            **Key Considerations:**
            *   Base your summary strictly on the available data.
            *   If data for a specific category (posts, friends, events) is missing or sparse for a profile, you may briefly acknowledge this within the narrative if relevant.
                """
        ),
    output_key="summary"
)

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

‫👉📝 באותו ~/instavibe-bootstrap/agents/social/agent.py, מחליפים את #REPLACE FOR check_agent בערך הבא:

check_agent = LlmAgent(
    name="check_agent",
    model="gemini-2.5-flash",
    description=(
        "Check if everyone's social profile are summarized and has been generated. Output 'completed' or 'pending'."
    ),
    output_key="summary_status"
)

אנחנו מוסיפים בדיקה תוכנתית פשוטה (CheckCondition) שבודקת באופן מפורש את summary_status שמאוחסן ב-State, שמוחזר על ידי check_agent, ומציינת לסוכן Loop אם להמשיך (escalate=False) או להפסיק (escalate=True).

‫👉📝 באותו קובץ ~/instavibe-bootstrap/agents/social/agent.py, מחליפים את #REPLACE FOR CheckCondition שנמצא בחלק העליון של הקובץ בטקסט הבא:

class CheckCondition(BaseAgent):
    async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]:
        #log.info(f"Checking status: {ctx.session.state.get("summary_status", "fail")}")
        log.info(f"Summary: {ctx.session.state.get("summary")}")

        status = ctx.session.state.get("summary_status", "fail").strip()
        is_done = (status == "completed")

        yield Event(author=self.name, actions=EventActions(escalate=is_done))

מצב וקריאות חוזרות לתוצאות של לולאה

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

בתרחיש שלנו, כשהסוכן Loop חוזר על עצמו, הוא מאחסן את הפלט (summary ו-summary_status) במצב של הסוכן.summary_agentcheck_agent כך המידע נשמר בין איטרציות. עם זאת, סוכן Loop עצמו לא מחזיר אוטומטית את הסיכום הסופי מהמצב כשהוא מסיים.

סוכן ליצירת פרופילים ברשתות חברתיות

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

נשתמש ב-after_agent_callback שפועל כשהלולאה מסתיימת (כי CheckCondition הועלה). פונקציית ה-callback‏ modify_output_after_agent מאחזרת את הסיכום הסופי מהמצב ומעצבת אותו כהודעת הפלט הסופית של הסוכן.

התקשרות חזרה

‫👉📝 באותו ~/instavibe-bootstrap/agents/social/agent.py, מחליפים את #REPLACE FOR modify_output_after_agent ב-follow:

def modify_output_after_agent(callback_context: CallbackContext) -> Optional[types.Content]:

    agent_name = callback_context.agent_name
    invocation_id = callback_context.invocation_id
    current_state = callback_context.state.to_dict()
    current_user_content = callback_context.user_content
    print(f"[Callback] Exiting agent: {agent_name} (Inv: {invocation_id})")
    print(f"[Callback] Current summary_status: {current_state.get("summary_status")}")
    print(f"[Callback] Current Content: {current_user_content}")

    status = current_state.get("summary_status").strip()
    is_done = (status == "completed")
    # Retrieve the final summary from the state

    final_summary = current_state.get("summary")
    print(f"[Callback] final_summary: {final_summary}")
    if final_summary and is_done and isinstance(final_summary, str):
        log.info(f"[Callback] Found final summary, constructing output Content.")
        # Construct the final output Content object to be sent back
        return types.Content(role="model", parts=[types.Part(text=final_summary.strip())])
    else:
        log.warning("[Callback] No final summary found in state or it's not a string.")
        # Optionally return a default message or None if no summary was generated
        return None

הגדרת סוכן Loop הראשי

לבסוף, אנחנו מגדירים את LoopAgent הראשי. הוא מתזמן את סוכני המשנה ברצף בכל איטרציה של הלולאה (profile_agent -> summary_agent -> check_agent -> CheckCondition). הפעולה הזו תחזור על עצמה עד max_iterations פעמים או עד שהפונקציה CheckCondition תאותת על השלמה. הפונקציה after_agent_callback מוודאת שהסיכום הסופי יוחזר.

‫👉📝 באותו ~/instavibe-bootstrap/agents/social/agent.py, מחליפים את #REPLACE FOR root_agent ב-follow:

root_agent = LoopAgent(
    name="InteractivePipeline",
    sub_agents=[
        profile_agent,
        summary_agent,
        check_agent,
        CheckCondition(name="Checker")
    ],
    description="Find everyone's social profile on events, post and friends",
    max_iterations=10,
    after_agent_callback=modify_output_after_agent
)

נבדוק את תהליך העבודה הזה עם כמה סוכנים באמצעות ממשק המשתמש של ADK Dev.

‫👉💻 מפעילים את שרת האינטרנט של ADK:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd  ~/instavibe-bootstrap/agents
sed -i "s|^\(O\?GOOGLE_CLOUD_PROJECT\)=.*|GOOGLE_CLOUD_PROJECT=${PROJECT_ID}|" ~/instavibe-bootstrap/agents/social/.env
adk web

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

‫👈 עכשיו, נותנים לו את המשימה ליצור פרופילים של כמה אנשים. בתיבת הדו-שיח של הצ'אט, מזינים:

Tell me about Mike and Bob

אחרי שהסוכן מגיב (יכול להיות שייקח קצת יותר זמן בגלל הלולאה והקריאות המרובות ל-LLM), אל תסתכלו רק על הפלט הסופי של הצ'אט. עוברים לכרטיסייה 'אירועים' בחלונית הימנית של ממשק המשתמש של ADK Dev.

‫👈 שלב האימות: בכרטיסייה 'אירועים' מוצג מעקב מפורט של ההפעלה, שלב אחר שלב. 09-01-adk-dev-ui.png

אחרי שמתבוננים באופן שבו הסוכן מפעיל כל סוכן משנה, במקום שבו מצפים שהזרימה תעבור מ-profile_agent -> summary_agent -> check_agent, Checker בכל איטרציה. אבל בפועל, אנחנו רואים את ה'אופטימיזציה העצמית' החזקה של הסוכן בפעולה.

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

09-02-ui-graph.png

והעדכונים של הסטטוס מ-check_agent ומ-CheckCondition. 09-03-ui-state.png

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

אחרי שבודקים את התשובה בצ'אט ואת מעקב האירועים, חוזרים לטרמינל של Cloud Shell ומקישים על Ctrl+C כדי להפסיק את ממשק המשתמש של ADK Dev.

10. תקשורת בין נציגים (A2A)

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

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

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

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

10-05-agent-card

ב-A2A נעשה שימוש בתקני אינטרנט מוכרים (HTTP, ‏ SSE, ‏ JSON-RPC) ולרוב מופעל מודל של לקוח-שרת שבו סוכן אחד (לקוח) שולח משימות לסוכן אחר (סוכן מרוחק/שרת). הסטנדרטיזציה הזו היא המפתח לבניית מערכות מודולריות וניתנות להרחבה, שבהן סוכנים שפותחו באופן עצמאי יכולים לעבוד יחד.

הפעלת A2A לסוכני InstaVibe

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

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

Planner Agent (A2A Enabled)

all-agent-planner

נתחיל בהוספת שכבת השרת A2A לסוכן לתכנון.

הגדרת לוגיקת ההפעלה של שרת A2A. הקוד הזה מגדיר את AgentCard (התיאור הציבורי של הסוכן), מגדיר את A2AServer ומתחיל אותו, ומקשר אותו אל PlatformAgentExecutor.

‫👉📝 מוסיפים את הקוד הבא לסוף ~/instavibe-bootstrap/agents/planner/a2a_server.py:

class PlannerAgent:
    """An agent to help user planning a event with its desire location."""
    SUPPORTED_CONTENT_TYPES = ["text", "text/plain"]

    def __init__(self):
        self._agent = self._build_agent()
        self.runner = Runner(
            app_name=self._agent.name,
            agent=self._agent,
            artifact_service=InMemoryArtifactService(),
            session_service=InMemorySessionService(),
            memory_service=InMemoryMemoryService(),
        )
        capabilities = AgentCapabilities(streaming=True)
        skill = AgentSkill(
            id="event_planner",
            name="Event planner",
            description="""
            This agent generates multiple fun plan suggestions tailored to your specified location, dates, and interests,
            all designed for a moderate budget. It delivers detailed itineraries,
            including precise venue information (name, latitude, longitude, and description), in a structured JSON format.
            """,
            tags=["instavibe"],
            examples=["What about Bostona MA this weekend?"],
        )
        self.agent_card = AgentCard(
            name="Event Planner Agent",
            description="""
            This agent generates multiple fun plan suggestions tailored to your specified location, dates, and interests,
            all designed for a moderate budget. It delivers detailed itineraries,
            including precise venue information (name, latitude, longitude, and description), in a structured JSON format.
            """,
            url=f"{PUBLIC_URL}",
            version="1.0.0",
            defaultInputModes=PlannerAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=PlannerAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=[skill],
        )

    def get_processing_message(self) -> str:
        return "Processing the planning request..."

    def _build_agent(self) -> LlmAgent:
        """Builds the LLM agent for the night out planning agent."""
        return agent.root_agent


if __name__ == '__main__':
    try:
        plannerAgent = PlannerAgent()

        request_handler = DefaultRequestHandler(
            agent_executor=PlannerAgentExecutor(plannerAgent.runner,plannerAgent.agent_card),
            task_store=InMemoryTaskStore(),
        )

        server = A2AStarletteApplication(
            agent_card=plannerAgent.agent_card,
            http_handler=request_handler,
        )
        logger.info(f"Attempting to start server with Agent Card: {plannerAgent.agent_card.name}")
        logger.info(f"Server object created: {server}")

        uvicorn.run(server.build(), host='0.0.0.0', port=port)
    except Exception as e:
        logger.error(f"An error occurred during server startup: {e}")
        exit(1)

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

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd ~/instavibe-bootstrap/agents/
python -m planner.a2a_server

‫👉 עכשיו פותחים חלון טרמינל נוסף. (לוחצים על הסימן + בחלונית הטרמינל) שני מסופים

‫👈💻 משתמשים ב-curl כדי לבקש את כרטיס הסוכן מהשרת שפועל באופן מקומי:

curl http://localhost:10003/.well-known/agent.json | jq

אמורה להופיע הצגת JSON של AgentCard שהגדרנו, כדי לאשר שהשרת פועל ומפרסם את סוכן המתכנן.

10-02-planner-a2a.png

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

‫👈💻 אחרי שמוסיפים את הלוגיקה של השרת A2A, אפשר ליצור את קובץ האימג' של הקונטיינר.

פיתוח ופריסה של סוכן התכנון

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/agents

# Set variables specific to the PLANNER agent
export IMAGE_TAG="latest"
export AGENT_NAME="planner"
export IMAGE_NAME="planner-agent"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="planner-agent"
export PUBLIC_URL="https://planner-agent-${PROJECT_NUMBER}.${REGION}.run.app"

echo "Building ${AGENT_NAME} agent..."
gcloud builds submit . \
  --config=cloudbuild-build.yaml \
  --project=${PROJECT_ID} \
  --region=${REGION} \
  --substitutions=_AGENT_NAME=${AGENT_NAME},_IMAGE_PATH=${IMAGE_PATH}

echo "Image built and pushed to: ${IMAGE_PATH}"

‫👈💻 פריסת סוכן התכנון ב-Cloud Run.

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/agents

# Set variables specific to the PLANNER agent
export IMAGE_TAG="latest"
export AGENT_NAME="planner"
export IMAGE_NAME="planner-agent"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="planner-agent"
export PUBLIC_URL="https://planner-agent-${PROJECT_NUMBER}.${REGION}.run.app"


gcloud run deploy ${SERVICE_NAME} \
  --image=${IMAGE_PATH} \
  --platform=managed \
  --region=${REGION} \
  --set-env-vars="A2A_HOST=0.0.0.0" \
  --set-env-vars="A2A_PORT=8080" \
  --set-env-vars="GOOGLE_GENAI_USE_VERTEXAI=TRUE" \
  --set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}" \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
  --set-env-vars="PUBLIC_URL=${PUBLIC_URL}" \
  --allow-unauthenticated \
  --project=${PROJECT_ID} \
  --min-instances=1

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

‫👈 בסרגל הכלים של Cloud Shell, לוחצים על סמל התצוגה המקדימה באינטרנט ובוחרים באפשרות 'שינוי יציאה'. מגדירים את היציאה ל-8081 ולוחצים על 'שינוי ותצוגה מקדימה'. תיפתח כרטיסיית דפדפן חדשה עם הממשק של הכלי לבדיקת נגישות (A2A).

10-08-web-preview.png

‫👈💻 במסוף, מקבלים את כתובת ה-URL של סוכן התכנון שפרסתם:

export PLANNER_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner-agent)
echo ${PLANNER_AGENT_URL}

‫👈💻 מעתיקים את כתובת ה-URL של הפלט.

‫👉 בממשק המשתמש של הכלי לבדיקת סוכני AI, מדביקים את כתובת ה-URL בשדה Agent URL (כתובת ה-URL של הסוכן) ולוחצים על Connect (קישור).

👀 פרטי הכרטיס של הסוכן ו-JSON אמורים להופיע בכרטיס הסוכן, כדי לאשר שהחיבור בוצע בהצלחה.

10-03-planner-a2a.png

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

Plan something for me in Boston MA this weekend, and I enjoy classical music

‫👀 כדי לבדוק את התקשורת הגולמית, לוחצים על בועת ההודעה שלכם ואז על בועת התשובה של הנציג בחלון הצ'אט. כשלוחצים על כל אחת מההודעות, מוצגת ההודעה המלאה בפורמט JSON-RPC 2.0 שנשלחה או התקבלה, וזה מאוד שימושי לניפוי באגים.

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

10-06-a2a-inspector.png

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

all-agent-platform

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

‫👈📝 מגדירים את השרת A2A, כולל כרטיס הסוכן הייחודי שלו, בסוף ~/instavibe-bootstrap/agents/platform_mcp_client/a2a_server.py:

class PlatformAgent:
  """An agent that post event and post to instavibe."""

  SUPPORTED_CONTENT_TYPES = ["text", "text/plain"]

  def __init__(self):
    self._agent = self._build_agent()
    self.runner = Runner(
        app_name=self._agent.name,
        agent=self._agent,
        artifact_service=InMemoryArtifactService(),
        session_service=InMemorySessionService(),
        memory_service=InMemoryMemoryService(),
    )
    capabilities = AgentCapabilities(streaming=True)
    skill = AgentSkill(
            id="instavibe_posting",
            name="Post social post and events on instavibe",
            description="""
            This "Instavibe" agent helps you create posts (identifying author, text, and sentiment – inferred if unspecified) and register
            for events (gathering name, date, attendee). It efficiently collects required information and utilizes dedicated tools
            to perform these actions on your behalf, ensuring a smooth sharing experience.
            """,
            tags=["instavibe"],
            examples=["Create a post for me, the post is about my cute cat and make it positive, and I'm Alice"],
        )
    self.agent_card = AgentCard(
            name="Instavibe Posting Agent",
            description="""
            This "Instavibe" agent helps you create posts (identifying author, text, and sentiment – inferred if unspecified) and register
            for events (gathering name, date, attendee). It efficiently collects required information and utilizes dedicated tools
            to perform these actions on your behalf, ensuring a smooth sharing experience.
            """,
            url=f"{PUBLIC_URL}",
            version="1.0.0",
            defaultInputModes=PlatformAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=PlatformAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=[skill],
        )


  def get_processing_message(self) -> str:
      return "Processing the social post and event request..."

  def _build_agent(self) -> LlmAgent:
    """Builds the LLM agent for the Processing the social post and event request."""
    return agent.root_agent


if __name__ == '__main__':
    try:
        platformAgent = PlatformAgent()

        request_handler = DefaultRequestHandler(
            agent_executor=PlatformAgentExecutor(platformAgent.runner,platformAgent.agent_card),
            task_store=InMemoryTaskStore(),
        )

        server = A2AStarletteApplication(
            agent_card=platformAgent.agent_card,
            http_handler=request_handler,
        )

        uvicorn.run(server.build(), host='0.0.0.0', port=port)
    except Exception as e:
        logger.error(f"An error occurred during server startup: {e}")
        exit(1)

נציג ברשתות חברתיות (מופעלת תקשורת בין נציגים)

all-agent-social

לבסוף, נפעיל את A2A לסוכן שלנו ליצירת פרופילים חברתיים.

‫👉📝 בסוף ~/instavibe-bootstrap/agents/social/a2a_server.py, מגדירים את השרת A2A ואת AgentCard:

class SocialAgent:
  """An agent that handles social profile analysis."""

  SUPPORTED_CONTENT_TYPES = ["text", "text/plain"]

  def __init__(self):
    self._agent = self._build_agent()
    self.runner = Runner(
        app_name=self._agent.name,
        agent=self._agent,
        artifact_service=InMemoryArtifactService(),
        session_service=InMemorySessionService(),
        memory_service=InMemoryMemoryService(),
    )
    capabilities = AgentCapabilities(streaming=True)
    skill = AgentSkill(
                id="social_profile_analysis",
                name="Analyze Instavibe social profile",
                description="""
                Using a provided list of names, this agent synthesizes Instavibe social profile information by analyzing posts, friends, and events.
                It delivers a comprehensive single-paragraph summary for individuals, and for groups, identifies commonalities in their social activities
                and connections based on profile data.
                """,
                tags=["instavibe"],
                examples=["Can you tell me about Bob and Alice?"],
    )
    self.agent_card = AgentCard(
                name="Social Profile Agent",
                description="""
                Using a provided list of names, this agent synthesizes Instavibe social profile information by analyzing posts, friends, and events.
                It delivers a comprehensive single-paragraph summary for individuals, and for groups, identifies commonalities in their social activities
                and connections based on profile data.
                """,
                url=f"{PUBLIC_URL}",
                version="1.0.0",
                defaultInputModes=self.SUPPORTED_CONTENT_TYPES,
                defaultOutputModes=self.SUPPORTED_CONTENT_TYPES,
                capabilities=capabilities,
                skills=[skill],
    )

  def get_processing_message(self) -> str:
      return "Processing the social profile analysis request..."

  def _build_agent(self) -> LoopAgent:
    """Builds the LLM agent for the social profile analysis agent."""
    return agent.root_agent

if __name__ == '__main__':
    try:
        socialAgent = SocialAgent()

        request_handler = DefaultRequestHandler(
            agent_executor=SocialAgentExecutor(socialAgent.runner,socialAgent.agent_card),
            task_store=InMemoryTaskStore(),
        )

        server = A2AStarletteApplication(
            agent_card=socialAgent.agent_card,
            http_handler=request_handler,
        )

        uvicorn.run(server.build(), host='0.0.0.0', port=port)
    except Exception as e:
        logger.error(f"An error occurred during server startup: {e}")
        exit(1)

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

הסוכנים האלה צריכים גישה ל-Spanner, לכן חשוב לוודא שמשתני הסביבה SPANNER_INSTANCE_ID, SPANNER_DATABASE_ID ו-MCP_SERVER_URL מועברים בצורה נכונה במהלך הפריסה.

‫👈💻 פיתוח ופריסה ב-Cloud Run באמצעות Cloud Build:

. ~/instavibe-bootstrap/set_env.sh
cd ~/instavibe-bootstrap/agents
export MCP_SERVER_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep mcp-tool-server)/sse


gcloud builds submit . \
  --config=cloudbuild.yaml \
  --project="${PROJECT_ID}" \
  --region="${REGION}" \
  --substitutions=\
_PROJECT_ID="${PROJECT_ID}",\
_PROJECT_NUMBER="${PROJECT_NUMBER}",\
_REGION="${REGION}",\
_REPO_NAME="${REPO_NAME}",\
_SPANNER_INSTANCE_ID="${SPANNER_INSTANCE_ID}",\
_SPANNER_DATABASE_ID="${SPANNER_DATABASE_ID}",\
_MCP_SERVER_URL="${MCP_SERVER_URL}"

‫👈💻 במסוף, מקבלים את כתובת ה-URL של סוכן הפלטפורמה שפרסתם:

export PLATFORM_MPC_CLIENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep platform-mcp-client)
echo $PLATFORM_MPC_CLIENT_URL

‫👈💻 מעתיקים את כתובת ה-URL של הפלט.

‫👉 בממשק המשתמש של הכלי לבדיקת סוכני AI, מדביקים את כתובת ה-URL בשדה Agent URL (כתובת ה-URL של הסוכן) ולוחצים על Connect (קישור).

👀 פרטי הכרטיס של הסוכן ו-JSON אמורים להופיע בכרטיס הסוכן, כדי לאשר שהחיבור בוצע בהצלחה.

10-05-platform-a2a.png

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

Create a post for me, the post says 'Paws, purrs, and ocean views 🐾☕🌊. Spent my morning at the Morning Seaside Cat Café, where every sip comes with a side of snuggles and sea breeze.' and make it positive, and I'm Oscar.

‫👀 כדי לבדוק את התקשורת הגולמית, לוחצים על בועת ההודעה שלכם ואז על בועת התשובה של הנציג בחלון הצ'אט. כשלוחצים על כל אחת מההודעות, מוצגת ההודעה המלאה בפורמט JSON-RPC 2.0 שנשלחה או התקבלה, וזה מאוד שימושי לניפוי באגים.

‫👈💻 במסוף, מקבלים את כתובת ה-URL של סוכן הרשתות החברתיות שפרסתם:

export SOCIAL_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep social-agent)
echo $SOCIAL_AGENT_URL

‫👈💻 מעתיקים את כתובת ה-URL של הפלט.

‫👉 בממשק המשתמש של הכלי לבדיקת סוכני AI, מדביקים את כתובת ה-URL בשדה Agent URL (כתובת ה-URL של הסוכן) ולוחצים על Connect (קישור).

👀 פרטי הכרטיס של הסוכן ו-JSON אמורים להופיע בכרטיס הסוכן, כדי לאשר שהחיבור בוצע בהצלחה.

10-04-social-a2a.png

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

Can you tell me about both Ian and Kevin's profile, what are their common interests?

‫👀 כדי לבדוק את התקשורת הגולמית, לוחצים על בועת ההודעה שלכם ואז על בועת התשובה של הנציג בחלון הצ'אט. כשלוחצים על כל אחת מההודעות, מוצגת ההודעה המלאה בפורמט JSON-RPC 2.0 שנשלחה או התקבלה, וזה מאוד שימושי לניפוי באגים.

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

11. סוכן תזמור (לקוח A2A)

יש לנו עכשיו שלושה סוכנים מומחים (Planner,‏ Platform,‏ Social) שפועלים כשירותים עצמאיים עם תמיכה ב-A2A ב-Cloud Run. החלק האחרון הוא Orchestrator Agent. הסוכן הזה יפעל כמתאם מרכזי או כלקוח A2A. הוא יקבל בקשות ממשתמשים, יזהה אילו סוכנים מרוחקים נדרשים כדי למלא את הבקשה (יכול להיות שיהיה צורך בכמה סוכנים, אחד אחרי השני), ואז ישתמש בפרוטוקול A2A כדי להקצות משימות לסוכנים המרוחקים האלה. בסדנה הזו נריץ את סוכן Orchestrator באופן מקומי באמצעות ממשק המשתמש של ADK Dev.

all-agent-orchestrator

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

‫👉📝 ב-~/instavibe-bootstrap/agents/orchestrate/agent.py, מחליפים את #REPLACE ME REG AGENT CARD ב:

async with httpx.AsyncClient(timeout=30) as client:
            for i, address in enumerate(REMOTE_AGENT_ADDRESSES):
                log.info(f"--- STEP 3.{i}: Attempting connection to: {address} ---")
                try:
                    card_resolver = A2ACardResolver(client, address)
                    card = await card_resolver.get_agent_card()
                    
                    remote_connection = RemoteAgentConnections(agent_card=card, agent_url=address)
                    self.remote_agent_connections[card.name] = remote_connection
                    self.cards[card.name] = card
                    log.info(f"--- STEP 5.{i}: Successfully stored connection for {card.name} ---")

                except Exception as e:
                    log.error(f"--- CRITICAL FAILURE at STEP 4.{i} for address: {address} ---")
                    log.error(f"--- The hidden exception type is: {type(e).__name__} ---")
                    log.error(f"--- Full exception details and traceback: ---", exc_info=True)

בשלב הבא, מגדירים את הכלי לסוכן Orchestrator עצמו ב-ADK.

  • send_message (פונקציית A2A להקצאת עבודה).

‫👉📝 מחליפים את #REPLACE ME CREATE AGENT ב-~/instavibe-bootstrap/agents/orchestrate/agent.py ב:

def create_agent(self) -> Agent:
        """Synchronously creates the ADK Agent object."""
        return Agent(
            model="gemini-2.5-flash",
            name="orchestrate_agent",
            instruction=self.root_instruction,
            before_agent_callback=self.before_agent_callback,
            description=("Orchestrates tasks for child agents."),
            tools=[self.send_message], 
        )

הלוגיקה המרכזית של Orchestrator נמצאת בהוראות שלו, שמסבירות לו איך להשתמש ב-A2A.

‫👉📝 מחליפים את #REPLACE ME INSTRUCTIONS ב-~/instavibe-bootstrap/agents/orchestrate/agent.py בשיטה הבאה ליצירת הוראות:

def root_instruction(self, context: ReadonlyContext) -> str:
        current_agent = self.check_active_agent(context)
        return f"""
                You are an expert AI Orchestrator. Your primary responsibility is to intelligently interpret user requests, break them down into a logical plan of discrete actions, and delegate each action to the most appropriate specialized remote agent using the send_message function. You do not perform the tasks yourself but manage their assignment, sequence, and critically, their outcomes.
                    **Core Directives & Decision Making:**

                    *   **Understand User Intent & Complexity:**
                        *   Carefully analyze the user's request to determine the core task(s) they want to achieve. Pay close attention to keywords and the overall goal.
                        *   Identify if the request requires a single agent or a sequence of actions from multiple agents. For example, "Analyze John Doe's profile and then create a positive post about his recent event attendance" would require two agents in sequence.

                    *   **Task Planning & Sequencing (for Multi-Step Requests):**
                        *   Before delegating, outline the clear sequence of agent tasks.
                        *   Identify dependencies. If Task B requires output from Task A, execute them sequentially. If tasks are independent (like creating a post and then creating an event), execute them one after the other as separate delegations.
                        *   Agent Reusability: An agent's completion of one task does not make it unavailable. If a user's plan involves multiple, distinct actions that fall under the same agent's expertise (e.g., create a post, then create an event), you must call that same agent again for the subsequent task.

                    *   **Task Delegation & Management (using `send_message`):**
                        *   **Delegation:** Use `send_message` to assign actionable tasks to the selected remote agent. Your `send_message` call MUST include:
                            *   The `remote_agent_name` you've selected.
                            *   The `user_request` or all necessary parameters extracted from the user's input, formatted in a way the target agent will understand.
                        *   **Contextual Awareness for Remote Agents:** If a remote agent repeatedly requests user confirmation or seems to lack context, assume it lacks access to the full conversation history. In such cases, enrich your `send_message` with all necessary contextual information relevant to that specific agent from the conversation history.
                        *   **Sequential Task Execution:**
                            *   After a preceding task completes (indicated by the agent's response or a success signal), gather any necessary output from it.
                            *   Then, use `send_message` for the next agent in the sequence, providing it with the user's original relevant intent and any necessary data obtained from the previous agent's task.
                        *   **Active Agent Prioritization:** If an active agent is already engaged and the user's request is related to its current task, route subsequent related requests directly to that agent by providing updated context via `send_message`.
                    
                    
                    **Critical Success Verification:**

                    *   You **MUST** wait for the tool_output after every send_message call before taking any further action.
                    *   Your decision to proceed to the next task in a sequence **MUST** be based entirely on a confirmation of success from the tool_output of the previous task.
                    *   If a tool call fails, returns an error, or the tool_output is ambiguous, you MUST STOP the sequence. Your next action is to report the exact failure or ambiguity to the user.
                    *   DO NOT assume a task was successful. Do not invent success messages like "The event has been created." Only state that a task is complete if the tool's response explicitly says so.
                    
                    **Communication with User:**

                    *   **Transparent Communication:** Always present the complete and detailed response from the remote agent to the user. Do not summarize or filter unless explicitly instructed.
                    *   When you delegate a task (or the first task in a sequence), clearly inform the user which remote agent is handling it.
                    *   For multi-step requests, you can optionally inform the user of the planned sequence (e.g., "Okay, first I'll ask the 'Social Profile Agent' to analyze the profile, and then I'll have the 'Instavibe Posting Agent' create the post.").
                    *   If waiting for a task in a sequence to complete, you can inform the user (e.g., "The 'Social Profile Agent' is currently processing. I'll proceed with the post once that's done.").
                    *   **User Confirmation Relay:** If a remote agent asks for confirmation, and the user has not already provided it, just make up something.
                    *   If the user's request is ambiguous, if necessary information is missing for any agent in the sequence, or if you are unsure about the plan, just make up something.

                    **Important Reminders:**

                    *   **Autonomous Agent Engagement:** Never seek user permission before engaging with remote agents. If multiple agents are required to fulfill a request, connect with them directly without requesting user preference or confirmation.
                    *   **Focused Information Sharing:** Provide remote agents with only relevant contextual information. Avoid extraneous details that are not directly pertinent to their task.
                    *   **No Redundant Confirmations:** Do not ask remote agents for confirmation of information or actions they have already processed or committed to.
                    *   **Tool Reliance:** Strictly rely on your available tools, primarily `send_message`, to address user requests. Do not generate responses based on assumptions. If information is insufficient, request clarification from the user.
                    *   **Prioritize Recent Interaction:** Focus primarily on the most recent parts of the conversation when processing requests, while maintaining awareness of the overall goal for multi-step tasks.
                    *   Always prioritize selecting the correct agent(s) based on their documented purpose.
                    *   Ensure all information required by the chosen remote agent is included in the `send_message` call, including outputs from previous agents if it's a sequential task.

                    Agents:
                    {self.agents}

                    Current agent: {current_agent['active_agent']}`
                """

בדיקת כלי התזמור ומערכת A2A המלאה

עכשיו נבדוק את כל המערכת. אנחנו נריץ את ה-Orchestrator באופן מקומי באמצעות ממשק המשתמש של ADK Dev, והוא יתקשר עם סוכני Planner, Platform ו-Social שפועלים מרחוק ב-Cloud Run.

‫👉💻 קודם כול, מוודאים שמשתנה הסביבה REMOTE_AGENT_ADDRESSES מכיל את כתובות ה-URL של הסוכנים שהופעלה בהם התכונה A2A, מופרדות באמצעות פסיקים. לאחר מכן, מגדירים את משתני הסביבה הדרושים לסוכן Orchestrator ומפעילים את ממשק המשתמש של ADK Dev:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate

export PLATFORM_MPC_CLIENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep platform-mcp-client)
export PLANNER_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner-agent)
export SOCIAL_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep social-agent)

export REMOTE_AGENT_ADDRESSES=${PLANNER_AGENT_URL},${PLATFORM_MPC_CLIENT_URL},${SOCIAL_AGENT_URL}

cd  ~/instavibe-bootstrap/agents
sed -i "s|^\(O\?REMOTE_AGENT_ADDRESSES\)=.*|REMOTE_AGENT_ADDRESSES=${REMOTE_AGENT_ADDRESSES}|" ~/instavibe-bootstrap/agents/orchestrate/.env
adk web

👈 פותחים את ממשק המשתמש של ADK Dev (משנים את היציאה בחזרה ל-8000 דרך תצוגה מקדימה באינטרנט).

10-08-web-preview.png

‫👉 בתפריט הנפתח של הסוכן, בוחרים בסוכן orchestrate.

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

You are an expert event planner for a user named  Diana.
    Your task is to design a fun and personalized event.

    Here are the details for the plan:
    - Friends to invite: Ian, Nora
    - Desired date: "2025-10-15"
    - Location idea or general preference: "Chicago"

    Your process should be:
    1. Analyze the provided friend names. If you have access to a tool to get their InstaVibe profiles or summarized interests, please use it.
    2. Based on their potential interests (or general good taste if profiles are unavailable), create a tailored plan for the outing, check if you have access to any event planner tools.
    3. Ensure the plan includes the original `planned_date`.

    The user wants a comprehensive plan that includes:
    - The list of invited friends.
    - A catchy and descriptive name for the event.
    - The exact planned date for the event.
    - A summary of what the group will do.
    - Specific recommended spots (e.g., restaurants, bars, activity venues) with their names, (if possible, approximate latitude/longitude for mapping, and address), and a brief description of why it fits the plan.
    - A short, exciting message that {Diana} can send to {Ian, Nora} to get them excited about the event.

תזמור

בודקים את האינטראקציה בחלון הצ'אט של ממשק המשתמש למפתחים של ADK. חשוב לשים לב לתשובות של המנהל – הוא אמור לציין לאיזה סוכן מרוחק הוא מעביר את המשימות (למשל, "Okay, I'll ask the Social Profile Agent about Ian and Nora first...").

בנוסף, כדאי לבדוק בכרטיסייה 'אירועים' בממשק המשתמש את הקריאות הבסיסיות לכלי (send_message) שמתבצעות לכתובות ה-URL של הסוכנים המרוחקים.

שליחת משימה

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

Hey, can you register an event on Instavibe for Laura and Charlie? Let's call it 'Vienna Concert & Castles Day'.
here are more info
"event_name": "Vienna Concert & Castles Day",
  "description": "A refined and unforgettable day in Vienna with Laura and Charlie. The day begins with a guided tour of the magnificent Schönbrunn Palace, showcasing imperial architecture and history. In the evening, enjoy a classical music concert in one of Vienna's most iconic concert halls.",
  "event_date": "2025-10-14T10:00:00+02:00",
  "locations": [
    {
      "name": "Schönbrunn Palace",
      "description": "A UNESCO World Heritage Site and former imperial summer residence, Schönbrunn Palace offers opulent rooms, beautiful baroque gardens, and a glimpse into the life of the Habsburg monarchy. Visitors can stroll the grounds or take a guided historical tour.",
      "latitude": 48.184516,
      "longitude": 16.312222,
      "address": "Schönbrunner Schloßstraße 47, 1130 Wien, Austria"
    },
    {
      "name": "Musikverein Vienna",
      "description": "Home to the world-renowned Vienna Philharmonic, the Musikverein is one of the finest concert halls in the world. Its 'Golden Hall' is famous for its acoustics and ornate design. Attendees can enjoy a powerful classical concert in an unforgettable setting.",
      "latitude": 48.200132,
      "longitude": 16.373777,
      "address": "Musikvereinsplatz 1, 1010 Wien, Austria"
    }
  ],
  "attendee_names": ["Laura", "Charlie", "Oscar"] And I am Oscar

שוב, עוקבים אחרי הצ'אט ואחרי הכרטיסייה 'אירועים'. ה-Orchestrator צריך לזהות את הצורך ליצור אירוע ולהעביר את המשימה (עם כל הפרטים שסופקו) אל Platform Integration Agent. אפשר גם ללחוץ על הלחצן Trace כדי לראות את ה-traces ולנתח את זמני התגובה של השאילתות ואת הפעולות שבוצעו. שליחת אירוע

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

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

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

12. ‫Agent Engine ושיחות מרחוק מ-InstaVibe

עד עכשיו הפעלנו את הסוכנים המומחים שלנו ב-Cloud Run ובדקנו את כלי התזמור באופן מקומי באמצעות ממשק המשתמש של ADK Dev. במקרה של תרחיש ייצור, אנחנו צריכים סביבה חזקה, ניתנת להרחבה ומנוהלת כדי לארח את הסוכנים שלנו. כאן נכנס לתמונה Google Vertex AI Agent Engine.

‫Agent Engine הוא שירות מנוהל לחלוטין ב-Vertex AI, שנועד במיוחד לפריסה ולהתאמה לעומס (scaling) של סוכני AI. הוא מפשט את ניהול התשתית, האבטחה והתקורה התפעולית, ומאפשר למפתחים (במיוחד לאלה שלא מכירים היטב סביבות ענן מורכבות) להתמקד בלוגי וביכולות של הסוכן במקום בניהול שרתים. הוא מספק זמן ריצה ייעודי שעבר אופטימיזציה לעומסי עבודה של סוכנים.

עכשיו נפיץ את סוכן ה-Orchestrator שלנו ל-Agent Engine. (הערה: מנגנון הפריסה שמוצג בהמשך משתמש בסקריפט בהתאמה אישית (agent_engine_app.py) שסופק בחומרי הסדנה, כי יכול להיות שעדיין מתבצעים שינויים בכלי הפריסה הרשמיים של ADK ל-Agent-Engine. הסקריפט הזה מטפל באריזה ובפריסה של סוכן Orchestrator, שמוגדר עם כתובות הסוכן המרוחקות הנדרשות).

מריצים את הפקודה הבאה כדי לפרוס את סוכן Orchestrator ב-Agent Engine. מוודאים שמשתנה הסביבה REMOTE_AGENT_ADDRESSES (שכולל את כתובות ה-URL של סוכני התכנון, הפלטפורמה והרשתות החברתיות ב-Cloud Run) עדיין מוגדר בצורה נכונה כמו בקטע הקודם.

‫👈💻 נטמיע את סוכן Orchestrate ב-Agent Engine (הערה: זו ההטמעה שלי לפריסה, ל-ADK יש CLI שיכול לעזור בפריסה. אעדכן את זה אחרי הטמעת BYO-SA).

cd ~/instavibe-bootstrap/agents/
. ~/instavibe-bootstrap/set_env.sh

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:service-$PROJECT_NUMBER@gcp-sa-aiplatform-re.iam.gserviceaccount.com" \
    --role="roles/viewer"


source ~/instavibe-bootstrap/env/bin/activate
export PLATFORM_MPC_CLIENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep platform-mcp-client)
export PLANNER_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner-agent)
export SOCIAL_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep social-agent)

export REMOTE_AGENT_ADDRESSES=${PLANNER_AGENT_URL},${PLATFORM_MPC_CLIENT_URL},${SOCIAL_AGENT_URL}
sed -i "s|^\(O\?REMOTE_AGENT_ADDRESSES\)=.*|REMOTE_AGENT_ADDRESSES=${REMOTE_AGENT_ADDRESSES}|" ~/instavibe-bootstrap/agents/orchestrate/.env

adk deploy agent_engine \
--display_name "orchestrate-agent" \
--project $GOOGLE_CLOUD_PROJECT \
--region $GOOGLE_CLOUD_LOCATION \
--staging_bucket gs://$GOOGLE_CLOUD_PROJECT-agent-engine \
--trace_to_cloud \
--requirements_file orchestrate/requirements.txt \
orchestrate

עכשיו, כשהכלי Orchestrator מתארח בפלטפורמת Agent Engine המנוהלת, אפליקציית האינטרנט InstaVibe צריכה לתקשר איתו. במקום לבצע אינטראקציה דרך ממשק המשתמש של ADK Dev, אפליקציית האינטרנט תבצע קריאות מרוחקות לנקודת הקצה (endpoint) של Agent Engine.

10-agent-remote.png

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

‫👉📝 פותחים את ~/instavibe-bootstrap/instavibe/introvertally.py ומחליפים את #REPLACE ME initiate agent_engine בקוד הבא. הקוד הבא מאחזר את מזהה Agent Engine ממשתנה סביבה (שנגדיר בהמשך) ומקבל אובייקט לקוח:

ORCHESTRATE_AGENT_ID = os.environ.get('ORCHESTRATE_AGENT_ID')
agent_engine = agent_engines.get(ORCHESTRATE_AGENT_ID)

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

מאחר שאפליקציית האינטרנט InstaVibe (שפועלת ב-Cloud Run) והסוכן Orchestrator (שפועל ב-Agent Engine) הם עכשיו שירותים נפרדים, אפליקציית האינטרנט צריכה לבצע קריאות מרוחקות לנקודת הקצה של Agent Engine כדי ליצור אינטראקציה עם הסוכן.

‫👈📝 בואו נעדכן את הקוד שמבצע את הקריאה הראשונית ליצירת המלצת התוכנית. באותו קובץ introvertally.py, מחליפים את #REPLACE ME Query remote agent get plan בקטע הקוד הבא, שמשתמש בלקוח agent_engine כדי לשלוח את בקשת המשתמש:

agent_engine.stream_query(
                user_id=user_id,
                message=prompt_message,
            )

‫👈📝 לאחר מכן, מעדכנים את הקוד שמטפל באישור של המשתמש (לדוגמה, כשהמשתמש לוחץ על 'אישור התוכנית'). הפעולה הזו שולחת הודעת המשך לאותה שיחה ב-Agent Engine, ומורה ל-Orchestrator להמשיך בפרסום האירוע (שהוא יעביר לנציג Platform Integration). מחליפים את #REPLACE ME Query remote agent for confirmation לאישור ב-introvertally.py ב:

agent_engine.stream_query(
            user_id=agent_session_user_id,
            message=prompt_message,
        )

לנתיבים של אפליקציית האינטרנט צריכה להיות גישה לפונקציות האלה. מוודאים שהפונקציות הנדרשות מ-introvertally.py מיובאות לקובץ הנתיבים של Flask.

‫👉📝 ב-cd ~/instavibe-bootstrap/instavibe/ally_routes.py, נצביע קודם על החלפת המופע # REPLACE ME TO ADD IMPORT באופן הבא:

from introvertally import call_agent_for_plan, post_plan_event

‫👉📝 מוסיפים את תכונת האב-טיפוס ל-InstaVibe, ובקובץ ~/instavibe-bootstrap/instavibe/templates/base.html מחליפים את <!–REPLACE_ME_LINK_TO_INTROVERT_ALLY–> בטקסט הבא:

            <li class="nav-item">
              <a class="nav-link" href="{{ url_for('ally.introvert_ally_page') }}">Introvert Ally</a>
            </li>

כדי שנוכל לפרוס מחדש את אפליקציית InstaVibe, אנחנו צריכים את Resource ID הספציפי של סוכן Orchestrator שפרסנו ב-Agent Engine.

נכון לעכשיו, יכול להיות שהאפשרות לאחזר את המזהה הזה באופן פרוגרמטי באמצעות gcloud מוגבלת, ולכן נשתמש בסקריפט Python (temp-endpoint.py שסופק בסדנה) כדי לאחזר את המזהה ולשמור אותו במשתנה סביבה.

‫👉💻 מריצים את הפקודות הבאות כדי להפעיל את הסקריפט. הסקריפט יתעד את מזהה נקודת הקצה של Agent Engine ויעניק את ההרשאות הנדרשות לחשבון השירות שמוגדר כברירת מחדל ב-Agent Engine (הערה: הסקריפט מוגדר להשתמש בחשבון השירות שמוגדר כברירת מחדל כי אי אפשר לשנות אותו בשלב הזה).

. ~/instavibe-bootstrap/set_env.sh
cd ~/instavibe-bootstrap/instavibe/
source ~/instavibe-bootstrap/env/bin/activate
python temp-endpoint.py
export ORCHESTRATE_AGENT_ID=$(cat temp_endpoint.txt)
echo "ORCHESTRATE_AGENT_ID set to: ${ORCHESTRATE_AGENT_ID}"

מזהה נקודת הקצה של מנוע הסוכן

לבסוף, צריך לפרוס מחדש את אפליקציית האינטרנט InstaVibe עם הקוד המעודכן ועם משתנה הסביבה החדש ORCHESTRATE_AGENT_ID, כדי שהיא תדע איך להתחבר לנציג שלנו שפועל ב-Agent Engine.

‫👈💻 הפקודות הבאות יוצרות מחדש את קובץ האימג' של אפליקציית InstaVibe ופורסות את הגרסה החדשה ב-Cloud Run:

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/instavibe/

export IMAGE_TAG="latest"
export APP_FOLDER_NAME="instavibe"
export IMAGE_NAME="instavibe-webapp"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="instavibe"

echo "Building ${APP_FOLDER_NAME} webapp image..."
gcloud builds submit . \
  --tag=${IMAGE_PATH} \
  --project=${PROJECT_ID}

echo "Deploying ${SERVICE_NAME} to Cloud Run..."

gcloud run deploy ${SERVICE_NAME} \
  --image=${IMAGE_PATH} \
  --platform=managed \
  --region=${REGION} \
  --allow-unauthenticated \
  --set-env-vars="SPANNER_INSTANCE_ID=${SPANNER_INSTANCE_ID}" \
  --set-env-vars="SPANNER_DATABASE_ID=${SPANNER_DATABASE_ID}" \
  --set-env-vars="APP_HOST=0.0.0.0" \
  --set-env-vars="APP_PORT=8080" \
  --set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}" \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
  --set-env-vars="GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}" \
  --set-env-vars="ORCHESTRATE_AGENT_ID=${ORCHESTRATE_AGENT_ID}" \
  --project=${PROJECT_ID} \
  --min-instances=1 \
  --cpu=2 \
  --memory=2Gi

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

בדיקת חוויית InstaVibe המלאה מבוססת-AI

התכונה 'עוזר אישי של InstaVibe' פעילה עכשיו. היא מבוססת על מערכת מרובת סוכנים שמתואמת באמצעות Vertex AI Agent Engine ומתקשרת דרך A2A.

12-02-new.png

לוחצים על InstaVibe Ally ומבקשים ממנו לתכנן אירוע.

12-03-introvertally.png

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

12-04-confirm.png

הכלי לניהול התזמור יורה לסוכן הפלטפורמה ליצור את הפוסט והאירוע ב-InstaVibe. 12-05-posting.png

בדף הבית של InstaVibe אפשר לראות את הפוסט והאירוע החדשים. 12-06-instavibe.png

בדף האירוע יופיעו הפרטים שנוצרו על ידי הסוכן.

12-07-event.png

ניתוח הביצועים באמצעות Cloud Trace

יכול להיות שהתהליך יימשך זמן מה. ‫Vertex AI Agent Engine משתלב עם Cloud Trace, ומאפשר לנו לנתח את זמן האחזור של מערכת מרובת הסוכנים שלנו.

עוברים אל Traces במסוף Google Cloud, בוחרים באפשרות agent_run[orchestrate_agent] ב-Span, אמורים לראות כמה Spans, לוחצים על אחד מהם

12-08-trace.png

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

באופן דומה, כשיוצרים את הפוסט והאירוע, יכול להיות שתראו את הזמן שהוקצה ל-Orchestrator לעיבוד נתונים ולהכנת קריאות לכלים עבור סוכן הפלטפורמה. 12-10-post.png

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

celebrate.png

מעולה! יצרתם, פרסתם ובדקתם בהצלחה מערכת מתוחכמת של AI מרובה-סוכנים באמצעות ADK,‏ A2A,‏ MCP ושירותי Google Cloud. התמודדתם עם תזמור סוכנים, שימוש בכלים, ניהול מצב ופריסה בענן, ויצרתם תכונה פונקציונלית מבוססת-AI עבור InstaVibe. כל הכבוד על השלמת הסדנה!

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

כדי להימנע מחיובים שוטפים בחשבון Google Cloud, חשוב למחוק את המשאבים שיצרנו במהלך הסדנה הזו. הפקודות הבאות יעזרו לכם להסיר את מופע Spanner, שירותי Cloud Run, מאגר Artifact Registry, מפתח API, Vertex AI Agent Engine והרשאות IAM משויכות.

חשוב:

  • חשוב לוודא שאתם מריצים את הפקודות האלה באותו פרויקט ב-Google Cloud שבו השתמשתם בסדנה.
  • אם סגרתם את מסוף Cloud Shell, יכול להיות שחלק ממשתני הסביבה כמו ‎ $PROJECT_ID,‏ ‎$SPANNER_INSTANCE_ID וכו' לא מוגדרים. תצטרכו לייצא אותם מחדש כמו שעשיתם במהלך הגדרת הסדנה, או להחליף את המשתנים בפקודות שלמטה בערכים בפועל.
  • הפקודות האלה ימחקו את המשאבים שלכם באופן סופי. אם יש בפרויקט נתונים חשובים אחרים, כדאי לבדוק שוב לפני שמריצים את הפקודה.

‫👈💻 מריצים את הסקריפטים הבאים כדי לנקות את המערכת.

איפוס משתני סביבה

. ~/instavibe-bootstrap/set_env.sh

מחיקת Agent Engine:

cd ~/instavibe-bootstrap/utils
source ~/instavibe-bootstrap/env/bin/activate
export ORCHESTRATE_AGENT_ID=$(cat ~/instavibe-bootstrap/instavibe/temp_endpoint.txt)
echo "ORCHESTRATE_AGENT_ID set to: ${ORCHESTRATE_AGENT_ID}"
python remote_delete.py
deactivate
echo "Vertex AI Agent Engine deletion initiated."

מחיקה של שירותי Cloud Run:

# InstaVibe Web Application
gcloud run services delete instavibe --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

# MCP Tool Server
gcloud run services delete mcp-tool-server --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

# Planner Agent (A2A Server)
gcloud run services delete planner-agent --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

# Platform MCP Client Agent (A2A Server)
gcloud run services delete platform-mcp-client --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

# Social Agent (A2A Server)
gcloud run services delete social-agent --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

echo "Cloud Run services deletion initiated."

עצירה והסרה של קונטיינר Docker של A2A Inspector

docker rm --force a2a-inspector

מחיקת מכונת Spanner:

echo "Deleting Spanner instance: ${SPANNER_INSTANCE_ID}..."
gcloud spanner instances delete ${SPANNER_INSTANCE_ID} --project=${PROJECT_ID} --quiet
echo "Spanner instance deletion initiated."

מחיקת מאגר של Artifact Registry:

echo "Deleting Artifact Registry repository: ${REPO_NAME}..."
gcloud artifacts repositories delete ${REPO_NAME} --location=${REGION} --project=${PROJECT_ID} --quiet
echo "Artifact Registry repository deletion initiated."

הסרת תפקידים מחשבון שירות:

echo "Removing roles from service account: $SERVICE_ACCOUNT_NAME in project $PROJECT_ID"

# Remove Project-level roles for default service account
gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/spanner.admin"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/spanner.databaseUser"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/artifactregistry.admin"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/cloudbuild.builds.editor"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/run.admin"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/iam.serviceAccountUser"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/aiplatform.user"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/logging.logWriter"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/logging.viewer"


echo "All specified roles have been removed."

מחיקת קבצים מקומיים של סדנאות:

echo "Removing local workshop directory ~/instavibe-bootstrap..."
rm -rf ~/instavibe-bootstrap
rm -rf ~/a2a-inspector
rm -f ~/mapkey.txt
rm -f ~/project_id.txt
echo "Local directory removed."

הסרת המשאבים