Agent Stack di Google in azione: ADK, A2A, MCP su Google Cloud

1. Cosa imparerai a fare

Ti diamo il benvenuto. Oggi intraprenderemo un viaggio piuttosto interessante. Iniziamo pensando a una piattaforma di eventi sociali molto diffusa, InstaVibe. Anche se è un successo, sappiamo che per alcuni utenti la pianificazione effettiva delle attività di gruppo può sembrare un compito ingrato. Immagina di dover capire cosa interessa a tutti i tuoi amici, poi di dover vagliare infinite opzioni per eventi o locali e infine di dover coordinare tutto. È tanto! È proprio qui che possiamo introdurre l'AI e, più nello specifico, gli agenti intelligenti, per fare davvero la differenza.

L'idea è quella di creare un sistema in cui questi agenti possano svolgere il lavoro più pesante, come "ascoltare" in modo intelligente per comprendere le preferenze degli utenti e degli amici e quindi suggerire in modo proattivo attività fantastiche e personalizzate. Il nostro obiettivo è trasformare la pianificazione sociale su InstaVibe in qualcosa di semplice e piacevole. Per iniziare a creare questi assistenti intelligenti, dobbiamo porre solide basi con gli strumenti giusti.

Ecco il concetto che vedrai:

Pagina del titolo

Elementi di base con l'ADK di Google: padroneggia i principi fondamentali per creare il tuo primo agente intelligente utilizzando l'Agent Development Kit (ADK) di Google. Comprendi i componenti essenziali, il ciclo di vita dell'agente e come utilizzare in modo efficace gli strumenti integrati del framework.

Estensione delle funzionalità degli agenti con il Model Context Protocol (MCP): scopri come dotare i tuoi agenti di strumenti e contesto personalizzati, consentendo loro di svolgere attività specializzate e accedere a informazioni specifiche. Introduci il concetto di Model Context Protocol (MCP). Imparerai a configurare un server MCP per fornire questo contesto.

Progettazione di interazioni e orchestrazione degli agenti:vai oltre i singoli agenti per comprendere l'orchestrazione degli agenti. Progetta pattern di interazione che vanno da semplici flussi di lavoro sequenziali a scenari complessi che coinvolgono cicli, logica condizionale ed elaborazione parallela. Introduci il concetto di sub-agenti all'interno del framework ADK per gestire le attività modulari.

Creazione di sistemi multi-agente collaborativi:scopri come progettare sistemi in cui più agenti collaborano per raggiungere obiettivi complessi. Scopri e implementa il protocollo di comunicazione da agente ad agente (A2A), stabilendo un modo standardizzato per interagire in modo affidabile tra agenti distribuiti (che potrebbero essere eseguiti su macchine o servizi diversi).

Produzione di agenti su Google Cloud:esegui la transizione delle applicazioni di agenti dagli ambienti di sviluppo al cloud. Scopri le best practice per la progettazione e il deployment di sistemi multi-agente scalabili e solidi su Google Cloud (GCP). Scopri come sfruttare i servizi GCP come Cloud Run ed esplora le funzionalità dell'ultima versione di Google Agent Engine per ospitare e gestire i tuoi agenti.

2. Architettura

Pianificazione dei social basata sull'AI con InstaVibe

Che cos'è l'ascolto dei social?

Il social listening è il processo di monitoraggio delle conversazioni digitali su piattaforme come social media, forum e siti di notizie per capire cosa dicono le persone di un argomento, un brand o un settore. Fornisce informazioni preziose su sentiment pubblico, tendenze ed esigenze degli utenti. In questo workshop, sfrutteremo questo concetto all'interno di un sistema basato su agenti.

Sei nel team di InstaVibe

Immagina di lavorare per "InstaVibe", una startup di successo con una piattaforma di eventi sociali molto apprezzata dai giovani. Le cose vanno bene, ma come molte aziende tecnologiche, il tuo team è sotto pressione da parte degli investitori per innovare utilizzando l'AI. A livello interno, hai anche notato un segmento di utenti che non interagiscono tanto quanto gli altri. Magari sono meno propensi ad avviare attività di gruppo o trovano difficile il processo di pianificazione. Per la tua azienda, questo significa una minore fidelizzazione alla piattaforma tra questo importante gruppo di utenti.

La ricerca del tuo team suggerisce che l'assistenza basata sull'AI potrebbe migliorare significativamente l'esperienza di questi utenti. L'idea è di semplificare la pianificazione delle uscite sociali suggerendo in modo proattivo attività pertinenti in base agli interessi dell'utente e dei suoi amici. La domanda che ti poni con i tuoi colleghi è: in che modo gli agenti AI possono automatizzare le attività spesso dispendiose in termini di tempo di scoperta degli interessi, ricerca di attività e potenzialmente coordinamento iniziale?

Una soluzione basata su agenti (concetto di prototipo)

Proponi di sviluppare una funzionalità prototipo basata su un sistema multi-agente. Ecco una suddivisione concettuale:

Caso d'uso

  • Agente di profilazione sociale: questo agente utilizza tecniche di ascolto sociale per analizzare le connessioni, le interazioni e le tendenze pubbliche potenzialmente più ampie correlate alle preferenze dell'utente. Il suo scopo è identificare interessi condivisi e caratteristiche di attività adatte (ad es. preferenze per incontri più tranquilli, hobby specifici).
  • Agente di pianificazione eventi: utilizzando gli approfondimenti dell'agente di profilazione social, questo agente cerca risorse online per eventi, luoghi o idee specifici che corrispondono ai criteri identificati (come località, interessi).
  • Agente di interazione con la piattaforma (che utilizza MCP): questo agente prende il piano finalizzato dall'agente di pianificazione delle attività. La sua funzione principale è interagire direttamente con la piattaforma InstaVibe utilizzando uno strumento MCP (Model Context Protocol) predefinito. Questo strumento fornisce all'agente la funzionalità specifica per creare una bozza di suggerimento per un evento e un post che ne descriva il piano.
  • Agente di orchestrazione: questo agente funge da coordinatore centrale. Riceve la richiesta iniziale dell'utente dalla piattaforma InstaVibe, comprende l'obiettivo generale (ad es. "organizza un evento per me e i miei amici") e poi delega attività specifiche agli agenti specializzati appropriati in una sequenza logica. Gestisce il flusso di informazioni tra gli agenti e garantisce che il risultato finale venga restituito all'utente.

Elementi e tecnologie architettoniche chiave

Architettura

Google Cloud Platform (GCP):

  • Vertex AI:
    • Modelli Gemini: fornisce l'accesso ai modelli linguistici di grandi dimensioni (LLM) all'avanguardia di Google, come Gemini, che potenziano le capacità di ragionamento e di processo decisionale dei nostri agenti.
    • Vertex AI Agent Engine: un servizio gestito utilizzato per eseguire il deployment, l'hosting e la scalabilità del nostro agente orchestratore, semplificando la produzione e astraendo le complessità dell'infrastruttura.
  • Cloud Run: una piattaforma serverless per il deployment di applicazioni containerizzate. Lo utilizziamo per:
    • Ospita l'applicazione web principale InstaVibe.
    • Esegui il deployment di singoli agent abilitati A2A (Planner, Profilazione social, Interazione con la piattaforma) come microservizi indipendenti.
    • Esegui il server dello strumento MCP, rendendo disponibili agli agenti le API interne di InstaVibe.
  • Spanner: un database relazionale completamente gestito, distribuito a livello globale e a elevata coerenza. In questo workshop, sfruttiamo le sue funzionalità come database grafico utilizzando le funzionalità di query e DDL GRAPH per:
    • Modella e memorizza relazioni sociali complesse (utenti, amicizie, partecipazione a eventi, post).
    • Consente di eseguire query efficienti su queste relazioni per gli agenti di profilazione sociale.
  • Artifact Registry: un servizio completamente gestito per archiviare, gestire e proteggere le immagini container.
  • Cloud Build: un servizio che esegue le tue build su Google Cloud. Lo utilizziamo per creare automaticamente immagini container Docker dal codice sorgente dell'agente e dell'applicazione.
  • Cloud Storage: utilizzato da servizi come Cloud Build per archiviare gli artefatti di build e da Agent Engine per le sue esigenze operative.
  • Framework e protocolli degli agenti principali:
    • Agent Development Kit (ADK) di Google: il framework principale per:
      • Definizione della logica di base, del comportamento e dei set di istruzioni per i singoli agenti intelligenti.
      • Gestione del ciclo di vita, dello stato e della memoria dell'agente (stato della sessione a breve termine e potenzialmente conoscenza a lungo termine).
      • Integrazione di strumenti (come la Ricerca Google o strumenti personalizzati) che gli agenti possono utilizzare per interagire con il mondo.
      • Orchestrazione di workflow multi-agente, inclusa l'esecuzione sequenziale, in loop e parallela di subagenti.
    • Protocollo di comunicazione da agente ad agente (A2A): uno standard aperto che consente:
      • Comunicazione e collaborazione dirette e standardizzate tra diversi agenti AI, anche se vengono eseguiti come servizi separati o su macchine diverse.
      • Gli agenti possono scoprire le capacità reciproche (tramite le schede degli agenti) e delegare le attività. Ciò è fondamentale per consentire all'agente Orchestrator di interagire con gli agenti specializzati Planner, Social e Platform.
    • Libreria Python A2A (a2a-python): la libreria concreta utilizzata per far sì che i nostri agenti ADK utilizzino il protocollo A2A. Fornisce i componenti lato server necessari per:
      • Esporre i nostri agenti come server conformi ad A2A.
      • Gestire automaticamente la pubblicazione della "scheda dell'agente" per il rilevamento.
      • Ricevere e gestire le richieste di attività in entrata da altri agenti (come Orchestrator).
    • Model Context Protocol (MCP): uno standard aperto che consente agli agenti di:
      • Connettiti e utilizza strumenti, origini dati e sistemi esterni in modo standardizzato.
      • Il nostro agente di interazione con la piattaforma utilizza un client MCP per comunicare con un server MCP, che a sua volta espone strumenti per interagire con le API esistenti della piattaforma InstaVibe.
  • Strumenti di debug:
    • A2A Inspector: A2A Inspector è uno strumento di debug basato sul web utilizzato in questo workshop per connettersi, ispezionare e interagire con i nostri agenti abilitati A2A. Sebbene non faccia parte dell'architettura di produzione finale, è una parte essenziale del nostro flusso di lavoro di sviluppo. Fornisce:
      • Visualizzatore schede agente: per recuperare e convalidare le funzionalità pubbliche di un agente.
      • Interfaccia della chat live: per inviare messaggi direttamente a un agente di cui è stato eseguito il deployment per test immediati.
      • Console di debug: per visualizzare i messaggi JSON-RPC non elaborati scambiati tra l'inspector e l'agente.
  • Modelli linguistici (LLM): il "cervello" del sistema:
    • Modelli Gemini di Google: in particolare, utilizziamo versioni come gemini-2.0-flash. Questi modelli vengono scelti per:
      • Ragionamento avanzato e rispetto delle istruzioni: la loro capacità di comprendere prompt complessi, seguire istruzioni dettagliate e ragionare sulle attività li rende adatti a supportare il processo decisionale degli agenti.
      • Uso di strumenti (chiamata di funzioni): i modelli Gemini eccellono nel determinare quando e come utilizzare gli strumenti forniti tramite l'ADK, consentendo agli agenti di raccogliere informazioni o eseguire azioni.
      • Efficienza (modelli Flash): le varianti "flash" offrono un buon equilibrio tra prestazioni e convenienza, adatte a molte attività di agenti interattivi che richiedono risposte rapide.

