1. Introduction
À mesure que les agents IA assument davantage de responsabilités, il devient difficile de gérer, de faire évoluer et de développer un agent unique qui fait tout. Différentes fonctionnalités nécessitent souvent des stratégies de déploiement ou des cycles de mise à jour différents, voire des équipes différentes pour les gérer.
- Le protocole A2A (Agent2Agent) résout le problème de communication en normalisant la façon dont les agents découvrent les capacités des autres et collaborent entre les frameworks et les organisations.
- Gemini Enterprise Agent Platform Runtime résout le problème du déploiement. Il s'agit d'une plate-forme sans serveur entièrement gérée qui héberge vos agents avec une compatibilité A2A intégrée, un autoscaling, des points de terminaison sécurisés, des sessions persistantes et une gestion de l'infrastructure nulle.
Ensemble, ils vous permettent de créer des agents spécialisés, de les déployer en tant que services A2A détectables et de les composer en systèmes multi-agents.
Objectifs de l'atelier
Un agent de réservation qui gère les réservations de tables de restaurant (création, vérification et annulation) à l'aide de l'état de session ADK géré par Gemini Enterprise Agent Platform Sessions. Vous déployez cet agent dans Gemini Enterprise Agent Platform Runtime, où il devient détectable via la fiche d'agent du protocole A2A. Vous allez ensuite mettre à niveau l'agent de concierge de restaurant Foodie Finds (à partir de l'atelier de programmation prérequis, ne vous inquiétez pas si vous n'avez pas suivi l'atelier de programmation, nous avons préparé un dépôt de démarrage pour vous) pour utiliser l'agent de réservation en tant que sous-agent A2A distant. Résultat : un système multi-agents dans lequel l'orchestrateur achemine les requêtes de menu vers la boîte à outils MCP et les demandes de réservation vers l'agent A2A distant.

Points abordés
- Créer un agent ADK qui utilise le service de gestion de session pour gérer les données de réservation
- Exposer un agent ADK en tant que serveur A2A avec des cartes d'agent et des compétences
- Déployer un agent A2A dans Gemini Enterprise Agent Runtime
- Consommer un agent A2A distant à partir d'un autre agent ADK à l'aide de
RemoteA2aAgentet gérer les requêtes authentifiées - Tester les systèmes multi-agents de manière incrémentielle : A2A local, A2A déployé, intégration partielle, déploiement complet
Prérequis
- (Recommandé) Vous avez terminé les ateliers de programmation suivants :
- Créer des agents IA persistants avec ADK et CloudSQL : plus d'informations sur la session et l'état ADK
- RAG agentique avec ADK, MCP Toolbox et Cloud SQL : vous pouvez continuer à créer votre agent à partir de cet atelier de programmation. Le code de démarrage fourni est identique.
- Un compte Google Cloud avec un compte de facturation actif
- Connaître les bases de Python et les concepts ADK
2. Configuration de l'environnement : continuer à partir de l'atelier de programmation précédent
Les récits que nous fournissons dans cet atelier de programmation sont en fait la suite de cet atelier de programmation prérequis : RAG agentique avec ADK, MCP Toolbox et Cloud SQL . Vous pouvez continuer votre travail à partir de l'atelier de programmation précédent.
Nous pouvons commencer à créer dans le répertoire de travail de l'atelier de programmation précédent ( le répertoire de travail doit être build-agent-adk-toolbox-cloudsql). Pour éviter toute confusion, renommons le répertoire avec le même nom de répertoire que celui que nous utilisons lorsque nous repartons de zéro.
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
Vérifiez que les fichiers clés de l'atelier de programmation précédent sont en place :
echo "--- Restaurant Agent ---"
cat restaurant_agent/agent.py | head -5
echo ""
echo "--- Toolbox Config ---"
cat tools.yaml | head -5
Vous devriez voir le fichier restaurant_agent/agent.py avec l'importation LlmAgent et le fichier tools.yaml avec la configuration de votre boîte à outils.
Ensuite, réinitialisons notre environnement Python.
rm -rf .venv
uv sync
Vérifiez également que la base de données est initialisée et prête :
uv run python scripts/verify_seed.py
Si vous suivez chaque détail de test de l'atelier de programmation précédent, vous pouvez obtenir un résultat comme celui-ci.
Menu Items: 16/15 Embeddings: 16/15 ✗ Database not ready
Pas de problème ! La vérification de la base de données ne tient pas compte des données supplémentaires que vous saisissez lors de la vérification de l'ingestion de données. Tant que vous disposez d'au moins 15 données, tout va bien.
Activer l'API requise
Ensuite, nous devrons nous assurer d'activer l'API requise pour interagir avec Gemini Enterprise Agent Platform.
gcloud services enable \
cloudresourcemanager.googleapis.com
Vous devriez déjà disposer des fichiers et de l'infrastructure nécessaires pour passer à la section suivante : A2A Protocol and Gemini Enterprise Agent Runtime !
3. Configuration de l'environnement : repartir à zéro avec le dépôt de démarrage
Cette étape prépare votre environnement Cloud Shell, configure votre projet Google Cloud et clone le dépôt de démarrage.
Ouvrir Cloud Shell
Ouvrez Cloud Shell dans votre navigateur. Cloud Shell fournit un environnement préconfiguré avec tous les outils dont vous avez besoin pour cet atelier de programmation. Cliquez sur Autoriser lorsque vous y êtes invité.
Cliquez ensuite sur Afficher > Terminal pour ouvrir le terminal.Votre interface devrait ressembler à ceci :

Ce sera notre interface principale, avec l'IDE en haut et le terminal en bas.
Configurer votre répertoire de travail
Clonez le dépôt de démarrage. Tout le code que vous écrirez dans cet atelier de programmation se trouvera ici :
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
Créez le fichier .env à partir du modèle fourni :
cp .env.example .env
Pour simplifier la configuration du projet dans votre terminal, téléchargez ce script de configuration du projet dans votre répertoire de travail :
curl -sL https://raw.githubusercontent.com/alphinside/cloud-trial-project-setup/main/setup_verify_trial_project.sh -o setup_verify_trial_project.sh
Exécutez le script. Il valide votre compte de facturation d'essai, crée un projet (ou en valide un existant), enregistre l'ID de votre projet dans un fichier .env du répertoire actuel et définit le projet actif dans gcloud.
bash setup_verify_trial_project.sh && source .env
Le script va :
- Vérifier que vous disposez d'un compte de facturation d'essai actif
- Recherchez un projet existant dans
.env(le cas échéant). - Créez un projet ou réutilisez-en un existant.
- Associer le compte de facturation d'essai à votre projet
- Enregistrez l'ID du projet dans
.env. - Définir le projet comme projet
gcloudactif
Vérifiez que le projet est correctement défini en examinant le texte jaune à côté de votre répertoire de travail dans l'invite du terminal Cloud Shell. L'ID de votre projet devrait s'afficher.

Activer l'API requise
Ensuite, nous devrons nous assurer d'activer l'API requise pour interagir avec Gemini Enterprise Agent Platform.
gcloud services enable \
aiplatform.googleapis.com \
cloudresourcemanager.googleapis.com
Configuration de l'infrastructure de démarrage
Tout d'abord, nous devons installer les dépendances Python à l'aide de uv. Il s'agit d'un gestionnaire de packages et de projets Python rapide écrit en Rust ( documentation uv). Cet atelier de programmation l'utilise pour la rapidité et la simplicité de la maintenance du projet Python.
uv sync
Exécutez ensuite le script de configuration complet, qui crée l'instance Cloud SQL, insère des données et déploie le service Toolbox qui servira d'état initial à notre agent de restaurant.
bash scripts/full_setup.sh > logs/full_setup.log 2>&1 &
4. Concept : Protocole Agent2Agent (A2A) et environnement d'exécution des agents Gemini Enterprise
Avant de commencer à compiler, prenons un instant pour comprendre les deux technologies clés présentées dans cet atelier de programmation afin de mettre à l'échelle notre application agentique.
Protocole Agent2Agent (A2A)
Le protocole Agent2Agent (A2A) est une norme ouverte conçue pour permettre une communication et une collaboration fluides entre les agents d'IA. Alors que le MCP (Model Context Protocol) connecte les agents à des outils et des données, A2A connecte les agents à d'autres agents, ce qui leur permet de découvrir les capacités de chacun, de déléguer des tâches et de collaborer entre les frameworks et les organisations.

La principale différence entre encapsuler un agent en tant qu'outil (via MCP) et l'exposer via A2A est que les outils sont sans état et effectuent des fonctions uniques, tandis que les agents A2A peuvent raisonner, maintenir un état et gérer des interactions en plusieurs tours comme la négociation ou la clarification. Un agent exposé via A2A conserve toutes ses capacités au lieu d'être réduit à un appel de fonction.
A2A définit trois concepts fondamentaux :
- Carte d'agent : document JSON décrivant ce que fait un agent, ses compétences et son point de terminaison. D'autres agents récupèrent cette fiche pour découvrir les capacités.
- Message : requête d'un utilisateur ou d'un agent envoyée à un point de terminaison A2A, déclenchant une tâche.
- Tâche : unité de travail avec un cycle de vie (soumise → en cours → terminée/échec) et artefacts contenant les résultats.

Pour en savoir plus, consultez Qu'est-ce que l'A2A ?
Runtime Gemini Enterprise Agent Platform
Agent Runtime est un service entièrement géré sur Google Cloud qui permet de déployer, de faire évoluer et de gérer des agents IA en production avec des fonctionnalités de sécurité Enterprise (par exemple, VPC Service Controls et CMEK). Il gère l'infrastructure pour vous permettre de vous concentrer sur la logique des agents.

Agent Runtime fournit les éléments suivants :
- Déploiement géré : déployez des agents créés avec ADK, LangGraph ou tout framework Python avec un seul appel de SDK.
- Hébergement A2A : déployez des agents en tant que points de terminaison conformes à A2A avec diffusion automatique de la carte d'agent et accès authentifié.
- Sessions persistantes :
VertexAiSessionServicestocke l'historique et l'état des conversations pour toutes les requêtes. - Autoscaling : effectue un scaling à partir de zéro pour gérer le trafic, sans gestion d'infrastructure
- Observabilité : traçage, journalisation et surveillance intégrés via la pile d'observabilité de Google Cloud
- Pour en savoir plus sur ces fonctionnalités et bien d'autres, consultez cette documentation.
Dans cet atelier de programmation, vous allez déployer l'agent de réservation dans Agent Runtime. Le processus de déploiement sérialise (pickle) le code de votre agent et l'importe. Agent Runtime provisionne un point de terminaison sans serveur qui sert le protocole A2A. D'autres agents (ou clients) interagissent avec lui via des appels HTTP standards, authentifiés avec des identifiants Google Cloud.
5. Créer l'agent de réservation
Cette étape permet de créer un agent ADK qui gère les réservations de restaurants à l'aide de l'état de la session. L'agent prend en charge trois opérations (créer, vérifier et annuler) avec le numéro de téléphone comme clé de recherche. Toutes les données de réservation se trouvent dans l'état de session de l'ADK.
Créer la structure de l'agent
Utilisez adk create pour générer la structure de répertoire de l'agent avec la configuration de modèle et de projet appropriée :
source .env
uv run adk create reservation_agent \
--model gemini-2.5-flash \
--project ${GOOGLE_CLOUD_PROJECT} \
--region ${GOOGLE_CLOUD_LOCATION}
Cela crée un répertoire reservation_agent/ avec __init__.py, agent.py et .env préconfigurés pour le modèle Gemini sur Agent Platform.
adk-a2a-agent-runtime-starter/ ├── reservation_agent/ │ ├── __init__.py │ ├── agent.py │ └── .env ├── logs ├── scripts └── ...
Ensuite, mettons à jour le code de l'agent.
Écrire le code de l'agent
Ouvrez le fichier d'agent généré :
cloudshell edit reservation_agent/agent.py
Remplacez ensuite le contenu par ce qui suit :
# 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. Préparer la configuration du serveur A2A
Définir la carte d'agent A2A
La carte d'agent est une description structurée des capacités de votre agent. D'autres agents et clients l'utilisent pour découvrir ce que fait votre agent. Créez la configuration de la carte :
cloudshell edit reservation_agent/a2a_config.py
Copiez ce qui suit dans 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],
)
Créer l'exécuteur A2A
L'exécuteur fait le lien entre le protocole A2A et l'agent ADK. Il reçoit les requêtes A2A, les exécute via l'agent ADK et renvoie les résultats sous forme de tâches A2A :
cloudshell edit reservation_agent/executor.py
Copiez ce qui suit dans 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())
L'exécuteur détecte automatiquement son environnement : lorsque GOOGLE_CLOUD_AGENT_ENGINE_ID est défini (Agent Runtime l'injecte au moment du déploiement), il utilise VertexAiSessionService pour les sessions persistantes. Localement, il revient à InMemorySessionService.
Votre répertoire reservation_agent devrait maintenant contenir les éléments suivants :
reservation_agent/ ├── __init__.py ├── agent.py ├── a2a_config.py ├── executor.py └── .env
7. Préparer un agent A2A à l'aide du SDK Agent Platform et le tester en local
Cette étape consiste à encapsuler l'agent de réservation en tant qu'agent conforme à A2A à l'aide de la classe A2aAgent du SDK Agent Platform ( le nom du SDK utilise toujours le terme vertex pour la rétrocompatibilité), puis à tester l'intégralité du flux de protocole A2A en local : récupération de la carte d'agent, envoi de messages et récupération de tâches. Il s'agit du même objet A2aAgent que vous déployez sur Agent Runtime à l'étape suivante.
Ajouter des dépendances
Installez le SDK Agent Platform avec la prise en charge d'Agent Runtime et d'ADK, ainsi que le SDK A2A :
uv add "google-cloud-aiplatform[agent_engines,adk]==1.149.0" "a2a-sdk==0.3.26"
Comprendre les composants A2A
Pour encapsuler un agent ADK pour A2A, vous avez besoin de trois composants :
- Carte d'agent : "carte de visite" qui décrit les capacités, les compétences et l'URL du point de terminaison de l'agent. Les autres agents l'utilisent pour découvrir ce que fait votre agent.
- AgentExecutor : le pont entre le protocole A2A et la logique de votre agent ADK. Il reçoit les requêtes A2A, les exécute via l'agent ADK et renvoie les résultats sous forme de tâches A2A.
- A2aAgent : classe du SDK Agent Platform qui combine la carte et l'exécuteur dans une unité déployable.
Créer le script de test
Créez le script suivant pour effectuer des tests en local.
cloudshell edit scripts/test_a2a_agent_local.py
Copiez ce qui suit dans 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())
Le script du test importe la fiche d'agent et l'exécuteur que vous avez créés à l'étape précédente. Il n'y a donc pas de duplication. Il créera un A2aAgent local, simulera les appels de protocole A2A via des requêtes HTTP fictives et vérifiera les trois opérations de réservation.
Comme aucun GOOGLE_CLOUD_AGENT_ENGINE_ID n'est défini localement, l'exécuteur utilise InMemorySessionService. Lorsqu'il est déployé sur Agent Runtime, le même exécuteur passe automatiquement à VertexAiSessionService pour les sessions persistantes.
Exécuter le test
PYTHONPATH=. uv run python scripts/test_a2a_agent_local.py
Le résultat décrit cinq étapes :
- Carte d'agent : récupère les capacités et les compétences de l'agent
- Créer une réservation : réserve une table et renvoie une tâche avec la confirmation.
- Obtenir le résultat de la tâche : récupère la tâche terminée avec la réponse
- Vérifier la réservation : recherche la réservation par numéro de téléphone.
- Annuler la réservation : annule la réservation et confirme l'annulation.
Exemple de résultat, comme indiqué ci-dessous
================================================== 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! ==================================================
À ce stade, vous avez vérifié que la fiche de l'agent A2A décrit les compétences appropriées, que les trois opérations de réservation fonctionnent via le flux de messages/tâches du protocole A2A et que l'état persiste dans les messages d'un même contexte.
8. Déployer l'agent de réservation sur Agent Runtime
Cette étape déploie l'agent de réservation dans Gemini Enterprise Agent Platform Runtime, une plate-forme sans serveur entièrement gérée qui héberge votre agent et l'expose en tant que point de terminaison A2A sécurisé. Après le déploiement, tout client autorisé peut découvrir l'agent et interagir avec lui via des points de terminaison HTTP A2A standards.
Créer le bucket intermédiaire
Créez un bucket Cloud Storage pour la préproduction d'Agent Runtime. L'environnement d'exécution de l'agent utilise ce bucket pour importer le code et les dépendances de votre agent lors du déploiement :
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
Créer le script de déploiement
Ensuite, nous devons préparer le script de déploiement.
cloudshell edit scripts/deploy_a2a_agent_runtime.py
Copiez ce qui suit dans 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()
Le script de déploiement importe les mêmes agent_card et ReservationAgentExecutor que ceux utilisés pour les tests locaux, ce qui évite toute duplication de code. Agent Runtime sérialise (pickle) l'objet A2aAgent avec ses dépendances pour le déploiement. À la fin du script de déploiement, il écrit la valeur RESERVATION_AGENT_RESOURCE_NAME dans le fichier .env.
Déployer sur Agent Runtime
Exécutez le script de déploiement :
PYTHONPATH=. uv run python scripts/deploy_a2a_agent_runtime.py
Le déploiement prend entre trois et cinq minutes. Le script provisionne un point de terminaison sans serveur sur Agent Runtime qui héberge l'agent de réservation. Une fois le déploiement réussi, un résultat semblable à celui-ci s'affiche :
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
Vous pouvez afficher l'agent déployé dans la console Cloud. Recherchez Agent Platform dans la barre de recherche de la console.

