La pile d'agents de Google en action: ADK, A2A et MCP sur Google Cloud

1. Objectifs de l'atelier

Bienvenue ! Nous allons vivre une expérience plutôt sympa aujourd'hui. Commençons par réfléchir à InstaVibe, une plate-forme populaire d'événements sociaux. Bien que cette fonctionnalité soit efficace, nous savons que pour certains utilisateurs, la planification d'activités de groupe peut sembler être une corvée. Imaginez que vous essayez de comprendre ce qui intéresse tous vos amis, puis que vous parcourez d'innombrables options d'événements ou de lieux, et que vous coordonnez enfin le tout. C'est beaucoup ! C'est précisément là que nous pouvons introduire l'IA, et plus particulièrement les agents intelligents, pour faire une réelle différence.

L'idée est de créer un système dans lequel ces agents peuvent effectuer le gros du travail, comme "écouter" intelligemment pour comprendre les préférences des utilisateurs et de leurs amis, puis suggérer de manière proactive des activités fantastiques et personnalisées. Notre objectif est de transformer la planification sociale sur InstaVibe en une expérience fluide et agréable. Pour commencer à créer ces assistants intelligents, nous devons établir des bases solides avec les bons outils.

Voici le concept que vous verrez :

Page de titre

Principes de base avec l'ADK de Google : maîtrisez les principes de base pour créer votre premier agent intelligent à l'aide de l'Agent Development Kit (ADK) de Google. Comprenez les composants essentiels, le cycle de vie de l'agent et comment exploiter efficacement les outils intégrés du framework.

Étendre les capacités des agents avec le protocole MCP (Model Context Protocol) : découvrez comment équiper vos agents avec des outils et un contexte personnalisés, leur permettant d'effectuer des tâches spécialisées et d'accéder à des informations spécifiques. Présentez le concept du protocole MCP (Model Context Protocol). Vous apprendrez à configurer un serveur MCP pour fournir ce contexte.

Conception des interactions et de l'orchestration des agents : allez au-delà des agents individuels pour comprendre l'orchestration des agents. Concevez des modèles d'interaction allant de workflows séquentiels simples à des scénarios complexes impliquant des boucles, une logique conditionnelle et un traitement parallèle. Présentez le concept de sous-agents dans le framework ADK pour gérer les tâches modulaires.

Créer des systèmes multi-agents collaboratifs : découvrez comment concevoir des systèmes dans lesquels plusieurs agents collaborent pour atteindre des objectifs complexes. Apprenez à implémenter le protocole de communication Agent-to-Agent (A2A), qui établit une méthode standardisée permettant aux agents distribués (pouvant s'exécuter sur différentes machines ou différents services) d'interagir de manière fiable.

Productionnaliser des agents sur Google Cloud : faites passer vos applications d'agent des environnements de développement au cloud. Découvrez les bonnes pratiques pour concevoir et déployer des systèmes multi-agents évolutifs et robustes sur Google Cloud Platform (GCP). Découvrez comment exploiter les services GCP tels que Cloud Run et explorez les fonctionnalités du dernier moteur Google Agent pour héberger et gérer vos agents.

2. Architecture

Planification des réseaux sociaux optimisée par l'IA avec InstaVibe

Qu'est-ce que la veille sur les réseaux sociaux ?

L'écoute sociale consiste à surveiller les conversations numériques sur des plates-formes telles que les réseaux sociaux, les forums et les sites d'actualités pour comprendre ce que les gens disent d'un sujet, d'une marque ou d'un secteur. Elle fournit des insights précieux sur l'opinion publique, les tendances et les besoins des utilisateurs. Dans cet atelier, nous allons exploiter ce concept dans un système basé sur les agents.

Vous êtes dans l'équipe InstaVibe

Imaginez que vous travaillez chez InstaVibe, une start-up à succès qui propose une plate-forme d'événements sociaux populaire auprès des jeunes adultes. Tout se passe bien, mais comme de nombreuses entreprises technologiques, votre équipe est sous la pression des investisseurs pour innover grâce à l'IA. En interne, vous avez également remarqué un segment d'utilisateurs qui ne s'impliquent pas autant que les autres. Peut-être sont-ils moins enclins à lancer des activités de groupe ou trouvent-ils le processus de planification difficile. Pour votre entreprise, cela signifie une fidélisation plus faible à la plate-forme parmi ce groupe d'utilisateurs important.

Les recherches de votre équipe suggèrent que l'assistance basée sur l'IA pourrait améliorer considérablement l'expérience de ces utilisateurs. L'idée est de simplifier la planification des sorties en suggérant de manière proactive des activités pertinentes en fonction des centres d'intérêt de l'utilisateur et de ses amis. La question que vous et vos collègues devez vous poser est la suivante : comment les agents IA peuvent-ils automatiser les tâches souvent chronophages de découverte des centres d'intérêt, de recherche d'activités et, potentiellement, de coordination initiale ?

Solution basée sur des agents (prototype)

Vous proposez de développer une fonctionnalité de prototype basée sur un système multi-agent. Voici une explication conceptuelle :

Cas d'utilisation

  • Agent de profilage social : cet agent utilise des techniques d'écoute sociale pour analyser les connexions et les interactions des utilisateurs, ainsi que les tendances publiques plus générales liées à leurs préférences. Son objectif est d'identifier les centres d'intérêt communs et les caractéristiques d'activité appropriées (par exemple, les préférences pour les réunions plus calmes, les loisirs spécifiques).
  • Agent de planification d'événements : à l'aide des insights de l'agent de profilage social, cet agent recherche des événements, des lieux ou des idées spécifiques sur les ressources en ligne qui correspondent aux critères identifiés (tels que le lieu et les centres d'intérêt).
  • Agent d'interaction avec la plate-forme (à l'aide de MCP) : cet agent prend le plan finalisé de l'agent de planification des activités. Sa fonction principale est d'interagir directement avec la plate-forme InstaVibe en utilisant un outil MCP (Model Context Protocol) prédéfini. Cet outil permet à l'agent de rédiger une suggestion d'événement et de créer un post décrivant le plan.
  • Agent Orchestrator : cet agent sert de coordinateur central. Il reçoit la demande initiale de l'utilisateur depuis la plate-forme InstaVibe, comprend l'objectif général (par exemple, "organise un événement pour moi et mes amis"), puis délègue des tâches spécifiques aux agents spécialisés appropriés dans une séquence logique. Il gère le flux d'informations entre les agents et s'assure que le résultat final est renvoyé à l'utilisateur.

Éléments et technologies architecturaux clés

Architecture

Google Cloud Platform (GCP) :

  • Vertex AI :
    • Modèles Gemini : permet d'accéder aux grands modèles de langage (LLM) de pointe de Google, comme Gemini, qui alimentent les capacités de raisonnement et de prise de décision de nos agents.
    • Vertex AI Agent Engine : service géré utilisé pour déployer, héberger et faire évoluer notre agent d'orchestration, ce qui simplifie la mise en production et élimine la complexité de l'infrastructure.
  • Cloud Run : plate-forme sans serveur permettant de déployer des applications conteneurisées. Nous l'utilisons pour :
    • Héberge l'application Web InstaVibe principale.
    • Déployez des agents individuels compatibles avec A2A (Planificateur, Profilage social, Interaction avec la plate-forme) en tant que microservices indépendants.
    • Exécutez le serveur d'outils MCP, ce qui rend les API internes d'InstaVibe disponibles pour les agents.
  • Spanner : base de données relationnelle entièrement gérée, distribuée à l'échelle mondiale et à cohérence forte. Dans cet atelier, nous allons exploiter ses capacités en tant que base de données de graphiques à l'aide de ses fonctionnalités de requête et de DDL GRAPH pour :
    • Modélisez et stockez des relations sociales complexes (utilisateurs, amitiés, participation à des événements, posts).
    • Permet aux agents de profilage social d'interroger efficacement ces relations.
  • Artifact Registry : service entièrement géré pour stocker, gérer et sécuriser les images de conteneurs.
  • Cloud Build : service qui exécute vos compilations sur Google Cloud. Nous l'utilisons pour créer automatiquement des images de conteneur Docker à partir du code source de notre agent et de notre application.
  • Cloud Storage : utilisé par des services tels que Cloud Build pour stocker les artefacts de compilation et par Agent Engine pour ses besoins opérationnels.
  • Frameworks et protocoles d'agent principaux :
    • Agent Development Kit (ADK) de Google : framework principal pour :
      • Définition de la logique, du comportement et des ensembles d'instructions de base pour chaque agent intelligent.
      • Gérer les cycles de vie, l'état et la mémoire des agents (état de la session à court terme et connaissances à long terme potentielles).
      • Intégrer des outils (comme la recherche Google ou des outils personnalisés) que les agents peuvent utiliser pour interagir avec le monde.
      • Orchestration des workflows multi-agents, y compris l'exécution séquentielle, en boucle et parallèle des sous-agents.
    • Protocole de communication Agent-to-Agent (A2A) : norme ouverte permettant :
      • Communication et collaboration directes et standardisées entre différents agents d'IA, même s'ils s'exécutent en tant que services distincts ou sur des machines différentes.
      • Permettez aux agents de découvrir les capacités des autres (via les fiches d'agent) et de déléguer des tâches. C'est essentiel pour que notre agent Orchestrator puisse interagir avec les agents spécialisés Planner, Social et Platform.
    • Bibliothèque Python A2A (a2a-python) : bibliothèque concrète utilisée pour que nos agents ADK parlent le protocole A2A. Il fournit les composants côté serveur nécessaires pour :
      • Exposer nos agents en tant que serveurs conformes à A2A.
      • Gérer automatiquement la diffusion de la "fiche de l'agent" pour la découverte.
      • Recevoir et gérer les demandes de tâches entrantes provenant d'autres agents (comme l'orchestrateur).
    • Protocole MCP (Model Context Protocol) : norme ouverte qui permet aux agents de :
      • Se connecter à des outils, des sources de données et des systèmes externes et les utiliser de manière standardisée.
      • Notre agent d'interaction avec la plate-forme utilise un client MCP pour communiquer avec un serveur MCP, qui expose à son tour des outils permettant d'interagir avec les API existantes de la plate-forme InstaVibe.
  • Outils de débogage :
    • A2A Inspector : A2A Inspector est un outil de débogage Web utilisé tout au long de cet atelier pour se connecter à nos agents compatibles A2A, les inspecter et interagir avec eux. Bien qu'il ne fasse pas partie de l'architecture de production finale, il constitue un élément essentiel de notre workflow de développement. Il propose les éléments suivants :
      • Lecteur de fiches d'agent : permet de récupérer et de valider les capacités publiques d'un agent.
      • Interface de chat en direct : permet d'envoyer des messages directement à un agent déployé pour des tests immédiats.
      • Console de débogage : permet d'afficher les messages JSON-RPC bruts échangés entre l'inspecteur et l'agent.
  • Modèles de langage (LLM) : le "cerveau" du système :
    • Modèles Gemini de Google : plus précisément, nous utilisons des versions telles que gemini-2.0-flash. Ces modèles sont choisis pour les raisons suivantes :
      • Raisonnement avancé et respect des instructions : leur capacité à comprendre des requêtes complexes, à suivre des instructions détaillées et à raisonner sur des tâches les rend aptes à alimenter la prise de décision des agents.
      • Utilisation d'outils (appel de fonction) : les modèles Gemini excellent dans la détermination du moment et de la manière d'utiliser les outils fournis via l'ADK, ce qui permet aux agents de collecter des informations ou d'effectuer des actions.
      • Efficacité (modèles Flash) : les variantes "Flash" offrent un bon équilibre entre performances et rentabilité. Elles conviennent à de nombreuses tâches d'agent interactif qui nécessitent des réponses rapides.