Hai bisogno di crediti Google Cloud?

3. Prima di iniziare

👉 Fai clic su Attiva Cloud Shell nella parte superiore della console Google Cloud (l'icona a forma di terminale nella parte superiore del riquadro di Cloud Shell), Cloud Shell

👉 Fai clic sul pulsante "Apri editor" (ha l'aspetto di una cartella aperta con una matita). Si aprirà l'editor di codice di Cloud Shell nella finestra. Sul lato sinistro vedrai un esploratore di file. Cloud Shell

👉 Fai clic sul pulsante Accedi a Cloud Code nella barra di stato in basso, come mostrato. Autorizza il plug-in come indicato. Se nella barra di stato vedi Cloud Code - no project, seleziona questa opzione, poi seleziona "Select a Google Cloud Project" (Seleziona un progetto Google Cloud) nel menu a discesa e infine seleziona il progetto Google Cloud specifico dall'elenco dei progetti che hai creato. Cloud Shell

👉 Trova il tuo ID progetto Google Cloud:

  • Apri la console Google Cloud: https://console.cloud.google.com
  • Seleziona il progetto che vuoi utilizzare per questo workshop dal menu a discesa dei progetti nella parte superiore della pagina.
  • L'ID progetto viene visualizzato nella scheda informativa del progetto nella dashboard.

Cloud Shell

👉 Apri il terminale nell'IDE cloud, Cloud Shell

👉💻 Nel terminale, verifica di essere già autenticato e che il progetto sia impostato sul tuo ID progetto utilizzando il seguente comando:

gcloud auth list

👉💻 Clona il progetto instavibe-bootstrap da 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

Informazioni sulla struttura del progetto

Prima di iniziare a creare, prendiamoci un momento per capire il layout del progetto instavibe-bootstrap che hai appena clonato. In questo modo, saprai dove trovare e modificare i file durante il workshop.

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

Ecco una suddivisione delle directory principali:

  • agents/: Questo è il cuore del nostro sistema di AI. Ogni sottodirectory (planner/, social/, ecc.) contiene il codice sorgente di un agente intelligente specifico.
    • agent.py: all'interno della cartella di ogni agente, questo è il file principale in cui si trova la logica dell'agente.
    • a2a_server.py: questo file esegue il wrapping dell'agente ADK con un server Agent-to-Agent (A2A).
    • Dockerfile: definisce come creare l'immagine container per il deployment dell'agente su Cloud Run o Agent Engine.
  • instavibe/: Questa directory contiene l'intero codice sorgente dell'applicazione web InstaVibe.
  • tools/: questa directory serve per creare strumenti esterni che i nostri agenti possono utilizzare.
    • instavibe/ contiene il server Model Context Protocol (MCP).

Questa struttura modulare separa l'applicazione web dai vari componenti di AI, rendendo l'intero sistema più facile da gestire, testare ed eseguire il deployment.

👉💻 Esegui lo script di inizializzazione:

Lo script ti chiederà di inserire l'ID progetto Google Cloud.

Inserisci l'ID progetto Google Cloud che hai trovato nell'ultimo passaggio quando ti viene richiesto dallo script init.sh:

cd ~/instavibe-bootstrap
./init.sh

👉💻 Imposta l'ID progetto necessario:

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

👉💻 Esegui il comando seguente per abilitare le API Google Cloud necessarie:

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

👉💻 Imposta tutte le variabili di ambiente necessarie:

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"

Configurazione dell'autorizzazione

👉💻 Concedi autorizzazioni. Nel terminale, esegui :

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"


👉 Convalida il risultato nella console IAMCloud Shell

👉💻 Esegui questi comandi nel terminale per creare un repository Artifact Registry. Tutte le immagini Docker per i nostri agenti, il server MCP e l'applicazione InstaVibe vengono archiviate qui prima del deployment in Cloud Run o 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"

Configurare la piattaforma Maps per le chiavi API

Per utilizzare i servizi Google Maps nella tua applicazione InstaVibe, devi creare una chiave API e limitarla in modo appropriato.

👉 In una nuova scheda, vai ad API e servizi > Credenziali. Nella pagina "Credenziali", fai clic sul pulsante + CREA CREDENZIALI in alto. Seleziona Chiave API dal menu a discesa. testo alternativo

👉 Viene visualizzata una finestra di dialogo che mostra la chiave API appena creata. Ti servirà in un secondo momento per la configurazione dell'applicazione.

👉 Fai clic su CHIUDI nella finestra di dialogo "Chiave API creata".

👉 Vedrai la nuova chiave API elencata (ad es. "API key 1"). Fai clic sui tre puntini a destra e seleziona Modifica chiave API per aprire la pagina "Limita e rinomina chiave API". testo alternativo

👉 Nel campo Nome in alto, modifica il nome predefinito in: Chiave API di Maps Platform (🚨🚨IMPORTANTE🚨🚨 Utilizza questo nome.)

Maps Platform API Key

👉 Nella sezione "Limitazioni delle applicazioni", assicurati che sia selezionata l'opzione Nessuna.

👉 Nella sezione "Restrizioni delle API", seleziona il pulsante di opzione Limita chiave.

👉 Fai clic sul menu a discesa Seleziona API. Nella casella di ricerca visualizzata, digita Maps JavaScript API e selezionalo dall'elenco. testo alternativo

👉 Fai clic su OK.

👉 Fai clic sul pulsante SALVA in fondo alla pagina.

Risultato chiave

Ora hai creato correttamente una chiave API denominata "Chiave API Google Maps Platform", l'hai limitata per consentire solo l'utilizzo dell'API Maps JavaScript e hai verificato che l'API sia abilitata per il tuo progetto.

4. Configura il database a grafo

Prima di poter creare i nostri agenti intelligenti, abbiamo bisogno di un modo per archiviare e comprendere le ricche connessioni all'interno del nostro social network InstaVibe. È qui che entra in gioco un database a grafo. A differenza dei database relazionali tradizionali che archiviano i dati in tabelle di righe e colonne, un database a grafo è progettato specificamente per rappresentare ed eseguire query sui dati in termini di nodi (come persone, eventi o post) e delle relazioni (archi) che li collegano (come amicizie, partecipazione a eventi o menzioni). Questa struttura è incredibilmente efficace per le applicazioni di social media perché rispecchia il modo in cui sono strutturati i social network reali, rendendo intuitivo esplorare il modo in cui sono interconnesse le diverse entità.

Stiamo implementando questo database a grafo utilizzando Google Cloud Spanner. Sebbene Spanner sia noto principalmente come database relazionale distribuito a livello globale e fortemente coerente, ci consente anche di definire ed eseguire query sulle strutture grafiche direttamente sopra le nostre tabelle relazionali.

In questo modo, otteniamo i vantaggi combinati di scalabilità, coerenza transazionale e interfaccia SQL familiare di Spanner, insieme alla potenza espressiva delle query grafiche per analizzare le complesse dinamiche sociali cruciali per le nostre funzionalità basate sull'AI.

👉💻 Nel terminale IDE di Cloud Shell. Esegui il provisioning dell'infrastruttura necessaria su Google Cloud. Inizieremo creando un'istanza Spanner, che funge da contenitore dedicato per i nostri database. Una volta che l'istanza è pronta, creeremo il database Spanner effettivo al suo interno, che conterrà tutte le nostre tabelle e i dati del grafico per 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

👉💻 Concedi l'accesso in lettura/scrittura a Spanner al service account predefinito

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}

👉💻 Ora. Configureremo un ambiente virtuale Python, installeremo i pacchetti Python richiesti, configureremo lo schema del database del grafico in Spanner, lo caricheremo con i dati iniziali ed eseguiremo lo 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

👉 In una nuova scheda del browser, vai alla console Google Cloud, vai a Spanner e dovresti visualizzare un elenco delle tue istanze Spanner. Fai clic su instavibe-graph-instance. istanza Spanner 👉 Nella pagina di panoramica dell'istanza, vedrai un elenco dei database all'interno dell'istanza. Fai clic su graphdbspanner db.

👉 Nel riquadro di navigazione a sinistra del database, fai clic su Spanner Studio spanner studio

👉 Nell'editor di query (scheda Query senza titolo), incolla la seguente query Graph SQL. Questa query troverà tutti i nodi Person e le loro relazioni di amicizia dirette con altri nodi Person. e fai clic su ESEGUI per visualizzare il risultato.

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

spanner graph

👉 Nello stesso editor di query, sostituisci il DDL precedente per trovare le persone che hanno partecipato allo stesso evento, il che implica una connessione indiretta tramite un'attività condivisa.

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

👉 Questa query esplora un tipo diverso di connessione, in cui le persone menzionate nei post scritti dagli amici di una persona specifica eseguono la seguente query nell'editor di query.

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

Queste query offrono solo un'idea della potenza dell'utilizzo di Spanner come database a grafo per la nostra applicazione InstaVibe. Modellando i nostri dati social come un grafico interconnesso, consentiamo un'analisi sofisticata delle relazioni e delle attività, che sarà fondamentale per consentire ai nostri agenti AI di comprendere il contesto degli utenti, scoprire i loro interessi e, in definitiva, fornire un'assistenza intelligente per la pianificazione social.

Ora che la nostra struttura di dati di base è stata implementata e testata, concentriamoci sull'applicazione InstaVibe esistente con cui interagiranno i nostri agenti.

5. Stato attuale di InstaVibe

Per capire dove si inseriranno i nostri agenti AI, dobbiamo prima eseguire il deployment ed eseguire l'applicazione web InstaVibe esistente. Questa applicazione fornisce l'interfaccia utente e le funzionalità di base che si connettono al database del grafico Spanner che abbiamo già configurato.

home page

L'applicazione InstaVibe utilizza Google Maps per visualizzare visivamente le sedi degli eventi nelle pagine dei dettagli degli eventi. Per abilitare questa funzionalità, l'applicazione ha bisogno della chiave API che abbiamo creato in precedenza. Il seguente script recupererà la stringa della chiave effettiva utilizzando il nome visualizzato che abbiamo assegnato ("Chiave API Maps Platform").

pagina dell&#39;evento

👉💻 Torna all'IDE Cloud Shell. Esegui lo script riportato di seguito. Successivamente, controlla attentamente l'output per assicurarti che la chiave GOOGLE_MAPS_API_KEY mostrata corrisponda a quella che hai creato e copiato in precedenza dalla 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}"

