1. מבוא
ככל שסוכני ה-AI מקבלים יותר אחריות, קשה יותר לתחזק, להרחיב ולפתח סוכן יחיד שמבצע את כל הפעולות. לרוב, יכולות שונות דורשות אסטרטגיות פריסה שונות, מחזורי עדכון שונים או אפילו צוותים שונים שאחראים עליהן.
- פרוטוקול A2A (Agent2Agent) פותר את בעיית התקשורת – הוא קובע תקן לאופן שבו סוכני AI מגלים את היכולות של סוכנים אחרים ומשתפים פעולה בין מסגרות וארגונים.
- Gemini Enterprise Agent Platform Runtime פותר את בעיית הפריסה – פלטפורמה מנוהלת ללא שרתים (serverless) שמארחת את הסוכנים שלכם עם תמיכה מובנית ב-A2A, התאמה אוטומטית לעומס, נקודות קצה מאובטחות, הפעלת סשנים רציפה ואפס ניהול תשתית.
הם מאפשרים לכם ליצור סוכנים מיוחדים, לפרוס אותם כשירותי A2A שניתן לגלות, ולשלב אותם במערכות מרובות סוכנים.
מה תפַתחו
סוכן הזמנות שמנהל הזמנות לשולחנות במסעדות (יצירה, בדיקה וביטול) באמצעות מצב סשן של ADK שמנוהל על ידי סשנים של פלטפורמת הסוכנים של Gemini Enterprise. אתם פורסים את הסוכן הזה ב-Gemini Enterprise Agent Platform Runtime, שם הוא הופך לזמין לגילוי באמצעות כרטיס הסוכן של פרוטוקול A2A. לאחר מכן , משדרגים את סוכן הקונסיירז של המסעדה Foodie Finds (מתוך ה-codelab של הדרישות המוקדמות. אל דאגה אם לא נכנסתם ל-codelab – הכנו בשבילכם מאגר התחלה) כדי להשתמש בסוכן ההזמנות כסוכן משנה מרוחק מסוג A2A. התוצאה: מערכת מרובת סוכנים שבה כלי התזמור מנתב שאילתות לגבי התפריט אל MCP Toolbox ובקשות להזמנות אל סוכן A2A מרוחק.

מה תלמדו
- פיתוח סוכן ADK שמשתמש בשירות מנוהל של סשנים כדי לנהל נתוני הזמנות
- חשיפת סוכן ADK כשרת A2A עם כרטיסי סוכן ומיומנויות
- פריסת סוכן A2A בזמן הריצה של סוכני Gemini Enterprise
- שימוש בסוכן A2A מרוחק מסוכן ADK אחר באמצעות
RemoteA2aAgentוטיפול בבקשה מאומתת - בדיקה של מערכות מרובות סוכנים באופן מצטבר: A2A מקומי, A2A שנפרס, אינטגרציה חלקית, פריסה מלאה
דרישות מוקדמות
- (מומלץ) השלמתם את ה-Codelabs הבאים :
- יצירת סוכני AI מתמידים באמצעות ADK ו-CloudSQL -> פרטים נוספים על סשן ומצב של ADK
- Agentic RAG with ADK, MCP Toolbox, and Cloud SQL -> אפשר להמשיך לבנות את הסוכן מתוך ה-Codelab הזה, קוד לתחילת הדרך שסופק זהה
- חשבון Google Cloud עם חשבון חיוב פעיל
- היכרות בסיסית עם Python ועם מושגי ADK
2. הגדרת סביבה – המשך מה-codelab הקודם
התיאורים שאנחנו מספקים ב-Codelab הזה הם המשך של ה-Codelab הקודם: Agentic RAG עם ADK, MCP Toolbox ו-Cloud SQL . אפשר להמשיך לעבוד מהנקודה שבה הפסקתם ב-codelab הקודם
אנחנו יכולים להתחיל לבנות בספריית העבודה של ה-codelab הקודם ( ספריית העבודה צריכה להיות build-agent-adk-toolbox-cloudsql). כדי למנוע בלבול, נשנה את שם הספרייה לאותו שם ספרייה שבו אנחנו משתמשים כשאנחנו מתחילים מחדש.
mv ~/build-agent-adk-toolbox-cloudsql ~/adk-a2a-agent-runtime-starter
cloudshell workspace ~/adk-a2a-agent-runtime-starter && cd ~/adk-a2a-agent-runtime-starter
source .env
מוודאים שקובצי המפתח מה-codelab הקודם נמצאים במקום:
echo "--- Restaurant Agent ---"
cat restaurant_agent/agent.py | head -5
echo ""
echo "--- Toolbox Config ---"
cat tools.yaml | head -5
אמור להופיע קובץ restaurant_agent/agent.py עם הייבוא של LlmAgent, וקובץ tools.yaml עם ההגדרה של ערכת הכלים.
בשלב הבא, נאתחל מחדש את סביבת Python
rm -rf .venv
uv sync
בנוסף, צריך לוודא שמסד הנתונים מאותחל ומוכן:
uv run python scripts/verify_seed.py
אם תעקבו אחרי כל פרטי הבדיקה מה-Codelab הקודם, יכול להיות שתראו פלט כמו זה
Menu Items: 16/15 Embeddings: 16/15 ✗ Database not ready
זה בסדר. בבדיקה של מסד הנתונים לא נלקחים בחשבון הנתונים הנוספים שהזנתם בבדיקה של הטמעת הנתונים. אם יש לכם 15 נתונים או יותר, הכול בסדר.
הפעלת API נדרש
לאחר מכן, נצטרך לוודא שהפעלנו את ה-API הנדרש כדי ליצור אינטראקציה עם פלטפורמת הסוכנים של Gemini Enterprise
gcloud services enable \
cloudresourcemanager.googleapis.com
כבר אמורים להיות לכם הקבצים והתשתית הדרושים כדי להמשיך לקטע הבא: A2A Protocol and Gemini Enterprise Agent Runtime !
3. הגדרת סביבה – התחלה חדשה עם מאגר המתחילים
בשלב הזה מכינים את סביבת Cloud Shell, מגדירים את הפרויקט ב-Google Cloud ומשכפלים את מאגר התבניות.
פתיחת Cloud Shell
פותחים את Cloud Shell בדפדפן. Cloud Shell מספקת סביבה שהוגדרה מראש עם כל הכלים שדרושים ל-Codelab הזה. כשמופיעה בקשה, לוחצים על Authorize
אחר כך לוחצים על View (תצוגה) -> Terminal (טרמינל) כדי לפתוח את הטרמינל.הממשק אמור להיראות בערך כך:

זה יהיה הממשק הראשי שלנו, סביבת הפיתוח המשולבת (IDE) בחלק העליון והטרמינל בחלק התחתון
הגדרת ספריית העבודה
משכפלים את מאגר המתחילים. כל הקוד שתכתבו ב-Codelab הזה יישמר כאן:
rm -rf ~/adk-a2a-agent-runtime-starter
git clone https://github.com/alphinside/adk-a2a-agent-runtime-starter.git
cloudshell workspace ~/adk-a2a-agent-runtime-starter && cd ~/adk-a2a-agent-runtime-starter
יוצרים את קובץ .env מהתבנית שסופקה:
cp .env.example .env
כדי לפשט את הגדרת הפרויקט במסוף, מורידים את סקריפט הגדרת הפרויקט הזה לספריית העבודה:
curl -sL https://raw.githubusercontent.com/alphinside/cloud-trial-project-setup/main/setup_verify_trial_project.sh -o setup_verify_trial_project.sh
מריצים את הסקריפט. הוא מאמת את החשבון לחיוב של תקופת הניסיון, יוצר פרויקט חדש (או מאמת פרויקט קיים), שומר את מזהה הפרויקט בקובץ .env בספרייה הנוכחית ומגדיר את הפרויקט הפעיל ב-gcloud.
bash setup_verify_trial_project.sh && source .env
הסקריפט:
- אימות שיש לכם חשבון לחיוב עם תקופת ניסיון פעילה
- בודקים אם יש פרויקט קיים ב-
.env(אם יש) - יוצרים פרויקט חדש או משתמשים בפרויקט קיים
- קישור החשבון לחיוב בתקופת הניסיון לפרויקט
- שומרים את מזהה הפרויקט ב-
.env - הגדרת הפרויקט כפרויקט פעיל ב-
gcloud
כדי לוודא שהפרויקט מוגדר בצורה נכונה, בודקים את הטקסט הצהוב שליד ספריית העבודה בהנחיה של טרמינל Cloud Shell. מזהה הפרויקט צריך להופיע בו.

הפעלת API נדרש
לאחר מכן, נצטרך לוודא שהפעלנו את ה-API הנדרש כדי ליצור אינטראקציה עם פלטפורמת הסוכנים של Gemini Enterprise
gcloud services enable \
aiplatform.googleapis.com \
cloudresourcemanager.googleapis.com
הגדרה של תשתית Starter
קודם כל, נצטרך להתקין יחסי תלות של Python באמצעות uv, שהוא מנהל פרויקטים וחבילות Python מהיר שנכתב ב-Rust ( מסמכי uv). בשיעור Codelab הזה אנחנו משתמשים בו כדי לשמור על מהירות ופשטות בתחזוקת פרויקט Python.
uv sync
לאחר מכן, מריצים את סקריפט ההגדרה המלא, שיוצר את מכונת Cloud SQL, מאכלס נתונים ופורס את שירות Toolbox שישמש כמצב התחלתי של סוכן המסעדה שלנו
bash scripts/full_setup.sh > logs/full_setup.log 2>&1 &
4. מושג: פרוטוקול Agent2Agent (A2A) וזמן הריצה של סוכנים ב-Gemini Enterprise
לפני שמתחילים לבנות, כדאי להקדיש רגע להסבר על שתי הטכנולוגיות העיקריות שמוצגות ב-codelab הזה כדי להרחיב את האפליקציה האג'נטית שלנו.
פרוטוקול Agent2Agent (A2A)
פרוטוקול Agent2Agent (A2A) הוא תקן פתוח שנועד לאפשר תקשורת ועבודה בשיתוף פעולה בין סוכני AI. MCP (Model Context Protocol) מקשר בין סוכנים לבין כלים ונתונים, ו-A2A מקשר בין סוכנים לבין סוכנים אחרים – כך הם יכולים לגלות את היכולות של כל אחד מהם, להעביר משימות ולשתף פעולה בין מסגרות וארגונים.

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

מידע נוסף זמין במאמר מה זה A2A?
Gemini Enterprise Agent Platform Runtime
Agent Runtime הוא שירות מנוהל במלואו ב-Google Cloud לפריסה, להרחבה ולניהול של סוכני AI בסביבת ייצור עם תכונות אבטחה ברמת Enterprise (למשל, VPC Service Controls, CMEK). השירות מטפל בתשתית, כך שאתם יכולים להתמקד בלוגיקה של הסוכן.