Vous avez besoin de crédits Google Cloud ?

3. Avant de commencer

👉 Cliquez sur Activer Cloud Shell en haut de la console Google Cloud (icône en forme de terminal en haut du volet Cloud Shell), Cloud Shell

👉 Cliquez sur le bouton "Ouvrir l'éditeur" (icône en forme de dossier ouvert avec un crayon). L'éditeur de code Cloud Shell s'ouvre dans la fenêtre. Un explorateur de fichiers s'affiche sur la gauche. Cloud Shell

👉 Cliquez sur le bouton Cloud Code – Se connecter dans la barre d'état inférieure (voir ci-dessous). Autorisez le plug-in comme indiqué. Si Cloud Code – Aucun projet est affiché dans la barre d'état, cliquez dessus. Dans le menu déroulant "Sélectionner un projet Google Cloud", sélectionnez le projet Google Cloud que vous avez créé. Cloud Shell

👉 Trouvez votre ID de projet Google Cloud :

  • Ouvrez la console Google Cloud : https://console.cloud.google.com
  • Sélectionnez le projet que vous souhaitez utiliser pour cet atelier dans le menu déroulant en haut de la page.
  • L'ID de votre projet est affiché dans la fiche "Informations sur le projet" du tableau de bord.

Cloud Shell

👉 Ouvrez le terminal dans l'IDE cloud, Cloud Shell

👉💻 Dans le terminal, vérifiez que vous êtes déjà authentifié et que le projet est défini sur votre ID de projet à l'aide de la commande suivante :

gcloud auth list

👉💻 Clonez le projet instavibe-bootstrap depuis 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

Comprendre la structure du projet

Avant de commencer à créer, prenons le temps de comprendre la mise en page du projet instavibe-bootstrap que vous venez de cloner. Cela vous aidera à savoir où trouver et modifier des fichiers tout au long de l'atelier.

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

Voici les principaux répertoires :

  • agents/ : il s'agit du cœur de notre système d'IA. Chaque sous-répertoire (planner/, social/, etc.) contient le code source d'un agent intelligent spécifique.
    • agent.py : dans le dossier de chaque agent, il s'agit du fichier principal où se trouve la logique de l'agent.
    • a2a_server.py : ce fichier encapsule l'agent ADK avec un serveur Agent-to-Agent (A2A).
    • Dockerfile : définit comment créer l'image de conteneur pour déployer l'agent sur Cloud Run ou Agent Engine.
  • instavibe/ : ce répertoire contient l'intégralité du code source de l'application Web InstaVibe.
  • tools/ : ce répertoire permet de créer des outils externes que nos agents peuvent utiliser.
    • instavibe/ contient le serveur MCP (Model Context Protocol).

Cette structure modulaire sépare l'application Web des différents composants d'IA, ce qui facilite la gestion, le test et le déploiement de l'ensemble du système.

👉💻 Exécutez le script d'initialisation :

Ce script vous invite à saisir l'ID de votre projet Google Cloud.

Saisissez l'ID de projet Google Cloud que vous avez trouvé à la dernière étape lorsque le script init.sh vous y invite :

cd ~/instavibe-bootstrap
./init.sh

👉💻 Définissez l'ID de projet requis :

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

👉💻 Exécutez la commande suivante pour activer les API Google Cloud nécessaires :

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

👉💻 Définissez toutes les variables d'environnement nécessaires :

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"

Configurer les autorisations

👉 💻 Accorde les autorisations. Dans le terminal, exécutez la commande suivante :

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"


👉 Validez le résultat dans votre console IAM.Cloud Shell

👉💻 Exécutez les commandes suivantes dans le terminal pour créer un dépôt Artifact Registry. Toutes les images Docker de nos agents, du serveur MCP et de l'application InstaVibe sont stockées ici avant d'être déployées sur Cloud Run ou 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"

Configurer la plate-forme Maps pour les clés API

Pour utiliser les services Google Maps dans votre application InstaVibe, vous devez créer une clé API et la restreindre de manière appropriée.

👉 Dans un nouvel onglet, accédez à API et services > Identifiants. Sur la page "Identifiants", cliquez sur le bouton + CRÉER DES IDENTIFIANTS en haut de la page. Sélectionnez "Clé API" dans le menu déroulant. texte alternatif

👉  Une boîte de dialogue s'affiche et indique la clé API que vous venez de créer. Vous en aurez besoin plus tard pour la configuration de votre application.

👉 Cliquez sur FERMER dans la boîte de dialogue "Clé API créée".

👉 Votre nouvelle clé API s'affiche (par exemple, "Clé API 1"). Cliquez sur les trois points à droite, puis sélectionnez Modifier la clé API pour ouvrir la page "Restreindre et renommer la clé API". texte alternatif

👉 Dans le champ "Nom" en haut de la page, remplacez le nom par défaut par Clé API Maps Platform (🚨🚨IMPORTANT🚨🚨 Veuillez utiliser ce nom !)

Maps Platform API Key

👉 Dans la section "Restrictions liées aux applications", assurez-vous que l'option Aucune est sélectionnée.

👉 Dans la section "Restrictions relatives aux API", sélectionnez le bouton radio "Restreindre la clé".

👉  Cliquez sur le menu déroulant "Sélectionner des API". Dans le champ de recherche qui s'affiche, saisissez Maps JavaScript API et sélectionnez-le dans la liste. texte alternatif

👉 Cliquez sur OK.

👉 Cliquez sur le bouton "ENREGISTRER" en bas de la page.

Résultat clé

Vous avez créé une clé API nommée "Maps Platform API Key", restreint son utilisation à l'API Maps JavaScript et vérifié que l'API est activée pour votre projet.

4. Configurer une base de données de graphes

Avant de pouvoir créer nos agents intelligents, nous avons besoin d'un moyen de stocker et de comprendre les liens riches au sein de notre réseau social InstaVibe. C'est là qu'une base de données graphiques entre en jeu. Contrairement aux bases de données relationnelles traditionnelles qui stockent les données dans des tables de lignes et de colonnes, une base de données graphiques est spécifiquement conçue pour représenter et interroger les données en termes de nœuds (comme des personnes, des événements ou des posts) et des relations (arêtes) qui les relient (comme des amitiés, des participations à des événements ou des mentions). Cette structure est incroyablement puissante pour les applications de réseaux sociaux, car elle reflète la façon dont les réseaux sociaux réels sont structurés. Il est donc intuitif d'explorer la façon dont différentes entités sont interconnectées.

Nous implémentons cette base de données de graphiques à l'aide de Google Cloud Spanner. Bien que Spanner soit principalement connu comme une base de données relationnelle à cohérence forte distribuée à l'échelle mondiale, il nous permet également de définir et d'interroger des structures de graphes directement au-dessus de nos tables relationnelles.

Nous bénéficions ainsi à la fois de l'évolutivité, de la cohérence transactionnelle et de l'interface SQL familière de Spanner, ainsi que de la puissance expressive des requêtes graphiques pour analyser les dynamiques sociales complexes essentielles à nos fonctionnalités basées sur l'IA.

👉💻 Dans le terminal Cloud Shell IDE. Provisionnez l'infrastructure nécessaire sur Google Cloud. Nous allons commencer par créer une instance Spanner, qui sert de conteneur dédié pour nos bases de données. Une fois l'instance prête, nous allons créer la base de données Spanner dans laquelle seront stockées toutes nos tables et les données graphiques pour 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

👉💻 Accorder l'accès en lecture/écriture à Spanner au compte de service par défaut

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}