risultato chiave

👉💻 Ora creiamo l'immagine container per l'applicazione web InstaVibe ed eseguiamo il push nel nostro repository 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}

👉💻 Esegui il deployment della nuova immagine di build dell'app web InstaVibe in 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

Una volta completato il deployment, i log di Cloud Run dovrebbero mostrare l'URL pubblico dell'applicazione InstaVibe in esecuzione.

URL

Puoi trovare questo URL anche andando alla sezione Cloud Run nella console Google Cloud e selezionando il servizio instavibe. ElencoURL

Apri l'URL nel browser web per esplorare la piattaforma di base di InstaVibe. Visualizza i post, gli eventi e le connessioni utente basati sul database a grafo che abbiamo configurato.

Ora che l'applicazione di destinazione è in esecuzione, iniziamo a creare il primo agente intelligente per migliorarne le funzionalità.

6. Agente base,Event Planner con ADK

Framework ADK

Introduzione al framework ADK di Google Ora che la base (l'app e il database InstaVibe) è impostata, possiamo iniziare a creare il nostro primo agente intelligente utilizzando l'Agent Development Kit (ADK) di Google.

Agent Development Kit (ADK) è un framework flessibile e modulare progettato specificamente per lo sviluppo e il deployment di agenti AI. Il suo principio di progettazione è rendere lo sviluppo di agenti più simile allo sviluppo software tradizionale, con l'obiettivo di semplificare notevolmente la creazione, il deployment e l'orchestrazione di architetture agentiche in grado di gestire qualsiasi attività, da quelle semplici e monoscopo a quelle complesse e multi-agente.

Al centro, l'ADK ruota attorno al concetto di Agent, che racchiude istruzioni, configurazione (come il modello linguistico scelto, ad es. Gemini) e un insieme di Tools che può utilizzare per eseguire azioni o raccogliere informazioni.

06-agent.png

Il nostro agente iniziale sarà un "Organizzatore di eventi". Il suo scopo principale è quello di prendere le richieste degli utenti per uscite sociali (specificando posizione, date e interessi) e generare suggerimenti creativi e personalizzati. Per garantire che i suggerimenti siano pertinenti e basati su informazioni aggiornate (come eventi specifici che si svolgono in quel fine settimana), utilizzeremo uno degli strumenti integrati di ADK: la Ricerca Google. In questo modo, l'agente può basare le sue risposte sui risultati web in tempo reale, recuperando i dettagli più recenti su luoghi, eventi e attività che corrispondono ai criteri dell'utente.

👉📝 Nell'IDE Cloud Shell, in ~/instavibe-bootstrap/agents/planner/agent.py aggiungi il seguente prompt e le seguenti istruzioni per creare l'agente

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

Il nostro primo agente è stato definito. Uno dei principali punti di forza di ADK è la sua natura intuitiva e gli strumenti utili che fornisce. Una particolarmente utile è la UI di sviluppo dell'ADK, che ti consente di testare in modo interattivo il tuo agente e vedere le sue risposte in tempo reale.

👉💻 Iniziamo. I seguenti comandi avvieranno l'interfaccia utente di 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

Dopo aver eseguito i comandi, dovresti visualizzare un output nel terminale che indica che il server web ADK è stato avviato, simile a questo:

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

👉 Successivamente, per accedere all'interfaccia utente di ADK Dev dal browser:

Dall'icona Anteprima web (spesso a forma di occhio o di quadrato con una freccia) nella barra degli strumenti di Cloud Shell (di solito in alto a destra), seleziona Cambia porta. Nella finestra popup, imposta la porta su 8000 e fai clic su "Cambia e visualizza anteprima". Cloud Shell aprirà quindi una nuova scheda o finestra del browser che mostra l'UI di ADK Dev.

Anteprima web

Una volta aperta l'interfaccia utente di ADK Dev nel browser: nel menu a discesa in alto a destra dell'interfaccia utente, seleziona planner come agente con cui vuoi interagire. Ora, nella finestra di dialogo della chat a destra, prova ad assegnare un'attività all'agente. Ad esempio, avvia una conversazione con l'agente:

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

Suggerisci una data (la tua preferenza)

July 12 2025

Dovresti vedere l'agente elaborare la tua richiesta e fornire un piano in base ai risultati della Ricerca Google.

adk dev ui

Interagire con un agente è una cosa, ma come facciamo a sapere se si comporta sempre come previsto, soprattutto quando apportiamo modifiche?

I metodi tradizionali di test del software spesso non sono sufficienti per gli agenti AI a causa della loro natura generativa e non deterministica. Per colmare il divario tra una demo interessante e un agente di produzione affidabile, è fondamentale una solida strategia di valutazione. A differenza della semplice verifica dell'output finale di un modello generativo, la valutazione di un agente spesso comporta la valutazione del suo processo decisionale e della sua capacità di utilizzare correttamente gli strumenti o seguire le istruzioni in vari scenari. L'ADK fornisce funzionalità utili a questo scopo.

Valuta

👉 Nell'interfaccia utente di sviluppo dell'ADK, fai clic sulla scheda "Valutazione" nel riquadro di navigazione a sinistra. Dovresti vedere un file di test precaricato denominato plan_eval. Questo file contiene input e criteri predefiniti per testare il nostro agente di pianificazione.

👉 Seleziona uno scenario, ad esempio "boston", e fai clic sul pulsante Esegui valutazione. Nella finestra popup visualizzata, abbassa il punteggio di corrispondenza a 0,3 e fai clic su Avvia.

Punteggio corrispondenza

In questo modo, l'agente verrà eseguito con l'input di test e verrà verificato se il suo output soddisfa le aspettative definite. In questo modo puoi testare sistematicamente il rendimento del tuo agente.

adk dev ui evaluation

👉 Ora vediamo cosa succede con una soglia più rigorosa. Seleziona lo scenario "nyc" e fai di nuovo clic su Esegui valutazione. Questa volta, lascia il punteggio di corrispondenza al valore predefinito (Punteggio di corrispondenza della risposta: 0,7) e fai clic su Avvia. Noterai che il risultato è Non superato. Questo è previsto, in quanto l'output creativo dell'agente non corrisponde perfettamente alla risposta "ideale" predefinita.

adk dev ui evaluation fail

👉 Per capire perché non è andato a buon fine, fai clic sull'icona di errore nella riga "nyc". L'interfaccia utente ora mostra un confronto affiancato della risposta effettiva dell'agente e della risposta prevista dello scenario di test. Questa visualizzazione è essenziale per il debug, in quanto ti consente di vedere esattamente dove l'output dell'agente è diverso e di perfezionare le istruzioni di conseguenza.

Una volta terminata l'esplorazione della UI e della valutazione, torna al terminale Cloud Shell Editor e premi Ctrl+C per interrompere la UI di ADK Dev.

Sebbene l'output di testo in formato libero sia un buon punto di partenza, per applicazioni come InstaVibe è molto più pratico utilizzare dati strutturati (come JSON) per consentire all'agente di utilizzare facilmente i suggerimenti. Modifichiamo l'agente in modo che restituisca il piano in un formato JSON coerente.

👉📝 In ~/instavibe-bootstrap/agents/planner/agent.py, trova la riga con la dicitura RETURN PLAN in MARKDOWN FORMAT all'interno della stringa di istruzioni dell'agente. Sostituisci la riga con la seguente struttura JSON dettagliata:

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

Ora che hai aggiornato le istruzioni dell'agente per richiedere specificamente l'output JSON, verifichiamo la modifica.

👉💻 Riavvia l'interfaccia utente per sviluppatori dell'ADK utilizzando lo stesso comando di prima:

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

Aggiorna la scheda se è già aperta. In alternativa, segui gli stessi passaggi di prima per aprire l'interfaccia utente di ADK Dev nel browser (tramite l'anteprima web di Cloud Shell sulla porta 8000). Una volta caricata l'interfaccia utente, assicurati che sia selezionato l'agente di pianificazione.