Ensuite, dans l'onglet de gauche, pointez sur Agents et sélectionnez Deployments.

Le Reservation Agent s'affiche dans la liste des déploiements, comme illustré ci-dessous.

Tester l'agent déployé
Nous sommes maintenant prêts à tester l'agent déployé. Pour ce faire, créez un script de test pour l'agent déployé :
cloudshell edit scripts/test_a2a_agent_runtime.py
Copiez ce qui suit dans 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())
Ensuite, exécutons le test.
source .env
uv run python scripts/test_a2a_agent_runtime.py
La sortie affiche la fiche de l'agent avec la compétence "Réservations de restaurants", suivie de la tâche terminée avec une confirmation de réservation.
================================================== 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! ==================================================
L'agent de réservation s'exécute désormais en tant que point de terminaison A2A géré sur Agent Runtime.
9. Intégrer l'agent de réservation A2A à l'agent Root Restaurant
Cette étape permet de mettre à niveau l'agent de restaurant pour qu'il utilise l'agent de réservation déployé en tant que sous-agent A2A distant. L'orchestrateur s'exécute en local, tandis que l'agent de réservation s'exécute sur Agent Runtime. Il s'agit d'une intégration partielle qui valide la connexion A2A avant le déploiement complet.
Résoudre l'URL de la carte d'agent A2A
RemoteA2aAgent a besoin de l'URL de la fiche de l'agent de réservation déployé pour découvrir ses capacités. Créez un script qui récupère cette URL à partir d'Agent Runtime et l'écrit dans le .env de l'agent de restaurant :
cloudshell edit scripts/resolve_agent_card_url.py
Copiez ce qui suit dans 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())
Exécutez le script pour remplir le fichier .env avec l'URL de la fiche de l'agent.
uv run python scripts/resolve_agent_card_url.py
source .env
Mettre à jour l'agent de restaurant
Ouvrez le fichier de l'agent de restaurant :
cloudshell edit restaurant_agent/agent.py
Ensuite, remplacez le contenu par la version mise à jour qui inclut l'agent de réservation à distance en tant que sous-agent :
# 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],
)
Voici les principales modifications par rapport à la version précédente :
GoogleCloudAuth: gestionnairehttpx.Authpersonnalisé qui actualise le jeton d'accès Google Cloud avant chaque requête. Agent Runtime nécessite des appels A2A authentifiés, et les jetons expirent au bout d'un certain temps.RemoteA2aAgentlitRESERVATION_AGENT_CARD_URLà partir de.env(écrit par le script de résolution) et utilisehttpx_clientauthentifié.- Enregistré en tant que sous-agent : l'orchestrateur d'ADK lui délègue automatiquement les demandes de réservation.
- Mise à jour des instructions pour mentionner la délégation de réservation
Tester l'agent intégré en local
L'agent de démarrage nécessitait une intégration à MCP Toolbox. Le fichier requis aurait déjà dû être fourni lors d'un atelier de programmation précédent ou à partir du dépôt de démarrage. Nous devons uniquement nous assurer que le processus de la boîte à outils s'exécute correctement.
Si TOOLBOX_URL dans votre .env pointe déjà vers un service Cloud Run (à partir de l'atelier de programmation précédent ou peut-être à partir du full_setup.sh du dépôt de démarrage), vous pouvez ignorer cette étape. L'agent se connectera à la boîte à outils déployée.
Si vous avez besoin d'une boîte à outils locale, vérifiez si une instance est déjà en cours d'exécution avant d'en démarrer une nouvelle :
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
Nous pouvons ensuite essayer d'interagir avec l'agent du restaurant via l'UI de développement Web ADK.
uv run adk web --allow_origins "regex:https://.*\.cloudshell\.dev" --port 8080
Ouvrez l'interface utilisateur Web de l'ADK à l'aide de l'aperçu sur le Web de Cloud Shell (cliquez sur le bouton "Aperçu sur le Web", puis remplacez le port par 8080) et sélectionnez restaurant_agent.

Tester une conversation mixte :
Requête de menu
What Italian dishes do you have?
Demande de réservation
I want to create reservation under name Bob, phone number 123456
Vérifier une réservation
Créer une session ( démarrer une nouvelle conversation) :
Check the reservation for 123456



Arrêtez le processus adk web en appuyant deux fois sur Ctrl+C. Ensuite, finalisons le système en déployant complètement l'agent.
10. Déployer l'agent de restaurant mis à jour sur Cloud Run
Cette étape redéploie l'agent de restaurant sur Cloud Run avec l'intégration A2A, ce qui permet de déployer complètement le système multi-agents.
Accorder des autorisations pour accéder à Agent Runtime
Le compte de service Cloud Run doit être autorisé à appeler Agent Runtime. Attribuez le rôle roles/aiplatform.user au compte de service Compute Engine par défaut :
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"
Déployer sur Cloud Run
Dans cette configuration, nous partons du principe que le service d'agent de restaurant existe déjà depuis l'atelier de programmation précédent ou en exécutant scripts/full_setup.sh si vous commencez à zéro. Le code mis à jour (nouvelle intégration RemoteA2aAgent) est redéployé et l'URL de la fiche de l'agent de réservation est ajoutée en tant que nouvelle variable d'environnement. Les variables d'environnement existantes (TOOLBOX_URL, GOOGLE_CLOUD_PROJECT, etc.) sont conservées :
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
Tester le système entièrement déployé
Obtenez l'URL du service déployé :
AGENT_URL=$(gcloud run services describe restaurant-agent --region=$REGION --format='value(status.url)')
echo "Agent URL: $AGENT_URL"
Ouvrez l'URL dans votre navigateur. L'UI Web de l'ADK se charge. Il s'agit de la même interface que celle que vous avez utilisée en local, qui s'exécute désormais sur Cloud Run.
N'hésitez pas à discuter avec l'agent.
Requête de menu
What spicy dishes do you have?
Demande de réservation
Book a table for 4 on Friday at 7pm. Name: Eve, Phone: 555-0505
Vérifier une réservation
Créer une session ( démarrer une nouvelle conversation) :
Check reservation for 555-0505


Le système multi-agent est entièrement déployé. L'agent de restaurant sur Cloud Run orchestre deux services de backend : MCP Toolbox pour les opérations de menu et l'agent de réservation A2A sur Agent Runtime.
11. Félicitations !
Vous avez créé et déployé un système multi-agents à l'aide du protocole A2A sur Google Cloud.
Connaissances acquises
- Créer un agent ADK qui utilise l'état de session (
ToolContext) pour gérer les données de réservation sans base de données - Vous avez déployé un agent A2A sur Agent Runtime à l'aide du SDK Agent Platform.
- Utilisation d'un agent A2A distant à partir d'un autre agent ADK à l'aide de
RemoteA2aAgentcomme sous-agent - Test du système de manière incrémentielle : A2A local → A2A déployé → intégration partielle → déploiement complet
Nettoyage
Pour éviter que les ressources créées dans cet atelier de programmation ne soient facturées sur votre compte Google Cloud, supprimez-les.
Option 1 : Supprimer le projet (recommandé)
gcloud projects delete $GOOGLE_CLOUD_PROJECT
Option 2 : Supprimer des ressources individuelles
# 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