👉💻 Maintenant. Nous allons configurer un environnement virtuel Python, installer les packages Python requis, puis configurer le schéma de la base de données de graphiques dans Spanner, le charger avec les données initiales et exécuter le script 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

👉 Dans un nouvel onglet de navigateur, accédez à la console Google Cloud, puis à Spanner. Vous devriez voir la liste de vos instances Spanner. Cliquez sur instavibe-graph-instance. Instance Spanner 👉 Sur la page "Présentation de l'instance", vous verrez la liste des bases de données de cette instance. Cliquez sur graphdbBase de données Spanner.

👉  Dans le volet de navigation de gauche de votre base de données, cliquez sur Spanner Studio spanner studio.

👉  Dans l'éditeur de requête (onglet "Requête sans titre"), collez la requête Graph SQL suivante. Cette requête trouvera tous les nœuds "Person" et leurs relations d'amitié directes avec d'autres nœuds "Person". Cliquez sur RUN (EXÉCUTER) pour afficher le résultat.

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

spanner graph

👉 Dans le même éditeur de requête, remplacez le DDL précédent pour trouver les personnes qui ont participé au même événement, ce qui implique une connexion indirecte par le biais d'une activité partagée.

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

👉 Cette requête explore un autre type de connexion, où les personnes mentionnées dans les posts écrits par les amis d'une personne spécifique exécutent la requête suivante dans l'éditeur de requête.

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

Ces requêtes ne donnent qu'un aperçu de la puissance de Spanner en tant que base de données de graphes pour notre application InstaVibe. En modélisant nos données sociales sous forme de graphique interconnecté, nous pouvons effectuer une analyse sophistiquée des relations et des activités, ce qui sera fondamental pour que nos agents d'IA comprennent le contexte des utilisateurs, découvrent leurs centres d'intérêt et, à terme, leur fournissent une aide intelligente pour la planification sociale.

Maintenant que notre structure de données de base est en place et testée, concentrons-nous sur l'application InstaVibe existante avec laquelle nos agents interagiront.

5. État actuel d'InstaVibe

Pour comprendre où nos agents d'IA s'intégreront, nous devons d'abord déployer et exécuter l'application Web InstaVibe existante. Cette application fournit l'interface utilisateur et les fonctionnalités de base qui se connectent à la base de données graphique Spanner que nous avons déjà configurée.

page d&#39;accueil

L'application InstaVibe utilise Google Maps pour afficher visuellement les lieux des événements sur ses pages d'informations. Pour activer cette fonctionnalité, l'application a besoin de la clé API que nous avons créée précédemment. Le script suivant récupère la chaîne de clé réelle à l'aide du nom à afficher que nous avons attribué ("Maps Platform API Key").

page de l&#39;événement

👉💻 Retournez dans l'IDE Cloud Shell. Exécutez le script ci-dessous. Ensuite, vérifiez attentivement le résultat pour vous assurer que la clé GOOGLE_MAPS_API_KEY affichée correspond à celle que vous avez créée et copiée précédemment depuis la console Google Cloud.

. ~/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}"

résultat clé

👉💻 À présent, créons l'image de conteneur pour l'application Web InstaVibe et transférons-la vers notre dépôt 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}

👉💻 Déployez la nouvelle image de l'application Web InstaVibe sur 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

Une fois le déploiement terminé, les journaux Cloud Run doivent afficher l'URL publique de votre application InstaVibe en cours d'exécution.

URL

Vous pouvez également trouver cette URL en accédant à la section Cloud Run de la console Google Cloud et en sélectionnant le service instavibe. ListeURL

Ouvrez cette URL dans votre navigateur Web pour explorer la plate-forme InstaVibe de base. Consultez les posts, les événements et les connexions utilisateur générés par la base de données de graphiques que nous avons configurée.

Maintenant que notre application cible est en cours d'exécution, commençons à créer le premier agent intelligent pour améliorer ses capacités.

6. Agent de base,planificateur d'événements avec ADK

Framework ADK

Présentation du framework ADK de Google Maintenant que notre base (l'application et la base de données InstaVibe) est définie, nous pouvons commencer à créer notre premier agent intelligent à l'aide de l'Agent Development Kit (ADK) de Google.

Agent Development Kit (ADK) est un framework flexible et modulaire spécialement conçu pour développer et déployer des agents IA. Son principe de conception est de rendre le développement d'agents plus proche du développement logiciel traditionnel. L'objectif est de permettre aux développeurs de créer, de déployer et d'orchestrer beaucoup plus facilement des architectures agentiques capables de gérer tout type de tâches, des plus simples à usage unique aux workflows multi-agents complexes.

L'ADK repose principalement sur le concept d'Agent, qui englobe les instructions et la configuration (comme le modèle linguistique choisi, par exemple, Gemini) et un ensemble de Tools qu'il peut utiliser pour effectuer des actions ou recueillir des informations.

06-agent.png

Notre agent initial sera un "planificateur d'événements". Son objectif principal est de prendre en compte les demandes des utilisateurs concernant des sorties sociales (en précisant le lieu, les dates et les centres d'intérêt) et de générer des suggestions créatives et personnalisées. Pour nous assurer que les suggestions sont pertinentes et basées sur des informations actuelles (comme des événements spécifiques qui se déroulent ce week-end), nous allons utiliser l'un des outils intégrés à ADK : la recherche Google. Cela permet à l'agent d'ancrer ses réponses dans les résultats Web en temps réel, en récupérant les dernières informations sur les lieux, les événements et les activités correspondant aux critères de l'utilisateur.

👉📝 De retour dans l'IDE Cloud Shell, dans ~/instavibe-bootstrap/agents/planner/agent.py, ajoutez la requête et l'instruction suivantes pour créer l'agent.

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]
)

Et voilà notre premier agent défini ! L'un des grands avantages de l'ADK est son caractère intuitif et les outils pratiques qu'il fournit. L'interface utilisateur de développement de l'ADK est particulièrement utile, car elle vous permet de tester votre agent de manière interactive et de voir ses réponses en temps réel.

👉💻 C'est parti ! Les commandes suivantes lanceront l'interface utilisateur 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

Une fois les commandes exécutées, vous devriez voir dans votre terminal un résultat indiquant que le serveur Web ADK a démarré, comme ceci :

+-----------------------------------------------------------------------------+
| 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)

👉 Ensuite, pour accéder à l'interface utilisateur de développement de l'ADK depuis votre navigateur :

Dans la barre d'outils Cloud Shell (généralement en haut à droite), cliquez sur l'icône Aperçu sur le Web (qui ressemble souvent à un œil ou à un carré avec une flèche), puis sélectionnez Modifier le port. Dans la fenêtre pop-up, définissez le port sur 8000, puis cliquez sur "Change and Preview" (Modifier et prévisualiser). Cloud Shell ouvre alors un nouvel onglet ou une nouvelle fenêtre de navigateur affichant l'UI de développement ADK.

Aperçu sur le Web

Une fois l'interface utilisateur de développement de l'ADK ouverte dans votre navigateur : dans le menu déroulant en haut à droite de l'interface utilisateur, sélectionnez planner comme agent avec lequel vous souhaitez interagir. Maintenant, dans la boîte de dialogue de chat à droite, essayez de donner une tâche à votre agent. Par exemple, engagez une conversation avec l'agent :

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

Suggérer une date (votre préférence)

July 12 2025

Vous devriez voir l'agent traiter votre demande et vous proposer un plan en fonction des résultats de la recherche Google.

Interface utilisateur de développement ADK

Interagir avec un agent est une chose, mais comment savoir s'il se comporte de manière cohérente comme prévu, surtout lorsque nous apportons des modifications ?

Les méthodes de test de logiciels traditionnelles sont souvent insuffisantes pour les agents d'IA en raison de leur nature générative et non déterministe. Pour passer d'une démo sympa à un agent de production fiable, il est essentiel de mettre en place une stratégie d'évaluation solide. Contrairement à la simple vérification du résultat final d'un modèle génératif, l'évaluation d'un agent implique souvent d'évaluer son processus de prise de décision et sa capacité à utiliser correctement des outils ou à suivre des instructions dans différents scénarios. L'ADK fournit des fonctionnalités pour vous aider à y parvenir.

Évaluer

👉 Dans l'interface utilisateur de développement de l'ADK, cliquez sur l'onglet "Eval" (Évaluer) dans le panneau de navigation de gauche. Un fichier de test préchargé nommé plan_eval devrait s'afficher. Ce fichier contient des entrées et des critères prédéfinis pour tester notre agent de planification.

👉 Sélectionnez un scénario, tel que "boston", puis cliquez sur le bouton Run Evaluation (Exécuter l'évaluation). Dans la fenêtre pop-up qui s'affiche, définissez le score de correspondance sur 0,3, puis cliquez sur "Démarrer".

Niveau de correspondance

Cette opération exécute l'agent avec l'entrée de test et vérifie si sa sortie répond aux attentes définies. Cela vous permet de tester systématiquement les performances de votre agent.

adk dev ui evaluation

👉 Voyons maintenant ce qu'il se passe avec un seuil plus strict. Sélectionnez le scénario "nyc", puis cliquez à nouveau sur Run Evaluation (Exécuter l'évaluation). Cette fois, laissez le score de correspondance à sa valeur par défaut (Score de correspondance de la réponse : 0,7), puis cliquez sur "Démarrer". Vous remarquerez que le résultat est "Échec". C'est normal, car le résultat créatif de l'agent ne correspond pas parfaitement à la réponse "idéale" prédéfinie.

adk dev ui evaluation fail