👉 Questa volta, proviamo con un'altra richiesta. Nella finestra di dialogo della chat, inserisci:

Plan an event Boston this weekend with art and coffee

Esamina attentamente la risposta dell'agente. Invece di una risposta di testo puramente conversazionale, ora dovresti vedere una risposta formattata rigorosamente come oggetto JSON, corrispondente alla struttura che abbiamo definito nelle istruzioni (contenente fun_plans, plan_description, locations_and_activities e così via). Ciò conferma che l'agente ora può produrre un output strutturato adatto all'utilizzo programmatico da parte della nostra applicazione InstaVibe.

adk dev ui json

Dopo aver confermato l'output JSON, torna al terminale Cloud Shell e premi Ctrl+C per interrompere l'interfaccia utente di ADK Dev.

Componenti ADK

Sebbene l'interfaccia utente di ADK Dev sia ideale per i test interattivi, spesso è necessario eseguire gli agenti in modo programmatico, magari nell'ambito di un'applicazione o di un servizio di backend più ampio. Per capire come funziona, esaminiamo alcuni concetti di base dell'ADK relativi alla gestione del runtime e del contesto.

Le conversazioni significative e multi-turno richiedono agli agenti di comprendere il contesto, ricordando ciò che è stato detto e fatto per mantenere la continuità. L'ADK fornisce modi strutturati per gestire questo contesto tramite Sessione, Stato e Memoria:

  • Sessione:quando un utente inizia a interagire con un agente, viene creata una sessione. Consideralo come il contenitore di un singolo thread di chat specifico. Contiene un ID univoco, la cronologia delle interazioni (eventi), i dati di lavoro correnti (stato) e metadati come l'ora dell'ultimo aggiornamento.
  • Stato:la memoria di lavoro a breve termine dell'agente all'interno di una singola sessione. È un dizionario modificabile in cui l'agente può archiviare le informazioni temporanee necessarie per completare l'attività corrente (ad es. le preferenze dell'utente raccolte finora, i risultati intermedi delle chiamate di strumenti).
  • Memoria:rappresenta il potenziale dell'agente di richiamo a lungo termine in diverse sessioni o l'accesso a knowledge base esterni. Mentre Sessione e Stato gestiscono la conversazione immediata, Memoria (spesso gestita da un MemoryService) consente a un agente di recuperare informazioni da interazioni passate o origini dati strutturate, fornendo un contesto di conoscenza più ampio. Nota: il nostro semplice client utilizza servizi in memoria per semplicità, il che significa che la memoria/lo stato vengono mantenuti solo durante l'esecuzione dello script.
  • Evento:ogni interazione all'interno di una sessione (messaggio dell'utente, risposta dell'agente, richiesta di utilizzo di uno strumento, risultato dello strumento, modifica dello stato, errore) viene registrata come evento immutabile. In questo modo viene creato un log cronologico, essenzialmente la trascrizione e la cronologia delle azioni della conversazione.

Quindi, come vengono gestiti quando viene eseguito un agente? Questo è il compito del runner.

  • Runner: il runner è il motore di esecuzione principale fornito da ADK. Definisci l'agente e gli strumenti che utilizza e Runner orchestra il processo di evasione della richiesta di un utente. Gestisce la sessione, gestisce il flusso degli eventi, aggiorna lo stato, richiama il modello linguistico sottostante, coordina le chiamate agli strumenti e potenzialmente interagisce con MemoryService. Immaginalo come il direttore d'orchestra che si assicura che tutte le parti funzionino correttamente insieme.

Possiamo utilizzare Runner per eseguire il nostro agente come applicazione Python autonoma, completamente indipendente dalla UI di sviluppo.

Creiamo un semplice script client per richiamare il nostro programma dell'agente pianificatore in modo programmatico.

👉📝 Nel file ~/instavibe-bootstrap/agents/planner/planner_client.py, aggiungi il seguente codice Python sotto le importazioni esistenti. In planner_client.py, sotto le importazioni, aggiungi quanto segue:

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

Questo codice configura i servizi in memoria per la gestione di sessioni e artefatti (per semplificare l'esempio), crea una sessione, definisce una query utente, configura Runner con il nostro agente e poi esegue l'agente in modo asincrono, stampando ogni evento generato durante l'esecuzione.

👉💻 Ora esegui questo script client dal terminale:

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

👀 Osserva l'output. Anziché solo il piano JSON finale, vedrai la struttura dettagliata di ogni oggetto Evento generato durante il flusso di esecuzione dell'agente. Ciò include l'evento del messaggio iniziale dell'utente, i potenziali eventi correlati alle chiamate di strumenti (come la Ricerca Google) e, infine, l'evento di risposta del modello contenente il piano JSON. Questo stream di eventi dettagliato è molto utile per il debug e per comprendere l'elaborazione passo passo che avviene all'interno di ADK Runtime.

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

Se lo script viene eseguito continuamente o si blocca, potrebbe essere necessario interromperlo manualmente premendo Ctrl+C.

7. Platform Interaction Agent - interact with MCP Server

Sebbene l'ADK aiuti a strutturare i nostri agenti, spesso questi devono interagire con sistemi o API esterni per eseguire azioni nel mondo reale.

Model Context Protocol (MCP)

Il Model Context Protocol (MCP) è uno standard aperto progettato per standardizzare il modo in cui le applicazioni AI, come gli agenti, si connettono a origini dati, strumenti e sistemi esterni. Il suo scopo è risolvere il problema della necessità di integrazioni personalizzate per ogni combinazione di applicazione AI e origine dati fornendo un'interfaccia universale. MCP utilizza un'architettura client-server in cui i client MCP, che risiedono all'interno delle applicazioni AI (host), gestiscono le connessioni ai server MCP. Questi server sono programmi esterni che espongono funzionalità specifiche come l'accesso ai dati locali, l'interazione con servizi remoti tramite API o la fornitura di prompt predefiniti, consentendo ai modelli di AI di accedere a informazioni aggiornate ed eseguire attività al di là del loro addestramento iniziale. Questa struttura consente ai modelli di AI di scoprire e interagire con funzionalità esterne in modo standardizzato, rendendo le integrazioni più semplici e scalabili.

Crea ed esegui il deployment del server MCP InstaVibe

07-mcp-server.png

I nostri agenti dovranno interagire con la piattaforma InstaVibe stessa, in particolare per creare post e registrare eventi utilizzando le API esistenti della piattaforma. L'applicazione InstaVibe espone già queste funzionalità tramite endpoint HTTP standard:

Endpoint

URL

Metodo HTTP

Descrizione

Crea post

api/posts

PUBBLICA

Endpoint API per aggiungere un nuovo post. È previsto un corpo JSON:
{"author_name": "...", "text": "...", "sentiment": "..." (optional)}

Crea evento

api/events

PUBBLICA

Endpoint API per aggiungere un nuovo evento e i relativi partecipanti (schema semplificato).
È previsto un corpo JSON: { "event_name": "...", "description": "...", "event_date": "YYYY-MM-DDTHH:MM:SSZ", "locations": [ {"name": "...", "description": "...", "latitude": 0.0, "longitude": 0.0, "address": "..."} ], "attendee_names": ["...", "..."] }

Per rendere queste funzionalità disponibili ai nostri agenti tramite MCP, dobbiamo prima creare semplici funzioni Python che fungano da wrapper per queste chiamate API. Queste funzioni gestiranno la logica della richiesta HTTP.

👉 Per prima cosa, implementiamo la funzione wrapper per creare un post. Apri il file ~/instavibe-bootstrap/tools/instavibe/instavibe.py e sostituisci il commento #REPLACE ME CREATE POST con il seguente codice Python:

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

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

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

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

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

👉📝 Successivamente, creeremo la funzione wrapper per l'API di creazione degli eventi. Nello stesso file ~/instavibe-bootstrap/tools/instavibe/instavibe.py, sostituisci il commento #REPLACE ME CREATE EVENTS con questo codice:

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

Come puoi vedere, queste funzioni sono semplici wrapper delle API InstaVibe esistenti. Questo pattern è utile se hai già API per i tuoi servizi, puoi esporre facilmente le loro funzionalità come strumenti per gli agenti creando questi wrapper.

Implementazione del server MCP

Ora che abbiamo le funzioni Python che eseguono le azioni (chiamando le API InstaVibe), dobbiamo creare il componente server MCP. Questo server esporrà queste funzioni come "strumenti" in base allo standard MCP, consentendo ai client MCP (come i nostri agenti) di rilevarli e richiamarli.

Un server MCP in genere implementa due funzionalità chiave:

  • list_tools: responsabile di consentire al client di scoprire gli strumenti disponibili sul server, fornendo metadati come nomi, descrizioni e parametri richiesti, spesso definiti utilizzando JSON Schema
  • call_tool: gestisce l'esecuzione di uno strumento specifico richiesto dal client, ricevendo il nome e gli argomenti dello strumento ed eseguendo l'azione corrispondente, ad esempio nel nostro caso l'interazione con un'API

I server MCP vengono utilizzati per fornire ai modelli di AI l'accesso a dati e azioni reali, consentendo attività come l'invio di email, la creazione di attività nei sistemi di gestione dei progetti, la ricerca nei database o l'interazione con vari software e servizi web. Mentre le implementazioni iniziali si concentravano spesso su server locali che comunicavano tramite input/output (stdio) standard per semplicità, in particolare negli ambienti di sviluppo o "studio", il passaggio a server remoti che utilizzano protocolli come HTTP con eventi inviati dal server (SSE) ha più senso per un'adozione più ampia e per i casi d'uso aziendali.

L'architettura remota, nonostante il livello di comunicazione di rete aggiuntivo, offre vantaggi significativi: consente a più client AI di condividere l'accesso a un singolo server, centralizza la gestione e gli aggiornamenti degli strumenti, migliora la sicurezza mantenendo i dati sensibili e le chiavi API sul lato server anziché distribuirli su potenzialmente molte macchine client e disaccoppia il modello di AI dalle specifiche dell'integrazione del sistema esterno, rendendo l'intero ecosistema più scalabile, sicuro e gestibile rispetto a quando ogni istanza di AI deve gestire le proprie integrazioni dirette.

07-mcp-server.png

Implementeremo il nostro server MCP utilizzando HTTP e Server-Sent Events (SSE) per la comunicazione, che è adatto a esecuzioni di strumenti potenzialmente di lunga durata e a scenari aziendali.

👉📝 Innanzitutto, implementiamo l'endpoint list_tools. Apri il file ~/instavibe-bootstrap/tools/instavibe/mcp_server.py e sostituisci il commento #REPLACE ME - LIST TOOLS con il seguente codice. :

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

Questa funzione definisce gli strumenti (create_event, create_post) e informa i client di connessione.

👉📝 Successivamente, implementa l'endpoint call_tool, che gestisce le richieste di esecuzione effettive dei client. Nello stesso file ~/instavibe-bootstrap/tools/instavibe/mcp_server.py, sostituisci il commento #REPLACE ME - CALL TOOLS con questo codice.

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

Questa funzione riceve il nome e gli argomenti dello strumento, trova la funzione wrapper Python corrispondente che abbiamo definito in precedenza, la esegue e restituisce il risultato

👉💻 Ora che la logica del server MCP è definita, dobbiamo pacchettizzarla come container. Nel terminale, esegui il seguente script per creare l'immagine Docker utilizzando 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}

👉💻 Esegui il deployment dell'immagine come servizio su 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

👉💻 Al termine del deployment, il server MCP sarà in esecuzione e accessibile tramite un URL pubblico. Dobbiamo acquisire questo URL in modo che il nostro agente (che funge da client MCP) sappia a cosa connettersi.

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

Ora dovresti anche vedere il servizio mcp-tool-server elencato come "In esecuzione" nella sezione Cloud Run della console Google Cloud.

Cloud Run

Con il server MCP di cui è stato eseguito il deployment e il relativo URL acquisito, ora possiamo implementare l'agente che fungerà da client MCP e utilizzare gli strumenti esposti da questo server.

8. Agente di interazione con la piattaforma (utilizzando MCP)

Client MCP Il client MCP è un componente che risiede all'interno di un'applicazione o di un agente AI e funge da interfaccia tra il modello AI e uno o più server MCP. Nella nostra implementazione, questo client verrà integrato direttamente nel nostro agente. La funzione principale di questo client è comunicare con i server MCP per scoprire gli strumenti disponibili tramite la funzione list_tools e successivamente richiedere l'esecuzione di strumenti specifici utilizzando la funzione call_tool, passando gli argomenti necessari forniti dal modello di AI o dall'agente che orchestra la chiamata.

Client MCP

Ora creeremo l'agente che funge da client MCP. Questo agente, in esecuzione all'interno del framework ADK, sarà responsabile della comunicazione con mcp-tool-server che abbiamo appena implementato.

👉 Innanzitutto, dobbiamo modificare la definizione dell'agente per recuperare dinamicamente gli strumenti dal nostro server MCP in esecuzione. In agents/platform_mcp_client/agent.py, sostituisci #REPLACE ME - FETCH TOOLS con quanto segue:

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

Questo codice utilizza il metodo MCPToolset.from_server per connettersi a MCP_SERVER_URL (che abbiamo impostato in precedenza come variabile di ambiente) e recuperare l'elenco degli strumenti disponibili.

Successivamente, dobbiamo indicare alla definizione dell'agente ADK di utilizzare effettivamente questi strumenti recuperati dinamicamente.

👉 In agents/platform_mcp_client/agent.py, sostituisci #REPLACE ME - SET TOOLs con quanto segue:

  tools=[tools],

👉💻 Ora testiamo questo agente localmente utilizzando l'interfaccia utente di ADK Dev per verificare se riesce a connettersi correttamente al server MCP e a utilizzare gli strumenti per interagire con la nostra applicazione InstaVibe in esecuzione.

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

Apri di nuovo la UI di sviluppo dell'ADK nel browser (utilizzando l'anteprima web di Cloud Shell sulla porta 8000). Questa volta, nel menu a discesa in alto a destra, seleziona l'agente platform_mcp_client.

Proviamo lo strumento create_post. Nella finestra di dialogo della chat, inserisci la seguente richiesta:

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 dell&#39;interfaccia utente per sviluppatori ADK

L'agente deve elaborare questa richiesta, identificare la necessità di utilizzare lo strumento create_post, comunicare con il server MCP, che a sua volta chiama l'API InstaVibe.

👉 Passaggio di verifica: dopo che l'agente ha confermato l'azione, apri (o aggiorna) la scheda in cui è in esecuzione l'applicazione InstaVibe. Dovresti vedere il nuovo post di "Giulia" nel feed principale.

Post InstaVibe

👉💻 Esegui questo script in un terminale separato per ottenere il link di Instavibe, se necessario:

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

👉📝 Ora testiamo lo strumento create_event. Inserisci la seguente richiesta su più righe nella finestra di dialogo della 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],
}