Agent Runtime מספק:
- פריסה מנוהלת – פריסת סוכנים שנוצרו באמצעות ADK, LangGraph או כל מסגרת Python באמצעות קריאה אחת ל-SDK
- אירוח A2A – פריסת סוכנים כנקודות קצה שתואמות ל-A2A עם הצגת כרטיסי סוכנים אוטומטית וגישה מאומתת
- סשנים מתמשכים –
VertexAiSessionServiceשומר את היסטוריית השיחות והסטטוס של הבקשות - התאמה אוטומטית לעומס – התאמה לעומס מאפס כדי לטפל בתעבורת נתונים, ללא ניהול תשתית
- ניראות (observability) – מעקב, רישום ביומן ומעקב מובנים באמצעות חבילת הניראות של Google Cloud
- ועוד הרבה תכונות אחרות. פרטים נוספים מופיעים במאמר הזה.
ב-Codelab הזה נסביר איך לפרוס את סוכן ההזמנות ל-Agent Runtime. תהליך הפריסה מבצע סריאליזציה (pickling) של קוד הסוכן ומעלה אותו. סביבת זמן הריצה של הסוכן מספקת נקודת קצה ללא שרת (serverless) שמשרתת את פרוטוקול A2A – סוכנים אחרים (או לקוחות) מקיימים איתה אינטראקציה באמצעות קריאות HTTP רגילות, שעוברות אימות באמצעות פרטי כניסה ל-Google Cloud.
5. איך בונים את סוכן ההזמנות
בשלב הזה יוצרים סוכן ADK חדש שמטפל בהזמנות למסעדות באמצעות מצב סשן. הסוכן תומך בשלוש פעולות – יצירה, בדיקה וביטול – עם מספר הטלפון כמפתח החיפוש. כל נתוני ההזמנות נמצאים במצב הסשן של ADK
יצירת תשתית לסוכן
משתמשים ב-adk create כדי ליצור את מבנה ספריית הסוכן עם המודל והגדרות הפרויקט הנכונים:
source .env
uv run adk create reservation_agent \
--model gemini-2.5-flash \
--project ${GOOGLE_CLOUD_PROJECT} \
--region ${GOOGLE_CLOUD_LOCATION}
פעולה זו יוצרת ספרייה בשם reservation_agent/ עם הקבצים __init__.py, agent.py ו-.env שהוגדרו מראש עבור מודל Gemini בפלטפורמת הסוכנים.
adk-a2a-agent-runtime-starter/ ├── reservation_agent/ │ ├── __init__.py │ ├── agent.py │ └── .env ├── logs ├── scripts └── ...
בשלב הבא, נעדכן את קוד הסוכן
כתיבת קוד הסוכן
פותחים את קובץ הסוכן שנוצר:
cloudshell edit reservation_agent/agent.py
אחר כך מחליפים את התוכן בתוכן הבא:
# reservation_agent/agent.py
from google.adk.agents import LlmAgent
from google.adk.tools import ToolContext
# App-scoped state prefix ensures reservations persist across all sessions.
# See https://adk.dev/sessions/state/ for state scope details.
STATE_PREFIX = "app:reservation:"
def create_reservation(
phone_number: str,
name: str,
party_size: int,
date: str,
time: str,
tool_context: ToolContext,
) -> dict:
"""Create a new restaurant reservation.
Args:
phone_number: Customer's phone number, used as the reservation ID.
name: Name for the reservation.
party_size: Number of guests.
date: Reservation date (e.g., '2025-07-15' or 'this Friday').
time: Reservation time (e.g., '7:00 PM').
Returns:
Confirmation of the reservation.
"""
reservation = {
"name": name,
"party_size": party_size,
"date": date,
"time": time,
"status": "confirmed",
}
tool_context.state[f"{STATE_PREFIX}{phone_number}"] = reservation
return {
"status": "confirmed",
"message": f"Reservation created for {name}, party of {party_size} on {date} at {time}. Phone: {phone_number}.",
}
def check_reservation(phone_number: str, tool_context: ToolContext) -> dict:
"""Look up an existing reservation by phone number.
Args:
phone_number: The phone number used when the reservation was created.
tool_context: ADK tool context for state access.
Returns:
The reservation details, or a message if not found.
"""
reservation = tool_context.state.get(f"{STATE_PREFIX}{phone_number}")
if reservation:
return {"found": True, "reservation": reservation}
return {"found": False, "message": f"No reservation found for {phone_number}."}
def cancel_reservation(phone_number: str, tool_context: ToolContext) -> dict:
"""Cancel an existing reservation by phone number.
Args:
phone_number: The phone number used when the reservation was created.
tool_context: ADK tool context for state access.
Returns:
Confirmation of cancellation, or a message if not found.
"""
key = f"{STATE_PREFIX}{phone_number}"
reservation = tool_context.state.get(key)
if not reservation:
return {"success": False, "message": f"No reservation found for {phone_number}."}
if reservation.get("status") == "cancelled":
return {"success": False, "message": f"Reservation for {phone_number} is already cancelled."}
reservation["status"] = "cancelled"
tool_context.state[key] = reservation
return {"success": True, "message": f"Reservation for {reservation['name']} ({phone_number}) has been cancelled."}
root_agent = LlmAgent(
name="reservation_agent",
model="gemini-2.5-flash",
instruction="""You are a friendly reservation assistant for "Foodie Finds" restaurant.
You help diners create, check, and cancel table reservations.
When a diner wants to make a reservation, collect these details:
- Name for the reservation
- Phone number (used as the reservation ID)
- Party size (number of guests)
- Date
- Time
Always confirm the details before creating the reservation.
When checking or cancelling, ask for the phone number if not provided.
Be concise and professional.""",
tools=[create_reservation, check_reservation, cancel_reservation],
)
6. הכנת הגדרת השרת A2A
הגדרת כרטיס הנציג A2A
כרטיס הסוכן הוא תיאור מובנה של היכולות של הסוכן – סוכנים ולקוחות אחרים משתמשים בו כדי להבין מה הסוכן עושה. יוצרים את הגדרות הכרטיס:
cloudshell edit reservation_agent/a2a_config.py
מעתיקים את הקוד הבא אל reservation_agent/a2a_config.py:
# reservation_agent/a2a_config.py
from a2a.types import AgentSkill
from vertexai.preview.reasoning_engines.templates.a2a import create_agent_card
reservation_skill = AgentSkill(
id="manage_reservations",
name="Restaurant Reservations",
description="Create, check, and cancel table reservations at Foodie Finds restaurant",
tags=["reservations", "restaurant", "booking"],
examples=[
"Book a table for 4 on Friday at 7pm",
"Check reservation for 555-0101",
"Cancel my reservation, phone number 555-0101",
],
input_modes=["text/plain"],
output_modes=["text/plain"],
)
agent_card = create_agent_card(
agent_name="Reservation Agent",
description="Handles restaurant table reservations — create, check, and cancel bookings for Foodie Finds restaurant.",
skills=[reservation_skill],
)
יצירת רכיב ההפעלה A2A
ה-executor מגשר בין פרוטוקול A2A לבין סוכן ADK. הוא מקבל בקשות A2A, מריץ אותן דרך סוכן ADK ומחזיר תוצאות כמשימות A2A:
cloudshell edit reservation_agent/executor.py
מעתיקים את הקוד הבא אל reservation_agent/executor.py:
# reservation_agent/executor.py
import os
from typing import NoReturn
import vertexai
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.tasks import TaskUpdater
from a2a.types import TaskState, TextPart, UnsupportedOperationError
from a2a.utils import new_agent_text_message
from a2a.utils.errors import ServerError
from google.adk.artifacts import InMemoryArtifactService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService, VertexAiSessionService
from google.genai import types
from reservation_agent.agent import root_agent as reservation_agent
class ReservationAgentExecutor(AgentExecutor):
"""Bridge between the A2A protocol and the ADK reservation agent.
Uses InMemorySessionService for local testing, VertexAiSessionService
when deployed to Agent Runtime (detected via GOOGLE_CLOUD_AGENT_ENGINE_ID).
"""
def __init__(self) -> None:
self.agent = None
self.runner = None
def _init_agent(self) -> None:
if self.agent is not None:
return
self.agent = reservation_agent
engine_id = os.environ.get("GOOGLE_CLOUD_AGENT_ENGINE_ID")
if engine_id:
project = os.environ.get("GOOGLE_CLOUD_PROJECT")
location = os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1")
vertexai.init(project=project, location=location)
session_service = VertexAiSessionService(
project=project, location=location, agent_engine_id=engine_id,
)
app_name = engine_id
else:
session_service = InMemorySessionService()
app_name = self.agent.name
self.runner = Runner(
app_name=app_name,
agent=self.agent,
artifact_service=InMemoryArtifactService(),
session_service=session_service,
memory_service=InMemoryMemoryService(),
)
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
if self.agent is None:
self._init_agent()
query = context.get_user_input()
updater = TaskUpdater(event_queue, context.task_id, context.context_id)
user_id = context.message.metadata.get("user_id", "a2a-user") if context.message.metadata else "a2a-user"
if not context.current_task:
await updater.submit()
await updater.start_work()
try:
session = await self._get_or_create_session(context.context_id, user_id)
content = types.Content(role="user", parts=[types.Part(text=query)])
async for event in self.runner.run_async(
session_id=session.id, user_id=user_id, new_message=content,
):
if event.is_final_response():
parts = event.content.parts
answer = " ".join(p.text for p in parts if p.text) or "No response."
await updater.add_artifact([TextPart(text=answer)], name="answer")
await updater.complete()
break
except Exception as e:
await updater.update_status(
TaskState.failed, message=new_agent_text_message(f"Error: {e!s}"),
)
raise
async def _get_or_create_session(self, context_id: str, user_id: str):
app_name = self.runner.app_name
if context_id:
session = await self.runner.session_service.get_session(
app_name=app_name, session_id=context_id, user_id=user_id,
)
if session:
return session
session = await self.runner.session_service.create_session(
app_name=app_name, user_id=user_id, session_id=context_id,
)
return session
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> NoReturn:
raise ServerError(error=UnsupportedOperationError())
ה-Executor מזהה אוטומטית את הסביבה שלו: אם GOOGLE_CLOUD_AGENT_ENGINE_ID מוגדר (Agent Runtime מוסיף את זה בזמן הפריסה), הוא משתמש ב-VertexAiSessionService עבור סשנים מתמשכים. באופן מקומי, המערכת חוזרת לערך InMemorySessionService.
הספרייה reservation_agent אמורה להכיל עכשיו:
reservation_agent/ ├── __init__.py ├── agent.py ├── a2a_config.py ├── executor.py └── .env
7. הכנת סוכן A2A באמצעות Agent Platform SDK ובדיקה מקומית
בשלב הזה, סוכן ההזמנות עוטף כסוכן שתואם ל-A2A באמצעות המחלקה A2aAgent של Agent Platform SDK ( שם ה-SDK עדיין משתמש במונח vertex לצורך תאימות לאחור), ואז בודק את זרימת פרוטוקול A2A המלאה באופן מקומי – אחזור כרטיס הסוכן, שליחת הודעות ואחזור משימות. זהו אותו אובייקט A2aAgent שפורסים ל-Agent Runtime בשלב הבא.
הוספת יחסי תלות
מתקינים את Agent Platform SDK עם תמיכה ב-Agent Runtime וב-ADK, וגם את A2A SDK:
uv add "google-cloud-aiplatform[agent_engines,adk]==1.149.0" "a2a-sdk==0.3.26"
הסבר על הרכיבים של A2A
כדי לעטוף סוכן ADK ל-A2A, צריך שלושה רכיבים:
- כרטיס סוכן – כרטיס ביקור שמתאר את היכולות, הכישורים וכתובת ה-URL של נקודת הקצה של הסוכן. סוכנים אחרים משתמשים בתיאור הזה כדי להבין מה הסוכן שלכם עושה.
- Agent Executor – הגשר בין פרוטוקול A2A לבין הלוגיקה של סוכן ADK. הוא מקבל בקשות A2A, מריץ אותן דרך סוכן ADK ומחזיר תוצאות כמשימות A2A.
- A2aAgent – המחלקה של Agent Platform SDK שמשלבת את הכרטיס ואת רכיב ההפעלה ליחידה שניתן לפרוס.
יצירת סקריפט הבדיקה
יוצרים את הסקריפט הבא כדי לבדוק באופן מקומי
cloudshell edit scripts/test_a2a_agent_local.py
מעתיקים את הקוד הבא אל scripts/test_a2a_agent_local.py:
# scripts/test_a2a_agent_local.py
import asyncio
import json
import os
from pprint import pprint
from dotenv import load_dotenv
from starlette.requests import Request
from vertexai.preview.reasoning_engines import A2aAgent
from reservation_agent.a2a_config import agent_card
from reservation_agent.executor import ReservationAgentExecutor
load_dotenv()
# --- Helper functions for building mock requests ---
def receive_wrapper(data: dict):
async def receive():
byte_data = json.dumps(data).encode("utf-8")
return {"type": "http.request", "body": byte_data, "more_body": False}
return receive
def build_post_request(data: dict = None, path_params: dict = None) -> Request:
scope = {"type": "http", "http_version": "1.1", "headers": [(b"content-type", b"application/json")], "app": None}
if path_params:
scope["path_params"] = path_params
return Request(scope, receive_wrapper(data))
def build_get_request(path_params: dict) -> Request:
scope = {"type": "http", "http_version": "1.1", "query_string": b"", "app": None}
if path_params:
scope["path_params"] = path_params
async def receive():
return {"type": "http.disconnect"}
return Request(scope, receive)
# --- Helper: poll for task completion ---
async def wait_for_task(a2a_agent, task_id, max_retries=30):
"""Poll on_get_task until the task reaches a terminal state."""
for _ in range(max_retries):
request = build_get_request({"id": task_id})
result = await a2a_agent.on_get_task(request=request, context=None)
state = result.get("status", {}).get("state", "")
if state in ["completed", "failed"]:
return result
await asyncio.sleep(1)
return result
def print_task_answer(result):
"""Extract and print the answer from task artifacts."""
print(f"Status: {result.get('status', {}).get('state')}")
for artifact in result.get("artifacts", []):
if artifact.get("parts") and "text" in artifact["parts"][0]:
print(f"Answer: {artifact['parts'][0]['text']}")
# --- Local test ---
async def main():
# Create and set up the A2A agent locally
a2a_agent = A2aAgent(agent_card=agent_card, agent_executor_builder=ReservationAgentExecutor)
a2a_agent.set_up()
# 1. Get agent card
print("=" * 50)
print("1. Retrieving agent card...")
print("=" * 50)
request = build_get_request(None)
card_response = await a2a_agent.handle_authenticated_agent_card(request=request, context=None)
print(f"Agent: {card_response.get('name')}")
print(f"Skills: {[s.get('name') for s in card_response.get('skills', [])]}")
# 2. Create a reservation
print("\n" + "=" * 50)
print("2. Creating a reservation...")
print("=" * 50)
message_data = {
"message": {
"messageId": f"msg-{os.urandom(4).hex()}",
"content": [{"text": "Book a table for 2 on Saturday at 6pm. Name: Bob, Phone: 555-0202"}],
"role": "ROLE_USER",
},
}
request = build_post_request(message_data)
response = await a2a_agent.on_message_send(request=request, context=None)
task_id = response["task"]["id"]
context_id = response["task"].get("contextId")
print(f"Task ID: {task_id}")
# 3. Wait for result
print("\n" + "=" * 50)
print("3. Waiting for task result...")
print("=" * 50)
result = await wait_for_task(a2a_agent, task_id)
print_task_answer(result)
# 4. Check the reservation (same context for session continuity)
print("\n" + "=" * 50)
print("4. Checking the reservation...")
print("=" * 50)
check_data = {
"message": {
"messageId": f"msg-{os.urandom(4).hex()}",
"content": [{"text": "Check the reservation for 555-0202"}],
"role": "ROLE_USER",
"contextId": context_id,
},
}
request = build_post_request(check_data)
check_response = await a2a_agent.on_message_send(request=request, context=None)
check_result = await wait_for_task(a2a_agent, check_response["task"]["id"])
print_task_answer(check_result)
# 5. Cancel the reservation
print("\n" + "=" * 50)
print("5. Cancelling the reservation...")
print("=" * 50)
cancel_data = {
"message": {
"messageId": f"msg-{os.urandom(4).hex()}",
"content": [{"text": "Cancel the reservation for 555-0202"}],
"role": "ROLE_USER",
"contextId": context_id,
},
}
request = build_post_request(cancel_data)
cancel_response = await a2a_agent.on_message_send(request=request, context=None)
cancel_result = await wait_for_task(a2a_agent, cancel_response["task"]["id"])
print_task_answer(cancel_result)
print("\n" + "=" * 50)
print("All tests passed!")
print("=" * 50)
if __name__ == "__main__":
asyncio.run(main())
סקריפט הבדיקה מייבא את כרטיס הסוכן ואת קובץ ההפעלה שיצרתם בשלב הקודם – ללא כפילות. הכלי ייצור A2aAgent מקומי, ידמה קריאות לפרוטוקול A2A באמצעות בקשות HTTP מדומות ויאמת את כל שלוש פעולות ההזמנה.
מכיוון שלא מוגדר GOOGLE_CLOUD_AGENT_ENGINE_ID באופן מקומי, המבצע משתמש ב-InMemorySessionService. כשפורסים את אותו רכיב לזמן ריצה של סוכן, הוא עובר אוטומטית ל-VertexAiSessionService עבור סשנים מתמשכים.
הרצת הבדיקה
PYTHONPATH=. uv run python scripts/test_a2a_agent_local.py
הפלט מחולק לחמישה שלבים:
- כרטיס הסוכן – מאחזר את היכולות והמיומנויות של הסוכן
- Create reservation (יצירת הזמנה) – מזמין שולחן ומחזיר משימה עם האישור
- Get task result – אחזור המשימה שהושלמה עם התשובה
- Check reservation (בדיקת הזמנה) – חיפוש ההזמנה לפי מספר הטלפון
- ביטול ההזמנה – מבטל את ההזמנה ומאשר את הביטול
דוגמה לפלט כמו שמוצג בהמשך
================================================== 1. Retrieving agent card... ================================================== Agent: Reservation Agent Skills: ['Restaurant Reservations'] ================================================== 2. Creating a reservation... ================================================== Task ID: f7f7004d-cfea-49c2-b57d-5bca9959e193 ================================================== 3. Waiting for task result... ================================================== Status: TASK_STATE_COMPLETED Answer: Your reservation for Bob, party of 2, on Saturday at 6:00 PM has been confirmed. The phone number associated is 555-0202. ================================================== 4. Checking the reservation... ================================================== Status: TASK_STATE_COMPLETED Answer: I found a reservation for Bob, party of 2, on Saturday at 6:00 PM. The reservation status is confirmed. ================================================== 5. Cancelling the reservation... ================================================== Status: TASK_STATE_COMPLETED Answer: Your reservation for Bob (555-0202) has been cancelled. ================================================== All tests passed! ==================================================
בשלב הזה, אימתתם את הדברים הבאים: כרטיס הסוכן של A2A מתאר את הכישורים הנכונים, כל שלושת פעולות ההזמנה פועלות באמצעות זרימת ההודעות/המשימות של פרוטוקול A2A, והמצב נשמר בהודעות באותו ההקשר.
8. פריסת סוכן ההזמנות ב-Agent Runtime
בשלב הזה, סוכן ההזמנות נפרס בזמן הריצה של פלטפורמת הסוכנים של Gemini Enterprise – פלטפורמה מנוהלת, בלי שרתים, שמארחת את הסוכן וחושפת אותו כנקודת קצה מאובטחת של A2A. אחרי הפריסה, כל לקוח מורשה יכול לגלות את הסוכן ולקיים איתו אינטראקציה באמצעות נקודות קצה רגילות של HTTP A2A.
יצירת קטגוריית ביניים
יוצרים קטגוריה של Cloud Storage לצורך הכנת Agent Runtime. סביבת זמן הריצה של הסוכן משתמשת בדלי הזה כדי להעלות את הקוד והתלות של הסוכן במהלך הפריסה:
STAGING_BUCKET="${GOOGLE_CLOUD_PROJECT}-adk-a2a-agent-runtime"
gsutil mb -l $REGION -p $GOOGLE_CLOUD_PROJECT gs://$STAGING_BUCKET 2>/dev/null || echo "Bucket already exists"
echo "STAGING_BUCKET=$STAGING_BUCKET" >> .env
source .env
יצירת סקריפט הפריסה
בשלב הבא, צריך להכין את סקריפט הפריסה
cloudshell edit scripts/deploy_a2a_agent_runtime.py
מעתיקים את הקוד הבא אל scripts/deploy_a2a_agent_runtime.py:
# scripts/deploy_a2a_agent_runtime.py
import os
from pathlib import Path
import vertexai
from dotenv import load_dotenv
from google.genai import types
from vertexai.preview.reasoning_engines import A2aAgent
from reservation_agent.a2a_config import agent_card
from reservation_agent.executor import ReservationAgentExecutor
load_dotenv()
PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
REGION = os.environ["REGION"]
STAGING_BUCKET = os.environ.get("STAGING_BUCKET", f"{PROJECT_ID}-adk-a2a-agent-runtime")
BUCKET_URI = f"gs://{STAGING_BUCKET}"
a2a_agent = A2aAgent(
agent_card=agent_card,
agent_executor_builder=ReservationAgentExecutor,
)
def main():
vertexai.init(project=PROJECT_ID, location=REGION, staging_bucket=BUCKET_URI)
client = vertexai.Client(
project=PROJECT_ID,
location=REGION,
http_options=types.HttpOptions(api_version="v1beta1"),
)
print("Deploying Reservation Agent to Agent Runtime...")
print("This may take 3-5 minutes.")
remote_agent = client.agent_engines.create(
agent=a2a_agent,
config={
"display_name": agent_card.name,
"description": agent_card.description,
"requirements": [
"google-cloud-aiplatform[agent_engines,adk]==1.149.0",
"a2a-sdk==0.3.26",
"google-adk==1.29.0",
"cloudpickle",
"pydantic"
],
"extra_packages": [
"./reservation_agent",
],
"http_options": {
"api_version": "v1beta1",
},
"staging_bucket": BUCKET_URI,
},
)
resource_name = remote_agent.api_resource.name
print(f"\nDeployment complete!")
print(f"Resource name: {resource_name}")
env_path = Path(".env")
lines = env_path.read_text().splitlines() if env_path.exists() else []
lines = [l for l in lines if not l.startswith("RESERVATION_AGENT_RESOURCE_NAME=")]
lines.append(f"RESERVATION_AGENT_RESOURCE_NAME={resource_name}")
env_path.write_text("\n".join(lines) + "\n")
print("Written RESERVATION_AGENT_RESOURCE_NAME to .env")
if __name__ == "__main__":
main()
סקריפט הפריסה מייבא את אותם agent_card וReservationAgentExecutor שבהם נעשה שימוש בבדיקה המקומית – אין כפילות בקוד. הסוכן בזמן ריצה מבצע סריאליזציה (pickling) של אובייקט A2aAgent יחד עם התלות שלו לצורך פריסה. בסוף סקריפט הפריסה, הערך RESERVATION_AGENT_RESOURCE_NAME ייכתב לקובץ .env
פריסה לזמן ריצה של סוכן
מריצים את סקריפט הפריסה:
PYTHONPATH=. uv run python scripts/deploy_a2a_agent_runtime.py
הפריסה נמשכת 3-5 דקות. הסקריפט מספק נקודת קצה בלי שרת (serverless) בזמן ריצה של סוכן שמארחת את סוכן ההזמנות. אחרי פריסה מוצלחת, יוצג פלט דומה לזה שמופיע בהמשך
Deploying Reservation Agent to Agent Runtime... This may take 3-5 minutes. Deployment complete! Resource name: projects/your-project-number/locations/us-central1/reasoningEngines/your-agent-deployment-unique-id Written RESERVATION_AGENT_RESOURCE_NAME to .env
אפשר לראות את הסוכן שנפרס במסוף Cloud. מחפשים את Agent Platform בסרגל החיפוש של המסוף.