👉 Pour comprendre pourquoi l'opération a échoué, cliquez sur l'icône d'échec sur la ligne "nyc". L'UI affiche désormais une comparaison côte à côte de la réponse réelle de l'agent et de la réponse attendue du cas de test. Cette vue est essentielle pour le débogage. Elle vous permet de voir exactement où la sortie de l'agent a divergé et d'affiner ses instructions en conséquence.

Une fois que vous avez terminé d'explorer l'UI et l'évaluation, revenez au terminal Éditeur Cloud Shell et appuyez sur Ctrl+C pour arrêter l'UI de développement ADK.

Si les résultats textuels de format libre sont un bon point de départ, les données structurées (comme JSON) sont beaucoup plus pratiques pour les applications comme InstaVibe qui doivent utiliser facilement les suggestions d'un agent. Modifions notre agent pour qu'il renvoie son plan dans un format JSON cohérent.

👉📝 Dans ~/instavibe-bootstrap/agents/planner/agent.py, recherchez la ligne qui indique actuellement RETURN PLAN in MARKDOWN FORMAT dans la chaîne d'instructions de l'agent. Remplacez cette ligne par la structure JSON détaillée suivante :

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
          ]
        }

Maintenant que vous avez mis à jour les instructions de l'agent pour demander spécifiquement une sortie JSON, vérifions la modification.

👉💻 Relancez l'interface utilisateur de développement ADK en utilisant la même commande que précédemment :

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

Actualisez l'onglet si vous l'avez déjà ouvert. Vous pouvez également suivre les mêmes étapes que précédemment pour ouvrir l'interface utilisateur de développement ADK dans votre navigateur (via l'aperçu sur le Web de Cloud Shell sur le port 8000). Une fois l'UI chargée, assurez-vous que l'agent de planification est sélectionné.

👉 Cette fois, formulons une autre requête. Dans la boîte de dialogue de chat, saisissez :

Plan an event Boston this weekend with art and coffee

Examinez attentivement la réponse de l'agent. Au lieu d'une réponse textuelle purement conversationnelle, vous devriez maintenant voir une réponse strictement formatée en tant qu'objet JSON, correspondant à la structure que nous avons définie dans les instructions (contenant fun_plans, plan_description, locations_and_activities, etc.). Cela confirme que l'agent peut désormais produire des résultats structurés adaptés à une utilisation programmatique par notre application InstaVibe.

adk dev ui json

Après avoir confirmé la sortie JSON, revenez au terminal Cloud Shell et appuyez sur Ctrl+C pour arrêter l'interface utilisateur de développement ADK.

Composants de l'ADK

Bien que l'UI de développement de l'ADK soit idéale pour les tests interactifs, nous devons souvent exécuter nos agents de manière programmatique, peut-être dans le cadre d'une application ou d'un service backend plus vaste. Pour comprendre comment cela fonctionne, examinons quelques concepts ADK de base liés à la gestion de l'exécution et du contexte.

Pour que les conversations multitours soient pertinentes, les agents doivent comprendre le contexte, c'est-à-dire se souvenir de ce qui a été dit et fait pour assurer la continuité. L'ADK fournit des moyens structurés de gérer ce contexte via Session, State et Memory :

  • Session : une session est créée lorsqu'un utilisateur commence à interagir avec un agent. Considérez-le comme le conteneur d'un fil de discussion spécifique. Il contient un ID unique, l'historique des interactions (événements), les données de travail actuelles (état) et les métadonnées telles que l'heure de la dernière mise à jour.
  • État : il s'agit de la mémoire de travail à court terme de l'agent au cours d'une même session. Il s'agit d'un dictionnaire mutable dans lequel l'agent peut stocker les informations temporaires nécessaires à l'exécution de la tâche en cours (par exemple, les préférences utilisateur collectées jusqu'à présent, les résultats intermédiaires des appels d'outils).
  • Mémoire : elle représente la capacité de l'agent à se souvenir d'informations à long terme au cours de différentes sessions ou à accéder à des bases de connaissances externes. Alors que Session et State gèrent la conversation immédiate, Memory (souvent géré par un MemoryService) permet à un agent de récupérer des informations à partir d'interactions passées ou de sources de données structurées, ce qui lui donne un contexte de connaissances plus large. (Remarque : Notre client simple utilise des services en mémoire pour plus de simplicité, ce qui signifie que la mémoire/l'état ne persistent que pendant l'exécution du script.)
  • Événement : chaque interaction au cours d'une session (message utilisateur, réponse de l'agent, demande d'utilisation d'un outil, résultat d'un outil, changement d'état, erreur) est enregistrée en tant qu'événement immuable. Cela crée un journal chronologique, qui correspond essentiellement à la transcription et à l'historique des actions de la conversation.

Alors, comment sont-ils gérés lorsqu'un agent s'exécute ? C'est le rôle de l'exécuteur.

  • Exécuteur : l'exécuteur est le moteur d'exécution principal fourni par ADK. Vous définissez votre agent et les outils qu'il utilise, et le Runner orchestre le processus de traitement de la requête d'un utilisateur. Il gère la session, traite le flux d'événements, met à jour l'état, appelle le modèle de langage sous-jacent, coordonne les appels d'outils et interagit potentiellement avec MemoryService. Imaginez que le contrôleur est le chef d'orchestre qui s'assure que toutes les parties fonctionnent correctement ensemble.

Nous pouvons utiliser le Runner pour exécuter notre agent en tant qu'application Python autonome, complètement indépendante de l'interface utilisateur pour les développeurs.

Créons un script client simple pour appeler notre agent de planification de manière programmatique.

👉📝 Dans le fichier ~/instavibe-bootstrap/agents/planner/planner_client.py, ajoutez le code Python suivant sous les importations existantes. Dans planner_client.py, sous les importations, ajoutez les éléments suivants :

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}")

Ce code configure des services en mémoire pour la gestion des sessions et des artefacts (pour simplifier cet exemple), crée une session, définit une requête utilisateur, configure le Runner avec notre agent, puis exécute l'agent de manière asynchrone, en imprimant chaque événement généré lors de l'exécution.

👉💻 Exécutez maintenant ce script client depuis votre terminal :

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

👀 Observez le résultat. Au lieu du plan JSON final, vous verrez la structure détaillée de chaque objet Event généré lors du flux d'exécution de l'agent. Cela inclut l'événement de message utilisateur initial, les événements potentiels liés aux appels d'outils (comme la recherche Google) et, enfin, l'événement de réponse du modèle contenant le plan JSON. Ce flux d'événements détaillé est très utile pour le débogage et pour comprendre le traitement étape par étape qui se produit dans le runtime ADK.

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

Si le script s'exécute en continu ou se bloque, vous devrez peut-être l'arrêter manuellement en appuyant sur Ctrl+C.

7. Agent d'interaction avec la plate-forme : interagit avec le serveur MCP

Bien que l'ADK aide à structurer nos agents, ils doivent souvent interagir avec des systèmes ou des API externes pour effectuer des actions concrètes.

Protocole MCP (Model Context Protocol)

Le protocole MCP (Model Context Protocol) est une norme ouverte conçue pour standardiser la façon dont les applications d'IA, comme les agents, se connectent aux sources de données, outils et systèmes externes. Il vise à résoudre le problème de la nécessité d'intégrations personnalisées pour chaque combinaison d'application d'IA et de source de données en fournissant une interface universelle. MCP utilise une architecture client-serveur dans laquelle les clients MCP, résidant dans les applications d'IA (hôtes), gèrent les connexions aux serveurs MCP. Ces serveurs sont des programmes externes qui exposent des fonctionnalités spécifiques, comme l'accès aux données locales, l'interaction avec des services à distance via des API ou la fourniture d'invites prédéfinies. Ils permettent aux modèles d'IA d'accéder aux informations actuelles et d'effectuer des tâches au-delà de leur entraînement initial. Cette structure permet aux modèles d'IA de découvrir des fonctionnalités externes et d'interagir avec elles de manière standardisée, ce qui simplifie et rend les intégrations plus évolutives.

Créer et déployer le serveur MCP InstaVibe

07-mcp-server.png

Nos agents devront à terme interagir avec la plate-forme InstaVibe elle-même, en particulier pour créer des posts et enregistrer des événements à l'aide des API existantes de la plate-forme. L'application InstaVibe expose déjà ces fonctionnalités via des points de terminaison HTTP standards :

Point de terminaison

URL

Méthode HTTP

Description

Créer un post

api/posts

PUBLIER

Point de terminaison de l'API permettant d'ajouter un post. Corps JSON attendu :
{"author_name": "...", "text": "...", "sentiment": "..." (optional)}

Créer un événement

api/events

PUBLIER

Point de terminaison de l'API permettant d'ajouter un événement et ses participants (schéma simplifié).
 Corps JSON attendu : { "event_name": "...", "description": "...", "event_date": "YYYY-MM-DDTHH:MM:SSZ", "locations": [ {"name": "...", "description": "...", "latitude": 0.0, "longitude": 0.0, "address": "..."} ], "attendee_names": ["...", "..."] }

Pour mettre ces fonctionnalités à la disposition de nos agents via MCP, nous devons d'abord créer des fonctions Python simples qui servent de wrappers autour de ces appels d'API. Ces fonctions gèrent la logique des requêtes HTTP.

👉 Commençons par implémenter la fonction wrapper pour créer un post. Ouvrez le fichier ~/instavibe-bootstrap/tools/instavibe/instavibe.py et remplacez le commentaire #REPLACE ME CREATE POST par le code Python suivant :

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