Anche in questo caso, l'agente deve utilizzare lo strumento appropriato tramite il server MCP. Nella scheda Eventi, fai clic sul singolo evento per visualizzare una traccia dettagliata e passo passo dell'esecuzione.

Evento UI di ADK Dev

👉 Passaggio di verifica: torna all'applicazione InstaVibe in esecuzione e vai alla sezione "Eventi" (o equivalente). Ora dovresti vedere l'evento "Mexico City Culinary & Art Day" appena creato.

Evento InstaVibe

Ciò dimostra correttamente come MCP consente al nostro agente di sfruttare gli strumenti esterni (in questo caso, le API di InstaVibe) in modo standardizzato.

Dopo aver verificato entrambe le azioni, torna al terminale Cloud Shell e premi Ctrl+C per interrompere l'interfaccia utente di ADK Dev.

9. Agente di workflow e multi-agenti in ADK

Finora i nostri agenti possono pianificare uscite e interagire con la piattaforma. Tuttavia, una pianificazione veramente personalizzata richiede la comprensione della cerchia sociale dell'utente. Per gli utenti impegnati che potrebbero non seguire da vicino le attività dei loro amici, raccogliere questo contesto manualmente è difficile. Per risolvere questo problema, creeremo un agente di profilazione sociale che sfrutta il nostro database grafico Spanner per analizzare le attività e gli interessi degli amici, consentendo suggerimenti più personalizzati.

Agente di profilazione social

Innanzitutto, abbiamo bisogno di strumenti per consentire all'agente di accedere ai dati del grafico.

👉📝 Aggiungi le seguenti funzioni Python alla fine del file ~/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

Ora vediamo come strutturare il nostro agente. L'analisi dei profili di più amici e il riepilogo dei risultati prevede diversi passaggi. Questo è lo scenario perfetto per utilizzare le funzionalità multi-agente dell'ADK, in particolare gli agenti del flusso di lavoro.

Nell'ADK di Google, un agente del flusso di lavoro non esegue direttamente le attività, ma coordina altri agenti, chiamati agenti secondari. Ciò consente una progettazione modulare, suddividendo i problemi complessi in componenti specializzati. L'ADK fornisce tipi di workflow integrati come

  • Sequenziale (passo passo)
  • Parallelo (esecuzione simultanea)
  • e Loop (esecuzione ripetuta)