אחר כך, בכרטיסייה הימנית, מעבירים את העכבר מעל Agents ובוחרים באפשרות Deployments.

האפליקציה Reservation Agent תופיע ברשימת הפריסות כמו שמוצג בהמשך

בדיקת הסוכן הפעיל
עכשיו אפשר לבדוק את הסוכן שפרסנו. כדי לעשות זאת, צריך ליצור סקריפט בדיקה בשבילו:
cloudshell edit scripts/test_a2a_agent_runtime.py
מעתיקים את הקוד הבא אל scripts/test_a2a_agent_runtime.py:
# scripts/test_a2a_agent_runtime.py
import asyncio
import os
import time
import vertexai
from a2a.types import TaskState
from dotenv import load_dotenv
from google.genai import types
load_dotenv()
PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
REGION = os.environ["REGION"]
RESOURCE_NAME = os.environ["RESERVATION_AGENT_RESOURCE_NAME"]
async def main():
vertexai.init(project=PROJECT_ID, location=REGION)
client = vertexai.Client(
project=PROJECT_ID, location=REGION,
http_options=types.HttpOptions(api_version="v1beta1"),
)
agent = client.agent_engines.get(name=RESOURCE_NAME)
# 1. Get agent card
print("=" * 50)
print("1. Retrieving agent card...")
print("=" * 50)
card = await agent.handle_authenticated_agent_card()
print(f"Agent: {card.name}")
print(f"URL: {card.url}")
print(f"Skills: {[s.name for s in card.skills]}")
# 2. Send a reservation request
print("\n" + "=" * 50)
print("2. Sending reservation request...")
print("=" * 50)
message_data = {
"messageId": "msg-remote-001",
"role": "user",
"parts": [{"kind": "text", "text": "Book a table for 3 on Sunday at noon. Name: Carol, Phone: 555-0303"}],
}
response = await agent.on_message_send(**message_data)
task_object = None
for chunk in response:
if isinstance(chunk, tuple) and len(chunk) > 0 and hasattr(chunk[0], "id"):
task_object = chunk[0]
break
task_id = task_object.id
print(f"Task ID: {task_id}")
print(f"Status: {task_object.status.state}")
# 3. Poll for result
print("\n" + "=" * 50)
print("3. Waiting for result...")
print("=" * 50)
result = None
for _ in range(30):
try:
result = await agent.on_get_task(id=task_id)
if result.status.state in [TaskState.completed, TaskState.failed]:
break
except Exception:
pass
time.sleep(1)
print(f"Final status: {result.status.state}")
if result.artifacts:
for artifact in result.artifacts:
if artifact.parts and hasattr(artifact.parts[0], "root") and hasattr(artifact.parts[0].root, "text"):
print(f"Answer: {artifact.parts[0].root.text}")
print("\n" + "=" * 50)
print("Remote agent test passed!")
print("=" * 50)
if __name__ == "__main__":
asyncio.run(main())
עכשיו נריץ את הבדיקה
source .env
uv run python scripts/test_a2a_agent_runtime.py
בפלט מוצג כרטיס הסוכן עם מיומנות 'הזמנות למסעדות', ואחריו השלמת המשימה עם אישור הזמנה.
================================================== 1. Retrieving agent card... ================================================== Agent: Reservation Agent URL: https://us-central1-aiplatform.googleapis.com/v1beta1/projects/your-project-id/locations/us-central1/reasoningEngines/your-agent-unique-id/a2a Skills: ['Restaurant Reservations'] ================================================== 2. Sending reservation request... ================================================== Task ID: b34585d0-5f03-4cb0-85a3-40710a0d224d Status: TaskState.completed ================================================== 3. Waiting for result... ================================================== Final status: TaskState.completed Answer: Your reservation for Carol, party of 3 on Sunday at noon with phone number 555-0303 is confirmed. ================================================== Remote agent test passed! ==================================================
סוכן ההזמנות פועל עכשיו בהצלחה כנקודת קצה מנוהלת של A2A ב-Agent Runtime.
9. שילוב של סוכן הזמנות A2A עם סוכן ראשי של מסעדה
בשלב הזה, סוכן המסעדה משודרג כך שישתמש בסוכן ההזמנות שנפרס כסוכן משנה מרוחק מסוג A2A. הכלי לניהול תזמור רץ באופן מקומי, בעוד שהסוכן להזמנות רץ ב-Agent Runtime – שילוב חלקי שמאמת את החיבור בין סוכן לאפליקציה לפני פריסה מלאה.
פתרון בעיות בכתובת ה-URL של כרטיס הנציג A2A
כדי לגלות את היכולות של סוכן ההזמנות, RemoteA2aAgent צריך את כתובת ה-URL של הכרטיס שלו. יוצרים סקריפט שמביא את כתובת ה-URL הזו מ-Agent Runtime וכותב אותה אל .env של סוכן המסעדה:
cloudshell edit scripts/resolve_agent_card_url.py
מעתיקים את הקוד הבא אל scripts/resolve_agent_card_url.py:
# scripts/resolve_agent_card_url.py
import asyncio
import os
from pathlib import Path
import vertexai
from dotenv import load_dotenv
from google.genai import types
load_dotenv()
PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
REGION = os.environ["REGION"]
RESOURCE_NAME = os.environ["RESERVATION_AGENT_RESOURCE_NAME"]
async def main():
vertexai.init(project=PROJECT_ID, location=REGION)
client = vertexai.Client(
project=PROJECT_ID, location=REGION,
http_options=types.HttpOptions(api_version="v1beta1"),
)
agent = client.agent_engines.get(name=RESOURCE_NAME)
card = await agent.handle_authenticated_agent_card()
card_url = f"{card.url}/v1/card"
print(f"Agent: {card.name}")
print(f"Card URL: {card_url}")
# Write to restaurant_agent/.env
# Write to both restaurant_agent/.env (for adk web) and root .env (for Cloud Run deploy)
for env_path in [Path("restaurant_agent/.env"), Path(".env")]:
lines = env_path.read_text().splitlines() if env_path.exists() else []
lines = [l for l in lines if not l.startswith("RESERVATION_AGENT_CARD_URL=")]
lines.append(f"RESERVATION_AGENT_CARD_URL={card_url}")
env_path.write_text("\n".join(lines) + "\n")
print(f"Written RESERVATION_AGENT_CARD_URL to {env_path}")
if __name__ == "__main__":
asyncio.run(main())
מריצים את הסקריפט כדי לאכלס את הקובץ .env בכתובת ה-URL של כרטיס הסוכן
uv run python scripts/resolve_agent_card_url.py
source .env
עדכון הסוכן של המסעדה
פותחים את הקובץ של סוכן המסעדה:
cloudshell edit restaurant_agent/agent.py
לאחר מכן, מחליפים את התוכן בגרסה המעודכנת שכוללת את סוכן ההזמנות המרוחק כסוכן משנה:
# restaurant_agent/agent.py
import os
import httpx
from google.adk.agents import LlmAgent
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
from google.auth import default
from google.auth.transport.requests import Request as AuthRequest
from toolbox_adk import ToolboxToolset
TOOLBOX_URL = os.environ.get("TOOLBOX_URL", "http://127.0.0.1:5000")
RESERVATION_AGENT_CARD_URL = os.environ.get("RESERVATION_AGENT_CARD_URL", "")
toolbox = ToolboxToolset(TOOLBOX_URL)
class GoogleCloudAuth(httpx.Auth):
"""Auto-refreshing Google Cloud authentication for httpx.
Refreshes the access token before each request if expired,
so long-running agents never hit 401 errors.
"""
def __init__(self):
self.credentials, _ = default(
scopes=["https://www.googleapis.com/auth/cloud-platform"]
)
def auth_flow(self, request):
# Refresh the token if it is expired or missing
if not self.credentials.valid:
self.credentials.refresh(AuthRequest())
request.headers["Authorization"] = f"Bearer {self.credentials.token}"
yield request
reservation_remote_agent = RemoteA2aAgent(
name="reservation_agent",
description="Handles restaurant table reservations — create, check, and cancel bookings. Delegate to this agent when the user wants to book a table, check a reservation, or cancel a reservation.",
agent_card=RESERVATION_AGENT_CARD_URL,
httpx_client=httpx.AsyncClient(auth=GoogleCloudAuth(), timeout=60),
)
root_agent = LlmAgent(
name="restaurant_agent",
model="gemini-2.5-flash",
instruction="""You are a friendly and knowledgeable concierge at "Foodie Finds," a restaurant. Your job:
- Help diners browse the menu by category or cuisine type.
- Provide full details about specific dishes, including ingredients, price, and dietary information.
- Recommend dishes based on natural language descriptions of what the diner is craving.
- Add new menu items when asked.
- For reservation requests (booking, checking, or cancelling tables), delegate to the reservation_agent.
When a diner asks about a specific dish by name or cuisine, use the get-item-details tool.
When a diner asks for a specific category or cuisine type, use the search-menu tool.
When a diner describes what kind of food they want — by flavor, texture, dietary needs, or cravings — use the search-menu-by-description tool for semantic search.
When in doubt between search-menu and search-menu-by-description, prefer search-menu-by-description — it searches dish descriptions and finds more relevant matches.
If a dish is not available (available is false), let the diner know and suggest similar alternatives from the search results.
Be conversational, knowledgeable, and concise.""",
tools=[toolbox],
sub_agents=[reservation_remote_agent],
)
השינויים העיקריים מהגרסה הקודמת:
-
GoogleCloudAuth— handler מותאם אישית שלhttpx.Authשמבצע רענון של אסימון הגישה ל-Google Cloud לפני כל בקשה. ה-Agent Runtime דורש קריאות מאומתות מ-A2A, והתוקף של הטוקנים פג אחרי פרק זמן מסוים. -
RemoteA2aAgentקורא אתRESERVATION_AGENT_CARD_URLמתוך.env(שנכתב על ידי סקריפט הפתרון) ומשתמש ב-httpx_clientהמאומת - רשום כסוכן משנה – רכיב ה-Orchestrator של ADK מעביר אליו באופן אוטומטי בקשות להזמנות
- הוראה מעודכנת שכוללת מידע על מתן הרשאה לניהול הזמנות
בדיקה מקומית של הסוכן המשולב
הסוכן למתחילים דרש שילוב עם MCP Toolbox, והקובץ הנדרש כבר היה אמור להינתן מתוך Codelab קודם או מתוך מאגר המתחילים. אנחנו רק צריכים לוודא שתהליך השימוש בארגז הכלים פועל בצורה תקינה.
אם TOOLBOX_URL ב-.env כבר מצביע על שירות Cloud Run (מה-codelab הקודם או אולי מ-full_setup.sh של מאגר המתחילים), אפשר לדלג על השלב הזה – הסוכן יתחבר ל-Toolbox שנפרס.
אם אתם צריכים Toolbox מקומי, כדאי לבדוק אם הוא כבר פועל לפני שמתחילים מופע חדש:
if curl -s http://127.0.0.1:5000/api/toolsets > /dev/null 2>&1; then
echo "Toolbox already running on port 5000"
else
set -a; source .env; set +a
./toolbox --config=tools.yaml > logs/toolbox.log 2>&1 &
echo "Toolbox started"
fi
אחר כך, אפשר לנסות ליצור אינטראקציה עם נציג המסעדה דרך ממשק המשתמש של ADK web dev
uv run adk web --allow_origins "regex:https://.*\.cloudshell\.dev" --port 8080
פותחים את ממשק המשתמש האינטרנטי של ADK באמצעות תצוגה מקדימה באינטרנט של Cloud Shell (לוחצים על לחצן התצוגה המקדימה באינטרנט, משנים את היציאה ל-8080) ואז בוחרים באפשרות restaurant_agent