👉📝 Ensuite, nous allons créer la fonction wrapper pour l'API de création d'événements. Dans le même fichier ~/instavibe-bootstrap/tools/instavibe/instavibe.py, remplacez le commentaire #REPLACE ME CREATE EVENTS par ce code :

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

Comme vous pouvez le voir, ces fonctions sont des wrappers simples autour des API InstaVibe existantes. Ce modèle est utile si vous disposez déjà d'API pour vos services. Vous pouvez facilement exposer leurs fonctionnalités en tant qu'outils pour les agents en créant de tels wrappers.

Implémentation du serveur MCP

Maintenant que nous disposons des fonctions Python qui exécutent les actions (en appelant les API InstaVibe), nous devons créer le composant serveur MCP. Ce serveur exposera ces fonctions en tant qu'"outils" conformément à la norme MCP, ce qui permettra aux clients MCP (comme nos agents) de les découvrir et de les appeler.

Un serveur MCP implémente généralement deux fonctionnalités clés :

  • list_tools : permet au client de découvrir les outils disponibles sur le serveur, en fournissant des métadonnées telles que leurs noms, leurs descriptions et les paramètres requis, souvent définis à l'aide du schéma JSON.
  • call_tool : gère l'exécution d'un outil spécifique demandé par le client, en recevant le nom et les arguments de l'outil et en effectuant l'action correspondante, comme dans notre cas, en interagissant avec une API.

Les serveurs MCP sont utilisés pour fournir aux modèles d'IA un accès à des données et des actions réelles, ce qui permet d'effectuer des tâches telles que l'envoi d'e-mails, la création de tâches dans des systèmes de gestion de projet, la recherche dans des bases de données ou l'interaction avec divers logiciels et services Web. Alors que les implémentations initiales se concentraient souvent sur les serveurs locaux communiquant via des entrées/sorties (stdio) standards pour plus de simplicité, en particulier dans les environnements de développement ou de "studio", le passage à des serveurs distants utilisant des protocoles tels que HTTP avec les événements envoyés par le serveur (SSE) est plus judicieux pour une adoption plus large et les cas d'utilisation en entreprise.

Malgré la couche de communication réseau supplémentaire, l'architecture à distance offre des avantages considérables : elle permet à plusieurs clients d'IA de partager l'accès à un même serveur, centralise la gestion et les mises à jour des outils, améliore la sécurité en conservant les données sensibles et les clés API côté serveur plutôt que de les distribuer sur potentiellement de nombreuses machines clientes, et découple le modèle d'IA des spécificités de l'intégration du système externe. L'ensemble de l'écosystème est ainsi plus évolutif, sécurisé et facile à gérer que si chaque instance d'IA devait gérer ses propres intégrations directes.

07-mcp-server.png

Nous allons implémenter notre serveur MCP à l'aide de HTTP et d'événements envoyés par le serveur (SSE) pour la communication, ce qui convient parfaitement aux exécutions d'outils potentiellement longues et aux scénarios d'entreprise.

👉📝 Commençons par implémenter le point de terminaison list_tools. Ouvrez le fichier ~/instavibe-bootstrap/tools/instavibe/mcp_server.py et remplacez le commentaire #REPLACE ME - LIST TOOLS par le code suivant. :

@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]

Cette fonction définit les outils (create_event, create_post) et en informe les clients connectés.

👉📝 Implémentez ensuite le point de terminaison call_tool, qui gère les requêtes d'exécution réelles des clients. Dans le même fichier ~/instavibe-bootstrap/tools/instavibe/mcp_server.py, remplacez le commentaire #REPLACE ME - CALL TOOLS par ce code.

@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)]

Cette fonction reçoit le nom et les arguments de l'outil, trouve la fonction wrapper Python correspondante que nous avons définie précédemment, l'exécute et renvoie le résultat.

👉💻 Maintenant que la logique du serveur MCP est définie, nous devons l'empaqueter en tant que conteneur. Dans le terminal, exécutez le script suivant pour créer l'image Docker à l'aide de 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}

👉💻 Déployez l'image en tant que service sur 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

👉💻 Une fois le déploiement terminé, le serveur MCP sera en cours d'exécution et accessible via une URL publique. Nous devons capturer cette URL pour que notre agent (agissant en tant que client MCP) sache où se connecter.

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

Vous devriez également voir le service mcp-tool-server listé comme "En cours d'exécution" dans la section Cloud Run de votre console Google Cloud.

Cloud Run

Une fois le serveur MCP déployé et son URL capturée, nous pouvons implémenter l'agent qui agira en tant que client MCP et utilisera les outils exposés par ce serveur.

8. Agent d'interaction avec la plate-forme (à l'aide de MCP)

Client MCP : le client MCP est un composant qui réside dans une application ou un agent d'IA. Il sert d'interface entre le modèle d'IA et un ou plusieurs serveurs MCP. Dans notre implémentation, ce client sera intégré directement à notre agent. La fonction principale de ce client est de communiquer avec les serveurs MCP pour découvrir les outils disponibles via la fonction list_tools, puis de demander l'exécution d'outils spécifiques à l'aide de la fonction call_tool, en transmettant les arguments nécessaires fournis par le modèle d'IA ou l'agent orchestrant l'appel.

Client MCP

Nous allons maintenant créer l'agent qui fait office de client MCP. Cet agent, qui s'exécute dans le framework ADK, sera chargé de communiquer avec le mcp-tool-server que nous venons de déployer.

👉 Tout d'abord, nous devons modifier la définition de l'agent pour récupérer dynamiquement les outils à partir de notre serveur MCP en cours d'exécution. Dans agents/platform_mcp_client/agent.py, remplacez #REPLACE ME - FETCH TOOLS par ce qui suit :

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

Ce code utilise la méthode MCPToolset.from_server pour se connecter à MCP_SERVER_URL (que nous avons défini comme variable d'environnement précédemment) et récupérer la liste des outils disponibles.

Nous devons ensuite indiquer à la définition de l'agent ADK d'utiliser réellement ces outils récupérés de manière dynamique.

👉 Dans agents/platform_mcp_client/agent.py, remplacez #REPLACE ME - SET TOOLs par ce qui suit :

  tools=[tools],

👉💻 Testons maintenant cet agent en local à l'aide de l'interface utilisateur de développement ADK pour voir s'il peut se connecter correctement au serveur MCP et utiliser les outils pour interagir avec notre application InstaVibe en cours d'exécution.

. ~/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

Ouvrez à nouveau l'interface utilisateur de développement ADK dans votre navigateur (en utilisant l'aperçu Web de Cloud Shell sur le port 8000). Cette fois, dans le menu déroulant en haut à droite, sélectionnez l'agent platform_mcp_client.

Testons l'outil create_post. Dans la boîte de dialogue du chat, saisissez la requête suivante :

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

Post sur l&#39;UI de développement de l&#39;ADK

L'agent doit traiter cette demande, identifier la nécessité d'utiliser l'outil create_post, communiquer avec le serveur MCP, qui à son tour appelle l'API InstaVibe.

👉 Étape de validation : Une fois que l'agent a confirmé l'action, ouvrez l'onglet dans lequel votre application InstaVibe s'exécute (ou actualisez-le). Le nouveau post de "Julia" devrait apparaître dans le flux principal.

Post InstaVibe

👉💻 Exécutez ce script dans un terminal distinct pour obtenir le lien Instavibe si nécessaire :

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

👉📝 Testons maintenant l'outil create_event. Saisissez la requête multiligne suivante dans la boîte de dialogue du chat :

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],
}

Là encore, l'agent doit utiliser l'outil approprié via le serveur MCP. Dans l'onglet "Événements", n'hésitez pas à cliquer sur un événement individuel pour afficher une trace détaillée et pas à pas de l'exécution.

Événement d&#39;UI ADK Dev

👉 Étape de validation : Revenez à votre application InstaVibe en cours d'exécution et accédez à la section "Événements" (ou équivalente). L'événement "Journée culinaire et artistique à Mexico" que vous venez de créer devrait maintenant s'afficher.

Événement InstaVibe

Cela montre comment MCP permet à notre agent d'utiliser des outils externes (dans ce cas, les API InstaVibe) de manière standardisée.

Une fois que vous avez vérifié les deux actions, revenez au terminal Cloud Shell et appuyez sur Ctrl+C pour arrêter l'interface utilisateur de développement ADK.

9. Agent de workflow et multi-agents dans l'ADK

Jusqu'à présent, nos agents peuvent planifier des sorties et interagir avec la plate-forme. Toutefois, pour une planification réellement personnalisée, il est nécessaire de comprendre le cercle social de l'utilisateur. Pour les utilisateurs occupés qui ne suivent pas de près les activités de leurs amis, il est difficile de recueillir ce contexte manuellement. Pour y remédier, nous allons créer un agent de profilage social qui s'appuie sur notre base de données Spanner Graph pour analyser les activités et les centres d'intérêt des amis, ce qui permet de fournir des suggestions plus personnalisées.

Agent de profilage sur les réseaux sociaux

Tout d'abord, nous avons besoin d'outils pour que cet agent puisse accéder aux données du graphique.

👉📝 Ajoutez les fonctions Python suivantes à la fin du fichier ~/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

Voyons maintenant comment structurer notre agent. L'analyse des profils de plusieurs amis et la synthèse des résultats impliquent plusieurs étapes. Il s'agit d'un scénario idéal pour utiliser les fonctionnalités multi-agents de l'ADK, en particulier les agents de workflow.

Dans l'ADK de Google, un agent de workflow n'effectue pas de tâches lui-même, mais orchestre d'autres agents appelés sous-agents. Cela permet une conception modulaire, en décomposant les problèmes complexes en composants spécialisés. L'ADK fournit des types de workflow intégrés, tels que :

  • Séquentiel (étape par étape)
  • Parallèle (exécution simultanée)
  • et Loop (exécution répétée)