Agente di profilazione social

Per l'attività di profilazione sociale, il nostro design utilizza un agente loop per creare un flusso di lavoro iterativo. L'intenzione è di elaborare una persona alla volta: profile_agent raccoglie i dati, summary_agent aggiorna l'analisi e check_agent determina se dobbiamo ripetere il ciclo.

Definiamo i subagenti necessari per questo workflow.

👉📝 In ~/instavibe-bootstrap/agents/social/agent.py, sostituisci #REPLACE FOR profile_agent con quanto segue:

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

Successivamente, l'agente prende le informazioni del profilo raccolte (accumulate nelle iterazioni del ciclo) e genera il riepilogo finale, identificando i punti in comune se sono state analizzate più persone.

👉📝 Nello stesso ~/instavibe-bootstrap/agents/social/agent.py, sostituisci #REPLACE FOR summary_agent con quanto segue:

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

Abbiamo bisogno di un modo per determinare quando il ciclo deve interrompersi (ovvero quando tutti i profili richiesti sono stati riepilogati).

👉📝 Nello stesso ~/instavibe-bootstrap/agents/social/agent.py, sostituisci #REPLACE FOR check_agent con quanto segue:

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

Aggiungiamo un semplice controllo programmatico (CheckCondition) che esamina esplicitamente summary_status memorizzato in State, restituito da check_agent e indica all'agente Loop se continuare (escalate=False) o interrompere (escalate=True).

👉📝 Nello stesso ~/instavibe-bootstrap/agents/social/agent.py, sostituisci #REPLACE FOR CheckCondition che si trova nella parte superiore del file con quanto segue:

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

Stato e callback per i risultati del ciclo

Nell'ADK di Google, State è un concetto fondamentale che rappresenta la memoria o i dati di lavoro di un agente durante la sua esecuzione. Si tratta essenzialmente di un contesto persistente che contiene le informazioni che un agente deve mantenere in diverse fasi, chiamate di strumenti o interazioni. Questo stato può memorizzare risultati intermedi, informazioni sull'utente, parametri per le azioni successive o qualsiasi altro dato che l'agente deve ricordare man mano che procede con un'attività.

Nel nostro scenario, man mano che l'agente loop itera, summary_agent e check_agent memorizzano i loro output (summary e summary_status) nello stato dell'agente. In questo modo, le informazioni vengono mantenute tra le iterazioni. Tuttavia, l'agente Loop non restituisce automaticamente il riepilogo finale dallo stato al termine.

Agente di profilazione social

I callback in ADK ci consentono di inserire una logica personalizzata da eseguire in punti specifici del ciclo di vita di un agente o in risposta a determinati eventi, ad esempio il completamento di una chiamata di funzione o prima che l'agente termini l'esecuzione. Forniscono un modo per personalizzare il comportamento dell'agente ed elaborare i risultati in modo dinamico.

Utilizzeremo un after_agent_callback che viene eseguito al termine del ciclo (perché CheckCondition è stato riassegnato). Questo callback modify_output_after_agent recupera il riepilogo finale dallo stato e lo formatta come messaggio di output finale dell'agente.

Richiama

👉📝 Nello stesso ~/instavibe-bootstrap/agents/social/agent.py, sostituisci #REPLACE FOR modify_output_after_agent con follow:

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

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

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

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

Definizione dell'agente Root Loop

Infine, definiamo l'agente principale LoopAgent. Coordina i sub-agenti in sequenza all'interno di ogni iterazione del ciclo (profile_agent -> summary_agent -> check_agent -> CheckCondition). Ripeterà questa sequenza fino a max_iterations volte o finché CheckCondition non segnala il completamento. after_agent_callback garantisce la restituzione del riepilogo finale.

👉📝 Nello stesso ~/instavibe-bootstrap/agents/social/agent.py, sostituisci #REPLACE FOR root_agent con follow:

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

Testiamo questo flusso di lavoro multi-agente utilizzando l'interfaccia utente di sviluppo dell'ADK.

👉💻 Avvia il server 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

Apri l'interfaccia utente di sviluppo dell'ADK (porta 8000 tramite l'anteprima web). Nel menu a discesa dell'agente (in alto a destra), seleziona l'agente Social.

👉 Ora, chiedi di creare il profilo di più persone. Nella finestra di dialogo della chat, inserisci:

Tell me about Mike and Bob

Dopo la risposta dell'agente (che potrebbe richiedere un po' più di tempo a causa del ciclo e delle chiamate a più LLM), non limitarti a guardare l'output finale della chat. Vai alla scheda Eventi nel riquadro a sinistra dell'interfaccia utente per sviluppatori dell'ADK.

👉 Passaggio di verifica: nella scheda Eventi, vedrai una traccia dettagliata e passo passo dell'esecuzione. 09-01-adk-dev-ui.png

Dopo aver osservato come l'agente richiama ogni subagente, dove prevedi che il flusso vada da profile_agent -> summary_agent -> check_agent, Checker in ogni iterazione. Tuttavia, in pratica, vediamo in azione la potente "auto-ottimizzazione" dell'agente.

Poiché il modello sottostante vede l'intera richiesta (ad es. "profilo Mike e Bob"), spesso sceglie il percorso più efficiente, raccogliendo tutti i dati richiesti in un'unica svolta consolidata anziché iterare più volte. Puoi visualizzare gli input, gli output e gli stati di ogni passaggio, incluse le chiamate agli strumenti effettuate da profile_agent

09-02-ui-graph.png

e gli aggiornamenti di stato di check_agent e CheckCondition. 09-03-ui-state.png

Questa traccia visiva è preziosa per comprendere ed eseguire il debug del funzionamento del flusso di lavoro multi-agente fino alla generazione e alla restituzione del riepilogo finale tramite il callback.

Dopo aver esplorato la risposta della chat e la traccia degli eventi, torna al terminale Cloud Shell e premi Ctrl+C per arrestare l'interfaccia utente di ADK Dev.

10. Comunicazione da agente ad agente (A2A)

Finora abbiamo creato agenti specializzati, ma operano in modo isolato o all'interno di un flusso di lavoro predefinito sulla stessa macchina. Per creare sistemi multi-agente veramente distribuiti e collaborativi, abbiamo bisogno di un modo per consentire agli agenti, potenzialmente in esecuzione come servizi separati, di scoprirsi e comunicare in modo efficace. È qui che entra in gioco il protocollo Agent-to-Agent (A2A).

Il protocollo A2A è uno standard aperto progettato specificamente per la comunicazione interoperabile tra agenti AI. Mentre MCP si concentra sull'interazione da agente a strumento, A2A si concentra sull'interazione da agente ad agente. Consente agli agenti di:

  • Scopri: trova altri agenti e scopri le loro funzionalità tramite schede standardizzate.
  • Comunica: scambia messaggi e dati in modo sicuro.
  • Collabora: delega attività e coordina azioni per raggiungere obiettivi complessi.

Il protocollo A2A facilita questa comunicazione tramite meccanismi come le "schede dell'agente", che gli agenti possono utilizzare per pubblicizzare le proprie funzionalità e informazioni di connessione.

10-05-agent-card

A2A utilizza standard web familiari (HTTP, SSE, JSON-RPC) e spesso impiega un modello client-server in cui un agente (client) invia attività a un altro (agente/server remoto). Questa standardizzazione è fondamentale per creare sistemi modulari e scalabili in cui gli agenti sviluppati in modo indipendente possono collaborare.

Attivazione di A2A per gli agenti InstaVibe

Per rendere i nostri agenti Planner, Interazione con la piattaforma e Social accessibili ad altri agenti tramite A2A, dobbiamo racchiuderli in un componente server A2A. Questo server:

  • Esporre una scheda dell'agente: pubblica una descrizione standard delle funzionalità dell'agente tramite un endpoint HTTP.
  • Listen for Tasks(Request Messages): accetta le richieste di attività in entrata da altri agenti (client A2A) in base al protocollo A2A.
  • Gestisci l'esecuzione delle attività(richiedi messaggi): trasferisci le attività ricevute alla logica dell'agente ADK sottostante per l'elaborazione.

Planner Agent (A2A Enabled)

all-agent-planner

Iniziamo aggiungendo il livello del server A2A al nostro agente Planner.

Definisci la logica di avvio del server A2A. Questo codice definisce AgentCard (la descrizione pubblica dell'agente), configura A2AServer e lo avvia, collegandolo a PlatformAgentExecutor.

👉📝 Aggiungi il seguente codice alla fine di ~/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)

👉💻 Verifichiamo rapidamente se il server A2A viene avviato correttamente in locale e se pubblica la scheda dell'agente. Esegui questo comando nel primo terminale:

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

👉 Ora apri un'altra finestra del terminale. (Fai clic sul segno + nel riquadro del terminale) due terminali

👉💻 Utilizza curl per richiedere la scheda dell'agente dal server in esecuzione locale:

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

Dovresti visualizzare la rappresentazione JSON di AgentCard che abbiamo definito, a conferma che il server è in esecuzione e che annuncia l'agente Planner.

10-02-planner-a2a.png

Torna al primo terminale (dove è in esecuzione il server) e premi Ctrl+C per arrestarlo.

👉💻 Con la logica del server A2A aggiunta, ora possiamo creare l'immagine container.

Crea ed esegui il deployment dell'agente di pianificazione

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