כדי לבדוק שיחה מעורבת:
שאילתת תפריט
What Italian dishes do you have?
בקשה להזמנה
I want to create reservation under name Bob, phone number 123456
בדיקת ההזמנה
יצירת סשן חדש ( התחלת שיחה חדשה):
Check the reservation for 123456



מפסיקים את התהליך של adk web באמצעות Ctrl+C פעמיים. בשלב הבא, נשלים את המערכת על ידי פריסה מלאה של הסוכן
10. פריסת סוכן המסעדה המעודכן ב-Cloud Run
בשלב הזה פורסים מחדש את סוכן המסעדה ב-Cloud Run עם שילוב A2A, וכך משלימים את הפריסה של המערכת מרובת הסוכנים.
מתן הרשאות גישה ל-Agent Runtime
לחשבון השירות של Cloud Run צריכה להיות הרשאה להפעיל את Agent Runtime. מקצים את התפקיד roles/aiplatform.user לחשבון השירות שמוגדר כברירת מחדל ב-Compute Engine:
PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)')
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
--role="roles/aiplatform.user"
פריסה ב-Cloud Run
בהגדרה הזו, אנחנו מניחים ששירות הסוכן של המסעדה כבר קיים מה-codelab הקודם או מהפעלת scripts/full_setup.sh אם מתחילים מחדש. הפעולה הזו תפרוס מחדש עם הקוד המעודכן (שילוב חדש של RemoteA2aAgent) ותוסיף את כתובת ה-URL של כרטיס סוכן ההזמנות כמשתנה סביבה חדש – משתני סביבה קיימים (TOOLBOX_URL, GOOGLE_CLOUD_PROJECT וכו') יישמרו:
gcloud run deploy restaurant-agent \
--source . \
--region=$REGION \
--allow-unauthenticated \
--update-env-vars="RESERVATION_AGENT_CARD_URL=$RESERVATION_AGENT_CARD_URL" \
--min-instances=0 \
--max-instances=1 \
--memory=1Gi \
--port=8080
בדיקת המערכת לאחר הפריסה המלאה
מקבלים את כתובת ה-URL של השירות שנפרס:
AGENT_URL=$(gcloud run services describe restaurant-agent --region=$REGION --format='value(status.url)')
echo "Agent URL: $AGENT_URL"
פותחים את כתובת ה-URL בדפדפן. ממשק המשתמש האינטרנטי של ADK נטען – זהו אותו ממשק שבו השתמשתם באופן מקומי, ועכשיו הוא פועל ב-Cloud Run.
אתם יכולים לשוחח בצ'אט עם הנציג
שאילתת תפריט
What spicy dishes do you have?
בקשה להזמנה
Book a table for 4 on Friday at 7pm. Name: Eve, Phone: 555-0505
בדיקת ההזמנה
יצירת סשן חדש ( התחלת שיחה חדשה):
Check reservation for 555-0505


מערכת מרובת הסוכנים פרוסה במלואה. סוכן המסעדה ב-Cloud Run מתאם בין שני שירותי בק-אנד: MCP Toolbox לפעולות שקשורות לתפריט וסוכן ההזמנות A2A ב-Agent Runtime.
11. מעולה!
יצרתם ופרסתם מערכת מרובת סוכנים באמצעות פרוטוקול A2A ב-Google Cloud.
מה למדתם
- יצירת סוכן ADK שמשתמש במצב סשן (
ToolContext) כדי לנהל נתוני הזמנות בלי מסד נתונים - פריסת סוכן A2A ב-Agent Runtime באמצעות Agent Platform SDK
- צריכת סוכן A2A מרוחק מסוכן ADK אחר באמצעות
RemoteA2aAgentכסוכן משנה - בדקנו את המערכת באופן מצטבר: A2A מקומי → A2A שנפרס → שילוב חלקי → פריסה מלאה
פינוי נפח
כדי להימנע מחיובים בחשבון Google Cloud, מוחקים את המשאבים שנוצרו ב-codelab הזה.
אפשרות 1: מחיקת הפרויקט (מומלץ)
gcloud projects delete $GOOGLE_CLOUD_PROJECT
אפשרות 2: מחיקת משאבים ספציפיים
# Delete the Agent Runtime deployment
uv run python -c "
import vertexai
from google.genai import types
vertexai.init(project='$GOOGLE_CLOUD_PROJECT', location='$REGION')
client = vertexai.Client(
project='$GOOGLE_CLOUD_PROJECT', location='$REGION',
http_options=types.HttpOptions(api_version='v1beta1'),
)
agent = client.agent_engines.get(name='$RESERVATION_AGENT_RESOURCE_NAME')
agent.delete(force=True)
print('Agent Runtime deployment deleted.')
"
# Delete Cloud Run services
gcloud run services delete restaurant-agent --region=$REGION --quiet
gcloud run services delete toolbox-service --region=$REGION --quiet
# Delete Cloud SQL instance
gcloud sql instances delete $DB_INSTANCE --quiet
# Delete GCS staging bucket
gsutil rm -r gs://$STAGING_BUCKET