Agent de profilage sur les réseaux sociaux

Pour notre tâche de profilage social, notre conception utilise un agent de boucle pour créer un workflow itératif. L'objectif est de traiter une personne à la fois : profile_agent collecte les données, summary_agent met à jour l'analyse et check_agent détermine si nous devons effectuer une nouvelle boucle.

Définissons les sous-agents requis pour ce workflow.

👉📝 Dans ~/instavibe-bootstrap/agents/social/agent.py, remplacez #REPLACE FOR profile_agent par ce qui suit :

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],
)

Ensuite, l'agent prend les informations de profil collectées (accumulées au fil des itérations de la boucle) et génère le résumé final, en identifiant les points communs si plusieurs personnes ont été analysées.

👉📝 Dans le même ~/instavibe-bootstrap/agents/social/agent.py, remplacez #REPLACE FOR summary_agent par le code suivant :

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"
)

Nous avons besoin d'un moyen de déterminer quand la boucle doit s'arrêter (c'est-à-dire lorsque tous les profils demandés ont été résumés).

👉📝 Dans le même ~/instavibe-bootstrap/agents/social/agent.py, remplacez #REPLACE FOR check_agent par le code suivant :

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"
)

Nous ajoutons une simple vérification programmatique (CheckCondition) qui examine explicitement les summary_status stockés dans l'état, qui sont renvoyés par check_agent et indique à l'agent de boucle s'il doit continuer (escalate=False) ou s'arrêter (escalate=True).

👉📝 Dans le même ~/instavibe-bootstrap/agents/social/agent.py, remplacez #REPLACE FOR CheckCondition situé en haut du fichier par ce qui suit :

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))

État et rappels pour les résultats de boucle

Dans l'ADK de Google, l'état est un concept essentiel qui représente la mémoire ou les données de travail d'un agent lors de son exécution. Il s'agit essentiellement d'un contexte persistant qui contient les informations dont un agent a besoin pour maintenir la cohérence entre différentes étapes, appels d'outils ou interactions. Cet état peut stocker des résultats intermédiaires, des informations utilisateur, des paramètres pour les actions suivantes ou toute autre donnée dont l'agent a besoin pour se souvenir de la progression d'une tâche.

Dans notre scénario, à mesure que l'agent Loop itère, summary_agent et check_agent stockent leurs sorties (summary et summary_status) dans l'état de l'agent. Cela permet aux informations de persister d'une itération à l'autre. Toutefois, l'agent de boucle lui-même ne renvoie pas automatiquement le récapitulatif final de l'état lorsqu'il se termine.

Agent de profilage sur les réseaux sociaux

Les rappels dans ADK nous permettent d'injecter une logique personnalisée à exécuter à des moments spécifiques du cycle de vie d'un agent ou en réponse à certains événements, comme la fin d'un appel d'outil ou avant que l'agent ne termine son exécution. Ils permettent de personnaliser le comportement de l'agent et de traiter les résultats de manière dynamique.

Nous utiliserons un after_agent_callback qui s'exécute à la fin de la boucle (car CheckCondition a été escaladé). Ce rappel modify_output_after_agent récupère le récapitulatif final de l'état et le met en forme en tant que message de sortie final de l'agent.

Rappeler

👉📝 Dans le même ~/instavibe-bootstrap/agents/social/agent.py, remplacez #REPLACE FOR modify_output_after_agent par ce qui suit :

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

Définir l'agent de boucle racine

Enfin, nous définissons l'agent LoopAgent principal. Il orchestre les sous-agents de manière séquentielle dans chaque itération de boucle (profile_agent → summary_agent → check_agent → CheckCondition). Cette séquence sera répétée jusqu'à max_iterations fois ou jusqu'à ce que CheckCondition signale la fin. Le after_agent_callback garantit que le récapitulatif final est renvoyé.

👉📝 Dans le même ~/instavibe-bootstrap/agents/social/agent.py, remplacez #REPLACE FOR root_agent par ce qui suit :

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
)

Testons ce workflow multi-agents à l'aide de l'interface utilisateur de développement ADK.

👉💻 Lancez le serveur Web 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

Ouvrez l'interface utilisateur de développement ADK (port 8000 via l'aperçu sur le Web). Dans le menu déroulant de l'agent (en haut à droite), sélectionnez l'agent Social.

👉 Maintenant, demandez-lui de dresser le profil de plusieurs personnes. Dans la boîte de dialogue de chat, saisissez :

Tell me about Mike and Bob

Une fois que l'agent a répondu (ce qui peut prendre un peu plus de temps en raison de la boucle et des multiples appels LLM), ne vous contentez pas de regarder le résultat final de la discussion. Accédez à l'onglet "Events" (Événements) dans le volet de gauche de l'interface utilisateur de développement ADK.

👉 Étape de validation : dans l'onglet "Événements", vous verrez une trace détaillée et pas à pas de l'exécution. 09-01-adk-dev-ui.png

Après avoir observé comment l'agent appelle chaque sous-agent, où vous vous attendez à ce que le flux passe de profile_agent à summary_agent, puis à check_agent, Checker à chaque itération. Mais en pratique, nous constatons la puissante "auto-optimisation" de l'agent en action.

Étant donné que le modèle sous-jacent voit l'intégralité de la requête (par exemple, "profile Mike and Bob"), il choisit souvent le chemin le plus efficace, en rassemblant toutes les données requises en une seule étape consolidée plutôt que d'itérer plusieurs fois. Vous pouvez voir les entrées, les sorties et les états de chaque étape, y compris les appels d'outils effectués par profile_agent.

09-02-ui-graph.png

et les mises à jour de l'état de check_agent et CheckCondition. 09-03-ui-state.png

Cette trace visuelle est essentielle pour comprendre et déboguer le fonctionnement du workflow multi-agents jusqu'à ce que le résumé final soit généré et renvoyé par le rappel.

Une fois que vous avez exploré la réponse du chat et la trace d'événement, revenez au terminal Cloud Shell et appuyez sur Ctrl+C pour arrêter l'interface utilisateur de développement ADK.

10. Communication entre agents (A2A)

Jusqu'à présent, nous avons créé des agents spécialisés, mais ils fonctionnent de manière isolée ou dans un workflow prédéfini sur la même machine. Pour créer des systèmes multi-agents véritablement distribués et collaboratifs, nous avons besoin d'un moyen permettant aux agents, qui peuvent s'exécuter en tant que services distincts, de se découvrir et de communiquer efficacement. C'est là qu'intervient le protocole Agent-to-Agent (A2A).

Le protocole A2A est une norme ouverte spécialement conçue pour la communication interopérable entre les agents d'IA. Alors que MCP se concentre sur l'interaction entre l'agent et l'outil, A2A se concentre sur l'interaction entre agents. Il permet aux agents :

  • Découverte : trouvez d'autres agents et découvrez leurs capacités grâce à des fiches d'agent standardisées.
  • Communiquez : échangez des messages et des données de manière sécurisée.
  • Collaborer : déléguer des tâches et coordonner des actions pour atteindre des objectifs complexes.

Le protocole A2A facilite cette communication grâce à des mécanismes tels que les "cartes d'agent", que les agents peuvent utiliser pour annoncer leurs capacités et leurs informations de connexion.

10-05-agent-card

A2A utilise des normes Web connues (HTTP, SSE, JSON-RPC) et emploie souvent un modèle client-serveur dans lequel un agent (client) envoie des tâches à un autre (agent/serveur distant). Cette standardisation est essentielle pour créer des systèmes modulaires et évolutifs dans lesquels des agents développés indépendamment peuvent collaborer.

Activer A2A pour les agents InstaVibe

Pour rendre nos agents Planner, Platform Interaction et Social existants accessibles à d'autres agents via A2A, nous devons envelopper chacun d'eux avec un composant A2A Server. Ce serveur :

  • Exposer une fiche d'agent : diffusez une description standard des capacités de l'agent via un point de terminaison HTTP.
  • Écouter les tâches(messages de requête) : accepter les demandes de tâches entrantes d'autres agents (clients A2A) conformément au protocole A2A.
  • Gérer l'exécution des tâches(messages de requête) : transférer les tâches reçues à la logique de l'agent ADK sous-jacent pour traitement.

Agent de planification (A2A activé)

all-agent-planner

Commençons par ajouter la couche de serveur A2A à notre agent Planner.

Définissez la logique de démarrage du serveur A2A. Ce code définit l'AgentCard (description publique de l'agent), configure l'A2AServer et le démarre en le liant à PlatformAgentExecutor.

👉📝 Ajoutez le code suivant à la fin de ~/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)

👉💻 Testons rapidement si le serveur A2A démarre correctement en local et s'il diffuse sa carte d'agent. Exécutez la commande suivante dans votre premier terminal :

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

👉 Ouvrez une autre fenêtre de terminal. (Cliquez sur le signe + dans le panneau du terminal.) deux bornes

👉💻 Utilisez curl pour demander la fiche de l'agent au serveur exécuté en local :

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

Vous devriez voir la représentation JSON de l'AgentCard que nous avons définie, ce qui confirme que le serveur est en cours d'exécution et qu'il annonce l'agent Planner.

10-02-planner-a2a.png

Revenez au premier terminal (où le serveur est en cours d'exécution) et appuyez sur Ctrl+C pour l'arrêter.

👉💻 Maintenant que la logique du serveur A2A est ajoutée, nous pouvons créer l'image du conteneur.

Créer et déployer l'agent Planner

. ~/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}"

👉💻 Déployez notre agent Planner sur 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