👉💻 Esegui il deployment del nostro agente di pianificazione su 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

Verifichiamo che il servizio di cui è stato eseguito il deployment sia in esecuzione e che la scheda dell'agente venga pubblicata correttamente dal cloud utilizzando A2A Inspector.

👉 Dall'icona Anteprima web nella barra degli strumenti di Cloud Shell, seleziona Cambia porta. Imposta la porta su 8081 e fai clic su "Cambia e visualizza anteprima". Si aprirà una nuova scheda del browser con l'interfaccia di A2A Inspector.

10-08-web-preview.png

👉💻 Nel terminale, recupera l'URL dell'agente di pianificazione di cui è stato eseguito il deployment:

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

👉💻 Copia l'URL di output.

👉 Nell'interfaccia utente di A2A Inspector, incolla l'URL nel campo URL agente e fai clic su Connetti.

👀 I dati della carta dell'agente e il file JSON dovrebbero essere visualizzati nella scheda Carta dell'agente, a conferma della riuscita della connessione.

10-03-planner-a2a.png

👉 Fai clic sulla scheda Chat in A2A Inspector. Qui puoi interagire direttamente con l'agente di cui è stato eseguito il deployment. Inviagli un messaggio per testare la sua capacità di pianificazione. Ad esempio:

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

👀 Per esaminare la comunicazione non elaborata, fai clic sulla bolla del messaggio e poi sulla bolla della risposta dell'agente nella finestra della chat. Quando fai clic su ciascuno, viene visualizzato il messaggio JSON-RPC 2.0 completo inviato o ricevuto, il che è molto utile per il debug.

Tieni a portata di mano la scheda A2A Inspector. NON chiuderla. Lo useremo di nuovo tra un attimo per testare gli altri due agenti.

10-06-a2a-inspector.png

Agente di interazione della piattaforma (A2A abilitato)

all-agent-platform

Successivamente, ripeteremo la procedura per l'agente di interazione con la piattaforma (quello che utilizza MCP).

👉📝 Definisci la configurazione del server A2A, inclusa la relativa AgentCard univoca, alla fine di ~/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)

Agente social (A2A abilitato)

all-agent-social

Infine, attiviamo A2A per il nostro agente di profilazione social.

👉📝 Definisci la configurazione del server A2A e AgentCard alla fine di ~/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)

Crea e implementa gli agenti di interazione con la piattaforma e social

Questi agenti devono accedere a Spanner, quindi assicurati che le variabili di ambiente SPANNER_INSTANCE_ID, SPANNER_DATABASE_ID e MCP_SERVER_URL vengano trasmesse correttamente durante il deployment.

👉💻 Crea ed esegui il deployment in Cloud Run con 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}"

👉💻 Nel terminale, recupera l'URL dell'agente della piattaforma di cui è stato eseguito il deployment:

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

👉💻 Copia l'URL di output.

👉 Nell'interfaccia utente di A2A Inspector, incolla l'URL nel campo URL agente e fai clic su Connetti.

👀 I dati della carta dell'agente e il file JSON dovrebbero essere visualizzati nella scheda Carta dell'agente, a conferma della riuscita della connessione.

10-05-platform-a2a.png

👉 Fai clic sulla scheda Chat in A2A Inspector. Qui puoi interagire direttamente con l'agente di cui hai eseguito il deployment, inviargli un messaggio e testare la sua capacità di creare post:

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.

👀 Per esaminare la comunicazione non elaborata, fai clic sulla bolla del messaggio e poi sulla bolla della risposta dell'agente nella finestra della chat. Quando fai clic su ciascuno, viene visualizzato il messaggio JSON-RPC 2.0 completo inviato o ricevuto, il che è molto utile per il debug.

👉💻 Nel terminale, recupera l'URL dell'agente social di cui è stato eseguito il deployment:

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

👉💻 Copia l'URL di output.

👉 Nell'interfaccia utente di A2A Inspector, incolla l'URL nel campo URL agente e fai clic su Connetti.

👀 I dati della carta dell'agente e il file JSON dovrebbero essere visualizzati nella scheda Carta dell'agente, a conferma della riuscita della connessione.

10-04-social-a2a.png

👉 Fai clic sulla scheda Chat in A2A Inspector. Qui puoi interagire direttamente con l'agente di cui è stato eseguito il deployment. Inviagli un messaggio per analizzare i profili utente del tuo database:

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

👀 Per esaminare la comunicazione non elaborata, fai clic sulla bolla del messaggio e poi sulla bolla della risposta dell'agente nella finestra della chat. Quando fai clic su ciascuno, viene visualizzato il messaggio JSON-RPC 2.0 completo inviato o ricevuto, il che è molto utile per il debug.

👉 Ottimo, abbiamo terminato l'ispezione di tutti i nostri agenti. Ora puoi chiudere la scheda A2A Inspector.

11. Agente orchestratore (client A2A)

Ora abbiamo tre agenti specializzati (Planner, Platform, Social) che vengono eseguiti come servizi indipendenti abilitati A2A su Cloud Run. L'ultimo componente è l'agente di orchestrazione. Questo agente fungerà da coordinatore centrale o client A2A. Riceverà le richieste degli utenti, determinerà quali agenti remoti sono necessari per soddisfare la richiesta (potenzialmente in sequenza) e quindi utilizzerà il protocollo A2A per delegare le attività a questi agenti remoti. Per questo workshop, eseguiremo l'agente Orchestrator localmente utilizzando l'interfaccia utente di sviluppo dell'ADK.

all-agent-orchestrator

Innanzitutto, miglioriamo la logica di Orchestrator per gestire la registrazione degli agenti remoti che rileva. Memorizza i dettagli della connessione delle schede dell'agente recuperate durante l'inizializzazione.

👉📝 In ~/instavibe-bootstrap/agents/orchestrate/agent.py, sostituisci #REPLACE ME REG AGENT CARD con:

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)

Successivamente, definisci lo strumento per l'agente di orchestrazione stesso all'interno dell'ADK.

  • send_message (la funzione A2A per delegare il lavoro).

👉📝 Sostituisci #REPLACE ME CREATE AGENT in ~/instavibe-bootstrap/agents/orchestrate/agent.py con:

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 logica principale di Orchestrator risiede nelle sue istruzioni, che indicano come utilizzare A2A.

👉📝 Sostituisci #REPLACE ME INSTRUCTIONS in ~/instavibe-bootstrap/agents/orchestrate/agent.py con questo metodo di generazione delle istruzioni:

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

Test di Orchestrator e del sistema A2A completo

Ora testiamo l'intero sistema. Eseguiremo l'orchestratore localmente utilizzando l'interfaccia utente di sviluppo dell'ADK, che comunicherà con gli agenti Planner, Platform e Social in esecuzione in remoto su Cloud Run.

👉💻 Innanzitutto, assicurati che la variabile di ambiente REMOTE_AGENT_ADDRESSES contenga gli URL separati da virgole degli agenti abilitati A2A di cui è stato eseguito il deployment. Quindi, imposta le variabili di ambiente necessarie per l'agente Orchestrator e avvia l'interfaccia utente di sviluppo 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

👉 Apri la UI di sviluppo dell'ADK (ripristina la porta 8000 tramite l'anteprima web).

10-08-web-preview.png

👉 Nel menu a discesa dell'agente, seleziona l'agente orchestrate.

👉 Ora, assegnagli un'attività complessa che richiede il coordinamento di più agenti remoti. Prova questo primo esempio, che dovrebbe coinvolgere l'agente social e poi l'agente di pianificazione:

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.

Orchestrare

Osserva l'interazione nella finestra della chat dell'interfaccia utente di ADK Dev. Presta molta attenzione alle risposte dell'orchestratore, che dovrebbe indicare a quale agente remoto delega le attività (ad es. "Ok, chiederò prima all'agente del profilo social di Ian e Nora...").

Inoltre, controlla la scheda Eventi nella UI per visualizzare le chiamate agli strumenti (send_message) sottostanti effettuate agli URL degli agenti remoti.

Invia attività

👉 Ora prova un secondo esempio che dovrebbe coinvolgere direttamente l'agente di integrazione della piattaforma:

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

Ancora una volta, monitora la chat e la scheda Eventi. L'orchestratore deve identificare la necessità di creare un evento e delegare l'attività (con tutti i dettagli forniti) all'"agente di integrazione della piattaforma". Puoi anche fare clic sul pulsante Traccia per visualizzare le tracce per analizzare i tempi di risposta delle query e le operazioni eseguite. Invia evento

Puoi quindi verificare che l'evento venga visualizzato nell'applicazione web InstaVibe. Evento InstaVibe

Ciò dimostra l'implementazione riuscita di un sistema multi-agente utilizzando ADK e il protocollo A2A, in cui un orchestratore centrale delega le attività ad agenti remoti specializzati.

Al termine del test, ricordati di interrompere l'interfaccia utente di ADK Dev (Ctrl+C nel terminale).

12. Agent Engine e Chiamata remota da InstaVibe

Finora abbiamo eseguito i nostri agenti specializzati su Cloud Run e testato l'orchestratore localmente utilizzando l'interfaccia utente di ADK Dev. Per uno scenario di produzione, abbiamo bisogno di un ambiente robusto, scalabile e gestito per ospitare i nostri agenti. In questo caso, può esserti utile Google Vertex AI Agent Engine.

Agent Engine è un servizio completamente gestito su Vertex AI progettato specificamente per il deployment e lo scaling degli agenti AI. Astrae la gestione dell'infrastruttura, la sicurezza e l'overhead operativo, consentendo agli sviluppatori (soprattutto a quelli meno esperti di ambienti cloud complessi) di concentrarsi sulla logica e sulle funzionalità dell'agente anziché sulla gestione dei server. Fornisce un runtime dedicato ottimizzato per i carichi di lavoro agentici.

Ora eseguiamo il deployment dell'agente Orchestrator in Agent Engine. (Nota: il meccanismo di deployment mostrato di seguito utilizza uno script personalizzato (agent_engine_app.py) fornito nei materiali del workshop, poiché gli strumenti di deployment diretti da ADK ad Agent Engine ufficiali potrebbero essere ancora in fase di sviluppo. Questo script gestisce il packaging e il deployment dell'agente Orchestrator, configurato con gli indirizzi degli agenti remoti necessari.

Esegui questo comando per eseguire il deployment dell'agente Orchestrator in Agent Engine. Assicurati che la variabile di ambiente REMOTE_AGENT_ADDRESSES (contenente gli URL degli agenti Planner, Platform e Social su Cloud Run) sia ancora impostata correttamente dalla sezione precedente.

👉💻 Implementeremo l'agente Orchestrate in Agent Engine (nota: questa è la mia implementazione del deployment, ADK ha una CLI per facilitare il deployment. Aggiornerò questo passaggio dopo l'implementazione di 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

Ora che Orchestrator è ospitato sulla piattaforma gestita Agent Engine, la nostra applicazione web InstaVibe deve comunicare con lui. Anziché interagire tramite l'interfaccia utente di ADK Dev, l'app web effettuerà chiamate remote all'endpoint di Agent Engine.

10-agent-remote.png

Innanzitutto, dobbiamo modificare il codice dell'applicazione InstaVibe per inizializzare il client Agent Engine utilizzando l'ID univoco dell'agente Orchestrator di cui è stato eseguito il deployment. Questo ID è necessario per scegliere come target l'istanza dell'agente corretta sulla piattaforma.

👉📝 Apri ~/instavibe-bootstrap/instavibe/introvertally.py e sostituisci #REPLACE ME initiate agent_engine con il seguente codice. Questo comando recupera l'ID Agent Engine da una variabile di ambiente (che imposteremo a breve) e ottiene un oggetto client:

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

Il flusso utente pianificato in InstaVibe prevede due interazioni con l'agente: prima, la generazione del piano consigliato e, seconda, la richiesta all'utente di confermare prima che l'agente pubblichi effettivamente l'evento sulla piattaforma.

Poiché l'applicazione web InstaVibe (in esecuzione su Cloud Run) e l'agente Orchestrator (in esecuzione su Agent Engine) sono ora servizi separati, l'app web deve effettuare chiamate remote all'endpoint Agent Engine per interagire con l'agente.

👉📝 Aggiorniamo il codice che esegue la chiamata iniziale per generare il consiglio sul piano. Nello stesso file introvertally.py, sostituisci #REPLACE ME Query remote agent get plan con il seguente snippet, che utilizza il client agent_engine per inviare la richiesta dell'utente:

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

👉📝 Successivamente, aggiorna il codice che gestisce la conferma dell'utente (ad es. quando l'utente fa clic su "Conferma piano"). Viene inviato un messaggio di follow-up alla stessa conversazione su Agent Engine, che indica a Orchestrator di procedere con la pubblicazione dell'evento (che delegherà all'agente di integrazione della piattaforma). Sostituisci #REPLACE ME Query remote agent for confirmation per la conferma in introvertally.py con:

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

Le route dell'applicazione web devono accedere a queste funzioni. Assicurati che le funzioni necessarie di introvertally.py siano importate nel file delle route Flask.

👉📝 In cd ~/instavibe-bootstrap/instavibe/ally_routes.py, inizialmente faremo riferimento all'istanza sostituendo # REPLACE ME TO ADD IMPORT con quanto segue:

from introvertally import call_agent_for_plan, post_plan_event

👉📝 Aggiungi la funzionalità prototipo a InstaVibe. In ~/instavibe-bootstrap/instavibe/templates/base.html, sostituisci <!–REPLACE_ME_LINK_TO_INTROVERT_ALLY–> con quanto segue:

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

Prima di poter eseguire nuovamente il deployment dell'app InstaVibe, abbiamo bisogno del Resource ID specifico dell'agente Orchestrator di cui abbiamo eseguito il deployment in Agent Engine.

Al momento, il recupero di questo valore in modo programmatico tramite gcloud potrebbe essere limitato, quindi utilizzeremo uno script Python di assistenza (temp-endpoint.py fornito nel workshop) per recuperare l'ID e memorizzarlo in una variabile di ambiente.

👉💻 Esegui i seguenti comandi per eseguire lo script. Lo script acquisisce l'ID endpoint di Agent Engine e concede le autorizzazioni necessarie al service account predefinito di Agent Engine (nota: lo script è configurato per utilizzare il service account predefinito in quanto al momento non è modificabile dall'utente).

. ~/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 endpoint motore agente

Infine, dobbiamo eseguire nuovamente il deployment dell'applicazione web InstaVibe con il codice aggiornato e la nuova variabile di ambiente ORCHESTRATE_AGENT_ID in modo che sappia come connettersi al nostro agente in esecuzione su Agent Engine.

👉💻 I seguenti comandi ricompilano l'immagine dell'applicazione InstaVibe ed eseguono il deployment della nuova versione in 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

Al termine dell'implementazione finale, vai all'URL dell'applicazione InstaVibe in un'altra scheda del browser.

Test dell'esperienza completa di InstaVibe basata sull'AI

La funzionalità "InstaVibe Ally" è ora disponibile, basata sul nostro sistema multi-agente orchestrato tramite Vertex AI Agent Engine e che comunica tramite A2A.

12-02-new.png

Fai clic su "InstaVibe Ally" e chiedi di pianificare un evento.

12-03-introvertally.png

Osserva il log attività a destra mentre gli agenti lavorano (potrebbe richiedere 90-120 secondi). Una volta visualizzato il piano, esaminalo e fai clic su "Conferma questo piano" per procedere con la pubblicazione.

12-04-confirm.png

L'orchestratore ora chiederà all'agente della piattaforma di creare il post e l'evento in InstaVibe. 12-05-posting.png

Controlla la home page di InstaVibe per il nuovo post e l'evento. 12-06-instavibe.png

La pagina dell'evento rifletterà i dettagli generati dall'agente.

12-07-event.png

Analisi delle prestazioni con Cloud Trace

Potresti notare che la procedura richiede un po' di tempo. Vertex AI Agent Engine si integra con Cloud Trace, consentendoci di analizzare la latenza del nostro sistema multi-agente.

Vai a Tracce nella console Google Cloud, seleziona agent_run[orchestrate_agent] in Span, dovresti visualizzare un paio di intervalli, fai clic al loro interno.

12-08-trace.png

All'interno dei dettagli della traccia, puoi identificare le parti che hanno richiesto più tempo. Ad esempio, le chiamate all'agente Planner potrebbero mostrare una latenza maggiore a causa della ricerca di base e della generazione complessa. 12-09-plan.png

Analogamente, durante la creazione del post e dell'evento, potresti notare il tempo impiegato da Orchestrator per elaborare i dati e preparare le chiamate agli strumenti per l'agente della piattaforma. 12-10-post.png

L'esplorazione di queste tracce aiuta a comprendere e ottimizzare il rendimento del sistema di agenti.

celebrate.png

Complimenti! Hai creato, eseguito il deployment e testato correttamente un sofisticato sistema di AI multi-agente utilizzando ADK, A2A, MCP e i servizi Google Cloud. Hai gestito l'orchestrazione degli agenti, l'utilizzo degli strumenti, la gestione dello stato e il deployment sul cloud, creando una funzionalità funzionale basata sull'AI per InstaVibe. Complimenti per aver completato il workshop.

13. Esegui la pulizia

Per evitare addebiti continui al tuo account Google Cloud, è importante eliminare le risorse che abbiamo creato durante questo workshop. I seguenti comandi ti aiuteranno a rimuovere l'istanza Spanner, i servizi Cloud Run, il repository Artifact Registry, la chiave API, Vertex AI Agent Engine e le autorizzazioni IAM associate.

Importante:

  • Assicurati di eseguire questi comandi nello stesso progetto Google Cloud utilizzato per il workshop.
  • Se hai chiuso il terminale Cloud Shell, alcune variabili di ambiente come $PROJECT_ID, $SPANNER_INSTANCE_ID e così via potrebbero non essere impostate. Dovrai esportarli di nuovo come hai fatto durante la configurazione del workshop o sostituire le variabili nei comandi riportati di seguito con i loro valori effettivi.
  • Questi comandi elimineranno definitivamente le tue risorse. Verifica prima dell'esecuzione se nel progetto sono presenti altri dati importanti.

👉💻 Esegui i seguenti script per la pulizia.

Reimposta le variabili di ambiente

. ~/instavibe-bootstrap/set_env.sh

Elimina motore dell'agente:

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

Elimina i servizi 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."

Arresta e rimuovi il container Docker A2A Inspector

docker rm --force a2a-inspector

Elimina istanza Spanner:

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

Elimina il repository 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."

Rimuovi ruoli dal service account:

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

Elimina i file dell'officina locale:

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

Esegui la pulizia