Vérifions que le service déployé s'exécute et diffuse correctement sa fiche d'agent depuis le cloud à l'aide de l'outil d'inspection A2A.

👉 Dans la barre d'outils Cloud Shell, sélectionnez l'icône Aperçu sur le Web, puis Modifier le port. Définissez le port sur 8081, puis cliquez sur "Change and Preview" (Modifier et prévisualiser). Un nouvel onglet de navigateur s'ouvre avec l'interface de l'inspecteur A2A.

10-08-web-preview.png

👉💻 Dans le terminal, obtenez l'URL de votre agent de planification déployé :

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

👉💻 Copiez l'URL de sortie.

👉 Dans l'interface utilisateur de l'inspecteur A2A, collez l'URL dans le champ "URL de l'agent", puis cliquez sur "Connecter".

👀 Les informations de la carte de l'agent et le JSON devraient s'afficher dans l'onglet "Agent Card" (Carte de l'agent), ce qui confirme que la connexion a été établie.

10-03-planner-a2a.png

👉 Cliquez sur l'onglet "Chat" dans l'inspecteur A2A. C'est ici que vous pouvez interagir directement avec votre agent déployé. Envoyez-lui un message pour tester sa capacité de planification. Exemple :

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

👀 Pour inspecter la communication brute, cliquez sur la bulle de votre message, puis sur la bulle de réponse de l'agent dans la fenêtre de chat. Lorsque vous cliquez sur chacun d'eux, le message JSON-RPC 2.0 complet envoyé ou reçu s'affiche, ce qui est très utile pour le débogage.

Gardons l'onglet "A2A Inspector" à portée de main. NE LA FERMEZ PAS ! Nous l'utiliserons à nouveau dans un instant pour tester nos deux autres agents.

10-06-a2a-inspector.png

Agent d'interaction avec la plate-forme (A2A activé)

all-agent-platform

Nous allons ensuite répéter le processus pour l'agent d'interaction avec la plate-forme (celui qui utilise MCP).

👉📝 Définissez la configuration du serveur A2A, y compris son AgentCard unique, à la fin de ~/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)

Agent social (A2A activé)

all-agent-social

Enfin, activons A2A pour notre agent de profilage social.

👉📝 Définissez la configuration du serveur A2A et AgentCard à la fin de ~/instavibe-bootstrap/agents/social/a2a_server.py :

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)

Créer et déployer les agents d'interaction avec la plate-forme et les agents sociaux

Ces agents ont besoin d'accéder à Spanner. Assurez-vous donc que les variables d'environnement SPANNER_INSTANCE_ID, SPANNER_DATABASE_ID et MCP_SERVER_URL sont correctement transmises lors du déploiement.

👉💻 Créez et déployez sur Cloud Run avec 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}"

👉💻 Dans le terminal, obtenez l'URL de votre agent de plate-forme déployé :

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

👉💻 Copiez l'URL de sortie.

👉 Dans l'interface utilisateur de l'inspecteur A2A, collez l'URL dans le champ "URL de l'agent", puis cliquez sur "Connecter".

👀 Les informations de la carte de l'agent et le JSON devraient s'afficher dans l'onglet "Agent Card" (Carte de l'agent), ce qui confirme que la connexion a été établie.

10-05-platform-a2a.png

👉 Cliquez sur l'onglet "Chat" dans l'inspecteur A2A. C'est ici que vous pouvez interagir directement avec votre agent déployé. Envoyez-lui un message pour tester sa capacité à créer des posts :

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.

👀 Pour inspecter la communication brute, cliquez sur la bulle de votre message, puis sur la bulle de réponse de l'agent dans la fenêtre de chat. Lorsque vous cliquez sur chacun d'eux, le message JSON-RPC 2.0 complet envoyé ou reçu s'affiche, ce qui est très utile pour le débogage.

👉💻 Dans le terminal, récupérez l'URL de votre agent Social déployé :

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

👉💻 Copiez l'URL de sortie.

👉 Dans l'interface utilisateur de l'inspecteur A2A, collez l'URL dans le champ "URL de l'agent", puis cliquez sur "Connecter".

👀 Les informations de la carte de l'agent et le JSON devraient s'afficher dans l'onglet "Agent Card" (Carte de l'agent), ce qui confirme que la connexion a été établie.

10-04-social-a2a.png

👉 Cliquez sur l'onglet "Chat" dans l'inspecteur A2A. C'est ici que vous pouvez interagir directement avec votre agent déployé. Envoyez-lui un message pour qu'il analyse les profils utilisateur de votre base de données :

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

👀 Pour inspecter la communication brute, cliquez sur la bulle de votre message, puis sur la bulle de réponse de l'agent dans la fenêtre de chat. Lorsque vous cliquez sur chacun d'eux, le message JSON-RPC 2.0 complet envoyé ou reçu s'affiche, ce qui est très utile pour le débogage.

👉 Parfait, nous avons terminé d'inspecter tous nos agents. Vous pouvez maintenant fermer l'onglet "Inspecteur A2A".

11. Agent Orchestrator (client A2A)

Nous disposons désormais de trois agents spécialisés (Planner, Platform et Social) qui s'exécutent en tant que services indépendants compatibles A2A sur Cloud Run. La dernière pièce est l'agent Orchestrator. Cet agent agira en tant que coordinateur central ou client A2A. Il recevra les requêtes des utilisateurs, déterminera le ou les agents distants nécessaires pour y répondre (potentiellement de manière séquentielle), puis utilisera le protocole A2A pour déléguer les tâches à ces agents distants. Pour cet atelier, nous allons exécuter l'agent Orchestrator en local à l'aide de l'interface utilisateur de développement ADK.

all-agent-orchestrator

Commençons par améliorer la logique de l'orchestrateur pour gérer l'enregistrement des agents distants qu'il découvre. Stocke les informations de connexion des cartes d'agent récupérées lors de l'initialisation.

👉📝 Dans ~/instavibe-bootstrap/agents/orchestrate/agent.py, remplacez #REPLACE ME REG AGENT CARD par :

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)

Définissez ensuite l'outil pour l'agent Orchestrator lui-même dans ADK.

  • send_message (fonction A2A permettant de déléguer des tâches).

👉📝 Remplacez #REPLACE ME CREATE AGENT dans ~/instavibe-bootstrap/agents/orchestrate/agent.py par :

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], 
        )

La logique de base de l'orchestrateur réside dans ses instructions, qui lui indiquent comment utiliser A2A.

👉📝 Remplacez #REPLACE ME INSTRUCTIONS dans ~/instavibe-bootstrap/agents/orchestrate/agent.py par cette méthode de génération d'instructions :

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']}`
                """

Tester Orchestrator et le système A2A complet

Testons maintenant l'ensemble du système. Nous allons exécuter l'orchestrateur localement à l'aide de l'interface utilisateur de développement ADK. Il communiquera avec les agents Planner, Platform et Social qui s'exécutent à distance sur Cloud Run.

👉💻 Tout d'abord, assurez-vous que la variable d'environnement REMOTE_AGENT_ADDRESSES contient les URL séparées par une virgule de vos agents déployés compatibles A2A. Définissez ensuite les variables d'environnement nécessaires pour l'agent Orchestrator et lancez l'interface utilisateur de développement ADK :

. ~/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

👉 Ouvrez l'UI de développement ADK (reconfigurez le port sur 8000 via l'aperçu sur le Web).

10-08-web-preview.png

👉 Dans le menu déroulant de l'agent, sélectionnez l'agent orchestrate.

👉 Maintenant, confie-lui une tâche complexe qui nécessite de coordonner plusieurs agents à distance. Essayez ce premier exemple, qui devrait impliquer l'agent social, puis l'agent de planification :

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.

Orchestrer

Observez l'interaction dans la fenêtre de chat de l'interface utilisateur de développement ADK. Portez une attention particulière aux réponses de l'orchestrateur. Il doit indiquer à quel agent à distance il délègue les tâches (par exemple, "OK, je vais d'abord demander à l'agent de profil social des informations sur Ian et Nora...").

Consultez également l'onglet "Événements" de l'UI pour voir les appels d'outils sous-jacents (send_message) effectués vers les URL des agents à distance.

Envoyer une tâche

👉 Essayez maintenant un deuxième exemple qui devrait impliquer directement l'agent d'intégration de plate-forme :

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

Encore une fois, surveillez le chat et l'onglet "Événements". L'orchestrateur doit identifier la nécessité de créer un événement et déléguer la tâche (avec tous les détails fournis) à l'"agent d'intégration de la plate-forme". Vous pouvez également cliquer sur le bouton Trace pour afficher les traces et analyser les temps de réponse des requêtes et les opérations exécutées. Envoyer un événement

Vous pouvez ensuite vérifier que l'événement apparaît dans l'application Web InstaVibe. Événement InstaVibe

Cela démontre la mise en œuvre réussie d'un système multi-agents utilisant ADK et le protocole A2A, où un orchestrateur central délègue des tâches à des agents distants spécialisés.

N'oubliez pas d'arrêter l'interface utilisateur de développement ADK (Ctrl+C dans le terminal) lorsque vous avez terminé les tests.

12. Agent Engine et appel à distance d'InstaVibe

Jusqu'à présent, nous avons exécuté nos agents spécialisés sur Cloud Run et testé l'orchestrateur en local à l'aide de l'interface utilisateur de développement ADK. Pour un scénario de production, nous avons besoin d'un environnement robuste, évolutif et géré pour héberger nos agents. C'est là que Google Vertex AI Agent Engine entre en jeu.

Agent Engine est un service entièrement géré sur Vertex AI, conçu spécifiquement pour déployer et faire évoluer des agents d'IA. Elle élimine la gestion de l'infrastructure, la sécurité et les frais généraux opérationnels, ce qui permet aux développeurs (en particulier ceux qui connaissent moins bien les environnements cloud complexes) de se concentrer sur la logique et les capacités de l'agent plutôt que sur la gestion des serveurs. Il fournit un environnement d'exécution dédié et optimisé pour les charges de travail agentives.

Nous allons maintenant déployer notre agent Orchestrator sur Agent Engine. (Remarque : Le mécanisme de déploiement présenté ci-dessous utilise un script personnalisé (agent_engine_app.py) fourni dans les supports de l'atelier, car les outils de déploiement ADK vers Agent Engine officiels et directs sont encore en cours de développement. Ce script gère l'empaquetage et le déploiement de l'agent Orchestrator, configuré avec les adresses d'agent à distance nécessaires.)

Exécutez la commande suivante pour déployer l'agent Orchestrator sur Agent Engine. Assurez-vous que la variable d'environnement REMOTE_AGENT_ADDRESSES (contenant les URL de vos agents Planner, Platform et Social sur Cloud Run) est toujours correctement définie par rapport à la section précédente.

👉💻 Nous allons déployer l'agent Orchestrate sur Agent Engine (remarque : il s'agit de ma propre implémentation du déploiement, ADK dispose d'une CLI pour faciliter le déploiement. Je mettrai à jour cette information après l'implémentation de 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

Maintenant que l'orchestrateur est hébergé sur la plate-forme Agent Engine gérée, notre application Web InstaVibe doit communiquer avec lui. Au lieu d'interagir via l'interface utilisateur de développement ADK, l'application Web effectuera des appels à distance au point de terminaison Agent Engine.

10-agent-remote.png

Nous devons d'abord modifier le code de l'application InstaVibe pour initialiser le client Agent Engine à l'aide de l'ID unique de notre agent Orchestrator déployé. Cet ID est nécessaire pour cibler la bonne instance d'agent sur la plate-forme.

👉📝 Ouvrez ~/instavibe-bootstrap/instavibe/introvertally.py et remplacez #REPLACE ME initiate agent_engine par le code suivant. Cela récupère l'ID Agent Engine à partir d'une variable d'environnement (que nous allons définir sous peu) et obtient un objet client :

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

Le parcours utilisateur prévu dans InstaVibe implique deux interactions avec l'agent : la première consiste à générer le programme recommandé et la seconde à demander à l'utilisateur de confirmer avant que l'agent ne publie réellement l'événement sur la plate-forme.

Étant donné que l'application Web InstaVibe (exécutée sur Cloud Run) et l'agent Orchestrator (exécuté sur Agent Engine) sont désormais des services distincts, l'application Web doit effectuer des appels à distance au point de terminaison Agent Engine pour interagir avec l'agent.

👉📝 Mettons à jour le code qui effectue l'appel initial pour générer la recommandation de plan. Dans le même fichier introvertally.py, remplacez #REPLACE ME Query remote agent get plan par l'extrait de code suivant, qui utilise le client agent_engine pour envoyer la requête de l'utilisateur :

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

👉📝 Ensuite, mettez à jour le code qui gère la confirmation de l'utilisateur (par exemple, lorsque l'utilisateur clique sur "Confirmer le forfait"). Un message de suivi est envoyé dans la même conversation sur Agent Engine, demandant à l'orchestrateur de procéder à la publication de l'événement (qu'il déléguera à l'agent d'intégration de la plate-forme). Remplacez #REPLACE ME Query remote agent for confirmation pour la confirmation dans introvertally.py par :

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

Les routes de l'application Web doivent avoir accès à ces fonctions. Assurez-vous que les fonctions nécessaires de introvertally.py sont importées dans le fichier des routes Flask.

👉📝 Dans cd ~/instavibe-bootstrap/instavibe/ally_routes.py, nous allons d'abord pointer vers l'instance, puis remplacer # REPLACE ME TO ADD IMPORT par ce qui suit :

from introvertally import call_agent_for_plan, post_plan_event

👉📝 Ajoutez la fonctionnalité de prototype à InstaVibe. Dans ~/instavibe-bootstrap/instavibe/templates/base.html, remplacez <!–REPLACE_ME_LINK_TO_INTROVERT_ALLY–> par ce qui suit :

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

Avant de pouvoir redéployer l'application InstaVibe, nous avons besoin du Resource ID spécifique de l'agent Orchestrator que nous avons déployé sur Agent Engine.

Actuellement, la récupération de cette valeur de manière programmatique via gcloud peut être limitée. Nous allons donc utiliser un script Python d'assistance (temp-endpoint.py fourni dans l'atelier) pour récupérer l'ID et le stocker dans une variable d'environnement.

👉💻 Exécutez les commandes suivantes pour exécuter le script. Le script capture l'ID du point de terminaison Agent Engine et accorde les autorisations nécessaires au compte de service par défaut d'Agent Engine (remarque : le script est configuré pour utiliser le compte de service par défaut, car il n'est actuellement pas modifiable par l'utilisateur).

. ~/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}"

ID du point de terminaison du moteur d&#39;agent

Enfin, nous devons redéployer l'application Web InstaVibe avec le code mis à jour et la nouvelle variable d'environnement ORCHESTRATE_AGENT_ID afin qu'elle sache comment se connecter à notre agent exécuté sur Agent Engine.

👉💻 Les commandes suivantes reconstruisent l'image de l'application InstaVibe et déploient la nouvelle version sur 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

Une fois le déploiement final terminé, accédez à l'URL de votre application InstaVibe dans un autre onglet de navigateur.

Tester l'expérience InstaVibe complète optimisée par l'IA

La fonctionnalité "InstaVibe Ally" est désormais disponible. Elle est optimisée par notre système multi-agents orchestré via Vertex AI Agent Engine et communique via A2A.

12-02-new.png

Cliquez sur "InstaVibe Ally" et demandez-lui de planifier un événement.

12-03-introvertally.png

Observez le journal d'activité à droite pendant que les agents travaillent (cela peut prendre entre 90 et 120 secondes). Une fois le plan affiché, examinez-le, puis cliquez sur "Confirmer ce plan" pour le publier.

12-04-confirm.png

L'orchestrateur va maintenant demander à l'agent de plate-forme de créer le post et l'événement dans InstaVibe. 12-05-posting.png

Consultez la page d'accueil InstaVibe pour découvrir le nouveau post et le nouvel événement. 12-06-instavibe.png

La page de l'événement reflète les détails générés par l'agent.

12-07-event.png

Analyser les performances avec Cloud Trace

Vous remarquerez peut-être que le processus prend un certain temps. Vertex AI Agent Engine s'intègre à Cloud Trace, ce qui nous permet d'analyser la latence de notre système multi-agents.

Accédez à Traces dans la console Google Cloud, puis sélectionnez agent_run[orchestrate_agent] dans la section "Span". Vous devriez voir plusieurs spans. Cliquez dessus.

12-08-trace.png

Dans les détails de la trace, vous pouvez identifier les parties qui ont pris le plus de temps. Par exemple, les appels à l'agent Planner peuvent afficher une latence plus élevée en raison de l'ancrage de la recherche et de la génération complexe. 12-09-plan.png

De même, lorsque vous créez le post et l'événement, vous pouvez voir le temps passé par l'orchestrateur à traiter les données et à préparer les appels d'outils pour l'agent de plate-forme. 12-10-post.png

L'exploration de ces traces permet de comprendre et d'optimiser les performances de votre système d'agent.

celebrate.png

Félicitations ! Vous avez réussi à créer, déployer et tester un système d'IA multi-agents sophistiqué à l'aide de l'ADK, d'A2A, de MCP et des services Google Cloud. Vous avez abordé l'orchestration des agents, l'utilisation des outils, la gestion des états et le déploiement dans le cloud, ce qui vous a permis de créer une fonctionnalité fonctionnelle basée sur l'IA pour InstaVibe. Bravo ! Vous avez terminé l'atelier.

13. Effectuer un nettoyage

Pour éviter que des frais ne soient facturés en permanence sur votre compte Google Cloud, il est important de supprimer les ressources que nous avons créées lors de cet atelier. Les commandes suivantes vous aideront à supprimer l'instance Spanner, les services Cloud Run, le dépôt Artifact Registry, la clé API, Vertex AI Agent Engine et les autorisations IAM associées.

Important :

  • Assurez-vous d'exécuter ces commandes dans le même projet Google Cloud que celui utilisé pour l'atelier.
  • Si vous avez fermé votre terminal Cloud Shell, il est possible que certaines variables d'environnement (comme $PROJECT_ID, $SPANNER_INSTANCE_ID, etc.) ne soient pas définies. Vous devrez les réexporter comme vous l'avez fait lors de la configuration de l'atelier ou remplacer les variables dans les commandes ci-dessous par leurs valeurs réelles.
  • Ces commandes supprimeront définitivement vos ressources. Vérifiez bien que vous n'avez pas d'autres données importantes dans ce projet avant de l'exécuter.

👉💻 Exécutez les scripts suivants pour effectuer un nettoyage.

Réinitialiser les variables d'environnement

. ~/instavibe-bootstrap/set_env.sh

Supprimer un moteur d'agent :

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."

Supprimez les services 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."

Arrêter et supprimer le conteneur Docker A2A Inspector

docker rm --force a2a-inspector

Supprimez l'instance Spanner :

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

Supprimez le dépôt 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."

Supprimez les rôles du compte de service :

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."

Supprimer les fichiers locaux de l'atelier :

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."

Effectuer un nettoyage