🤖 Crea un agente AI multimodale con Graph RAG, ADK e Memory Bank

1. Introduzione

cover

1. La sfida

Negli scenari di risposta ai disastri, il coordinamento dei sopravvissuti con competenze, risorse ed esigenze diverse in più località richiede funzionalità di gestione e ricerca intelligenti dei dati. Questo workshop ti insegna a creare un sistema di AI di produzione che combina:

  1. 🗄️ Database a grafo (Spanner): memorizza le relazioni complesse tra sopravvissuti, competenze e risorse
  2. 🔍 Ricerca basata sull'AI: ricerca ibrida semantica e per parole chiave utilizzando gli embedding
  3. 📸 Elaborazione multimodale: estrai dati strutturati da immagini, testo e video
  4. 🤖 Orchestrazione multi-agente: coordina agenti specializzati per workflow complessi
  5. 🧠 Memoria a lungo termine: personalizzazione con Vertex AI Memory Bank

conversazione

2. Cosa creerai

Un database del grafico della rete di sopravvissuti con:

  • 🗺️ Visualizzazione interattiva del grafico 3D delle relazioni tra i sopravvissuti
  • 🔍 Ricerca intelligente (per parole chiave, semantica e ibrida)
  • 📸 Pipeline di caricamento multimodale (estrai entità da immagini/video)
  • 🤖 Sistema multi-agente per l'orchestrazione di attività complesse
  • 🧠 Integrazione di Memory Bank per interazioni personalizzate

3. Tecnologie di base

Componente

Tecnologia

Finalità

Database

Cloud Spanner Graph

Memorizza nodi (sopravvissuti, abilità) e archi (relazioni)

AI Search

Gemini + Embeddings

Comprensione semantica + ricerca per similarità

Framework dell'agente

ADK (Agent Development Kit)

Orchestrare i workflow di AI

Memoria

Vertex AI Memory Bank

Archiviazione delle preferenze degli utenti a lungo termine

Frontend

React + Three.js

Visualizzazione interattiva del grafico 3D

2. Preparazione dell'ambiente (salta se partecipi al workshop)

Parte 1: abilita l'account di fatturazione

  • Per richiedere il tuo account di fatturazione con un credito di 5 $, ti servirà per il deployment. Assicurati di accedere al tuo account Gmail.

Parte 2: Open Environment

  1. 👉 Fai clic su questo link per passare direttamente all'editor di Cloud Shell
  2. 👉 Se ti viene chiesto di concedere l'autorizzazione in qualsiasi momento della giornata, fai clic su Autorizza per continuare. Fai clic per autorizzare Cloud Shell
  3. 👉 Se il terminale non viene visualizzato nella parte inferiore dello schermo, aprilo:
    • Fai clic su Visualizza.
    • Fai clic su TerminaleApri un nuovo terminale nell'editor di Cloud Shell.
  4. 👉💻 Nel terminale, verifica di aver già eseguito l'autenticazione e che il progetto sia impostato sul tuo ID progetto utilizzando il seguente comando:
    gcloud auth list
    
  5. 👉💻 Clona il progetto di bootstrap da GitHub:
    git clone https://github.com/google-americas/way-back-home.git
    

3. Configurazione dell'ambiente

1. Inizializza

Nel terminale Cloud Shell Editor, se il terminale non viene visualizzato nella parte inferiore dello schermo, aprilo:

  • Fai clic su Visualizza.
  • Fai clic su Terminale.

Apri un nuovo terminale nell'editor di Cloud Shell

👉💻 Nel terminale, rendi eseguibile lo script init ed eseguilo:

cd ~/way-back-home/level_2
./init.sh

2. Configura il progetto

👉💻 Imposta l'ID progetto:

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

👉💻 Abilita le API richieste (operazione che richiede circa 2-3 minuti):

gcloud services enable compute.googleapis.com \
                       aiplatform.googleapis.com \
                       run.googleapis.com \
                       cloudbuild.googleapis.com \
                       artifactregistry.googleapis.com \
                       spanner.googleapis.com \
                       storage.googleapis.com

3. Esegui lo script di configurazione

👉💻 Esegui lo script di configurazione:

cd ~/way-back-home/level_2
./setup.sh

In questo modo verrà creato .env. In Cloud Shell, apri way_back_homeproject. Nella cartella level_2, puoi vedere che è stato creato il file .env. Se non riesci a trovarlo, puoi fare clic su View -> Toggle Hidden File per visualizzarlo. open_project

4. Carica dati di esempio

👉💻 Vai al backend e installa le dipendenze:

cd ~/way-back-home/level_2/backend
uv sync

👉💻 Carica i dati iniziali dei sopravvissuti:

uv run python ~/way-back-home/level_2/backend/setup_data.py

In questo modo viene creato:

  • Istanza Spanner (survivor-network)
  • Database (graph-db)
  • Tutte le tabelle dei nodi e dei bordi
  • Grafici delle proprietà per l'esecuzione di query Risultato previsto:
============================================================
SUCCESS! Database setup complete.
============================================================

Instance:  survivor-network
Database:  graph-db
Graph:     SurvivorGraph

Access your database at:
https://console.cloud.google.com/spanner/instances/survivor-network/databases/graph-db?project=waybackhome

Se fai clic sul link dopo Access your database at nell'output, puoi aprire Spanner nella console Google Cloud.

open_spanner

e vedrai Spanner nella console Google Cloud.

spanner

4. Visualizzare i dati del grafico in Spanner Studio

Questa guida ti aiuta a visualizzare e interagire con i dati del grafico Survivor Network direttamente nella console Google Cloud utilizzando Spanner Studio. Questo è un ottimo modo per verificare i dati e comprendere la struttura del grafico prima di creare l'agente AI.

1. Accedere a Spanner Studio

  1. Nell'ultimo passaggio, assicurati di fare clic sul link e di aprire Spanner Studio.

spanner_studio

2. Comprendere la struttura del grafico (il quadro generale)

Considera il set di dati Survivor Network come un puzzle logico o uno stato del gioco:

Entità

Ruolo nel sistema

Analogia

Survivors

Gli agenti/giocatori

Giocatori

Biomi

Dove si trovano

Zone della mappa

Competenze

Cosa può fare

Funzionalità

Necessità

Cosa manca (crisi)

Quest/missioni

Risorse

Elementi trovati nel mondo

Loot

L'obiettivo: il compito dell'agente AI è quello di collegare le abilità (soluzioni) ai bisogni (problemi), tenendo conto dei biomi (vincoli di posizione).

🔗 Bordi (relazioni):

  • SurvivorInBiome: Monitoraggio della posizione
  • SurvivorHasSkill: Inventario delle funzionalità
  • SurvivorHasNeed: Elenco dei problemi attivi
  • SurvivorFoundResource: Inventario degli articoli
  • SurvivorCanHelp: Relazione dedotta (calcolata dall'AI)

3. Esecuzione di query sul grafico

Eseguiamo alcune query per visualizzare la "storia" nei dati.

Spanner Graph utilizza GQL (Graph Query Language). Per eseguire una query, utilizza GRAPH SurvivorNetwork seguito dal pattern di corrispondenza.

👉 Query 1: The Global Roster (Chi è dove?) Questa è la base: comprendere la posizione è fondamentale per le operazioni di soccorso.

GRAPH SurvivorNetwork
MATCH result = (s:Survivors)-[:SurvivorInBiome]->(b:Biomes)
RETURN TO_JSON(result) AS json_result

Dovresti visualizzare un risultato simile a questo: query1

👉 Query 2: la matrice delle competenze (capacità) Ora che sai dove si trova ogni persona, scopri cosa può fare.

GRAPH SurvivorNetwork
MATCH result = (s:Survivors)-[h:SurvivorHasSkill]->(k:Skills)
RETURN TO_JSON(result) AS json_result

Dovresti visualizzare un risultato simile a questo: query2

👉 Query 3: Who is in Crisis? (Chi è in crisi?) (La "Bacheca delle missioni") Vedi i sopravvissuti che hanno bisogno di aiuto e di cosa hanno bisogno.

GRAPH SurvivorNetwork
MATCH result = (s:Survivors)-[h:SurvivorHasNeed]->(n:Needs)
RETURN TO_JSON(result) AS json_result

Dovresti visualizzare un risultato simile a questo: query3

🔎 Avanzato: abbinamento: chi può aiutare chi?

È qui che il grafico diventa potente. Questa query trova sopravvissuti con competenze che possono soddisfare le esigenze di altri sopravvissuti.

GRAPH SurvivorNetwork
MATCH result = (helper:Survivors)-[:SurvivorHasSkill]->(skill:Skills)-[:SkillTreatsNeed]->(need:Needs)<-[:SurvivorHasNeed]-(helpee:Survivors)
RETURN TO_JSON(result) AS json_result

Dovresti visualizzare un risultato simile a questo: query4

aside positive Funzione di questa query:

Anziché mostrare solo "Il primo soccorso cura le ustioni" (ovvio dallo schema), questa query trova:

  • Dott. ssa Elena Frost (con formazione medica) → può curare → Capitano Tanaka (con ustioni)
  • David Chen (che ha il kit di pronto soccorso) → può curare → il tenente Park (che ha una distorsione alla caviglia)

Perché è importante:

Cosa farà il tuo agente AI:

Quando un utente chiede "Chi può curare le ustioni?", l'agente:

  1. Eseguire una query sul grafico simile
  2. Restituisci: "Il dottor Frost ha una formazione medica e può aiutare il capitano Tanaka"
  3. L'utente non ha bisogno di conoscere tabelle o relazioni intermedie.

5. Incorporamenti basati sull'AI in Spanner

1. Perché gli incorporamenti? (Nessuna azione, sola lettura)

Nello scenario di sopravvivenza, il tempo è fondamentale. Quando una persona sopravvissuta segnala un'emergenza, ad esempio I need someone who can treat burns o Looking for a medic, non può perdere tempo a indovinare i nomi esatti delle competenze nel database.

Scenario reale: Survivor: Captain Tanaka has burns—we need medical help NOW!

Ricerca tradizionale per parole chiave di "medico" → 0 risultati ❌

Ricerca semantica con incorporamenti → Trova "Medical Training", "First Aid" ✅

È esattamente ciò di cui hanno bisogno gli agenti: una ricerca intelligente e simile a quella umana che comprenda l'intento, non solo le parole chiave.

2. Crea modello di incorporamento

spanner_embedding

Ora creiamo un modello che converte il testo in incorporamenti utilizzando text-embedding-004 di Google.

👉 In Spanner Studio, esegui questo SQL (sostituisci $YOUR_PROJECT_ID con l'ID progetto effettivo):

‼️ Nell'editor di Cloud Shell, apri File -> Open Folder -> way-back-home/level_2 per visualizzare l'intero progetto.

project_id

👉 Esegui questa query in Spanner Studio copiandola e incollandola di seguito, quindi fai clic sul pulsante Esegui:

CREATE MODEL TextEmbeddings
INPUT(content STRING(MAX))
OUTPUT(embeddings STRUCT<values ARRAY<FLOAT32>>)
REMOTE OPTIONS (
    endpoint = '//aiplatform.googleapis.com/projects/$YOUR_PROJECT_ID/locations/us-central1/publishers/google/models/text-embedding-004'
);

Cosa fa:

  • Crea un modello virtuale in Spanner (nessun peso del modello archiviato localmente)
  • Punti all'text-embedding-004 di Google su Vertex AI
  • Definisce il contratto: l'input è testo, l'output è un array float a 768 dimensioni

Perché "OPZIONI REMOTO"?

  • Spanner non esegue il modello stesso
  • Chiama Vertex AI tramite API quando utilizzi ML.PREDICT
  • Zero-ETL: non è necessario esportare i dati in Python, elaborarli e reimportarli

Fai clic sul pulsante Run. Una volta completata l'operazione, puoi visualizzare il risultato come mostrato di seguito:

spanner_result

3. Aggiungere la colonna di incorporamento

👉 Aggiungi una colonna per archiviare gli embedding:

ALTER TABLE Skills ADD COLUMN skill_embedding ARRAY<FLOAT32>;

Fai clic sul pulsante Run. Una volta completata l'operazione, puoi visualizzare il risultato come mostrato di seguito:

embedding_result

4. Genera incorporamenti

👉 Utilizza l'AI per creare vector embedding per ogni competenza:

UPDATE Skills
SET skill_embedding = (
    SELECT embeddings.values
    FROM ML.PREDICT(
        MODEL TextEmbeddings,
        (SELECT name AS content)
    )
)
WHERE skill_embedding IS NULL;

Fai clic sul pulsante Run. Una volta completata l'operazione, puoi visualizzare il risultato come mostrato di seguito:

skills_result

Cosa succede: ogni nome di skill (ad es. "primo soccorso") viene convertito in un vettore a 768 dimensioni che ne rappresenta il significato semantico.

5. Verifica degli incorporamenti

👉 Controlla che siano stati creati gli incorporamenti:

SELECT 
    skill_id,
    name,
    ARRAY_LENGTH(skill_embedding) AS embedding_dimensions
FROM Skills
LIMIT 5;

Output previsto:

spanner_result

Ora testiamo il caso d'uso esatto del nostro scenario: trovare competenze mediche utilizzando il termine "medico".

👉 Trova competenze simili a "medico":

WITH query_embedding AS (
    SELECT embeddings.values AS val
    FROM ML.PREDICT(MODEL TextEmbeddings, (SELECT "medic" AS content))
)
SELECT
    s.name AS skill_name,
    s.category,
    COSINE_DISTANCE(s.skill_embedding, (SELECT val FROM query_embedding)) AS distance
FROM Skills AS s
WHERE s.skill_embedding IS NOT NULL
ORDER BY distance ASC
LIMIT 10;
  • Converte il termine di ricerca "medico " dell'utente in un embedding
  • Lo memorizza nella query_embedding tabella temporanea

Risultati previsti (distanza inferiore = maggiore somiglianza):

spanner_result

7. Crea modello Gemini per l'analisi

spanner_gemini

👉 Crea un riferimento al modello di AI generativa (sostituisci $YOUR_PROJECT_ID con l'ID progetto effettivo):

CREATE MODEL GeminiPro
INPUT(prompt STRING(MAX))
OUTPUT(content STRING(MAX))
REMOTE OPTIONS (
    endpoint = '//aiplatform.googleapis.com/projects/$YOUR_PROJECT_ID/locations/us-central1/publishers/google/models/gemini-2.5-pro',
    default_batch_size = 1
);

Differenza rispetto al modello di incorporamento:

  • Embedding: testo → vettore (per la ricerca di somiglianze)
  • Gemini: Testo → Testo generato (per ragionamento/analisi)

spanner_result

8. Utilizzare Gemini per l'analisi della compatibilità

👉 Analizza le coppie di sopravvissuti per la compatibilità con la missione:

WITH PairData AS (
    SELECT
        s1.name AS Name_A,
        s2.name AS Name_B,
        CONCAT(
            "Assess compatibility of these two survivors for a resource-gathering mission. ",
            "Survivor 1: ", s1.name, ". ",
            "Survivor 2: ", s2.name, ". ",
            "Give a score from 1-10 and a 1-sentence reason."
        ) AS prompt
    FROM Survivors s1
    JOIN Survivors s2 ON s1.survivor_id < s2.survivor_id
    LIMIT 1
)
SELECT
    Name_A,
    Name_B,
    content AS ai_assessment
FROM ML.PREDICT(
    MODEL GeminiPro,
    (SELECT Name_A, Name_B, prompt FROM PairData)
);

Output previsto:

Name_A          | Name_B            | ai_assessment
----------------|-------------------|----------------
"David Chen"    | "Dr. Elena Frost" | "**Score: 9/10** Their compatibility is extremely high as David's practical, hands-on scavenging skills are perfectly complemented by Dr. Frost's specialized knowledge to identify critical medical supplies and avoid biological hazards."

6. Creare l'agente RAG del grafico con la ricerca ibrida

1. Panoramica dell'architettura di sistema

Questa sezione crea un sistema di ricerca multimetodo che offre all'agente la flessibilità di gestire diversi tipi di query. Il sistema ha tre livelli: livello agente, livello strumento e livello servizio.

architecture_hybrid_search

Perché tre livelli?

  • Separazione delle competenze: l'agente si concentra sull'intent, gli strumenti sull'interfaccia e il servizio sull'implementazione
  • Flessibilità: l'agente può forzare metodi specifici o lasciare che l'AI esegua il routing automatico
  • Ottimizzazione: può saltare l'analisi AI costosa quando il metodo è noto

In questa sezione, implementerai principalmente la ricerca semantica (RAG), ovvero la ricerca di risultati in base al significato, non solo alle parole chiave. Più avanti spiegheremo come la ricerca ibrida unisce più metodi.

2. Implementazione del servizio RAG

👉💻 Nel terminale, apri il file nell'editor di Cloud Shell eseguendo:

cloudshell edit ~/way-back-home/level_2/backend/services/hybrid_search_service.py

Individua il commento # TODO: REPLACE_SQL

Sostituisci l'intera riga con il seguente codice:

        # This is your working query from the successful run!
        sql = """
            WITH query_embedding AS (
                SELECT embeddings.values AS val
                FROM ML.PREDICT(
                    MODEL TextEmbeddings,
                    (SELECT @query AS content)
                )
            )
            SELECT
                s.survivor_id,
                s.name AS survivor_name,
                s.biome,
                sk.skill_id,
                sk.name AS skill_name,
                sk.category,
                COSINE_DISTANCE(
                    sk.skill_embedding, 
                    (SELECT val FROM query_embedding)
                ) AS distance
            FROM Survivors s
            JOIN SurvivorHasSkill shs ON s.survivor_id = shs.survivor_id
            JOIN Skills sk ON shs.skill_id = sk.skill_id
            WHERE sk.skill_embedding IS NOT NULL
            ORDER BY distance ASC
            LIMIT @limit
        """

3. Definizione dello strumento di ricerca semantica

👉💻 Nel terminale, apri il file nell'editor di Cloud Shell eseguendo:

cloudshell edit ~/way-back-home/level_2/backend/agent/tools/hybrid_search_tools.py

In hybrid_search_tools.py, individua il commento # TODO: REPLACE_SEMANTIC_SEARCH_TOOL

👉Sostituisci l'intera riga con il seguente codice:

async def semantic_search(query: str, limit: int = 10) -> str:
    """
    Force semantic (RAG) search using embeddings.
    
    Use this when you specifically want to find things by MEANING,
    not just matching keywords. Great for:
    - Finding conceptually similar items
    - Handling vague or abstract queries
    - When exact terms are unknown
    
    Example: "healing abilities" will find "first aid", "surgery", 
    "herbalism" even though no keywords match exactly.
    
    Args:
        query: What you're looking for (describe the concept)
        limit: Maximum results
        
    Returns:
        Semantically similar results ranked by relevance
    """
    try:
        service = _get_service()
        result = service.smart_search(
            query, 
            force_method=SearchMethod.RAG,
            limit=limit
        )
        
        return _format_results(
            result["results"],
            result["analysis"],
            show_analysis=True
        )
        
    except Exception as e:
        return f"Error in semantic search: {str(e)}"

Quando l'agente utilizza:

  • Query che chiedono similarità ("trova contenuti simili a X")
  • Query concettuali ("capacità di guarigione")
  • Quando comprendere il significato è fondamentale

4. Guida alle decisioni dell'agente (istruzioni)

Nella definizione dell'agente, copia e incolla la parte relativa alla ricerca semantica nelle istruzioni.

👉💻 Nel terminale, apri il file nell'editor di Cloud Shell eseguendo:

cloudshell edit ~/way-back-home/level_2/backend/agent/agent.py

L'agente utilizza questa istruzione per selezionare lo strumento giusto:

👉 Nel file agent.py, individua il commento # TODO: REPLACE_SEARCH_LOGIC, sostituisci l'intera riga con il seguente codice:

- `semantic_search`: Force RAG/embedding search
  Use for: "Find similar to X", conceptual queries, unknown terminology
  Example: "Find skills related to healing"

👉 Individua il commento # TODO: ADD_SEARCH_TOOLReplace this whole line e sostituisci l'intera riga con il seguente codice:

    semantic_search,         # Force RAG

5. Informazioni sul funzionamento della ricerca ibrida (sola lettura, nessuna azione necessaria)

Nei passaggi 2-4, hai implementato la ricerca semantica (RAG), il metodo di ricerca principale che trova i risultati in base al significato. Tuttavia, potresti aver notato che il sistema si chiama "Ricerca ibrida". Ecco come si combinano tutti questi elementi:

Come funziona l'unione ibrida:

Nel file way-back-home/level_2/backend/services/hybrid_search_service.py, quando viene chiamato hybrid_search(), il servizio esegue ENTRAMBE le ricerche e unisce i risultati:

# Location: backend/services/hybrid_search_service.py

    rank_kw = keyword_ranks.get(surv_id, float('inf'))
    rank_rag = rag_ranks.get(surv_id, float('inf'))

    rrf_score = 0.0
    if rank_kw != float('inf'):
        rrf_score += 1.0 / (K + rank_kw)
    if rank_rag != float('inf'):
        rrf_score += 1.0 / (K + rank_rag)

    combined_score = rrf_score

Per questo codelab, hai implementato il componente di ricerca semantica (RAG), che è la base. I metodi basati su parole chiave e ibridi sono già implementati nel servizio, quindi il tuo agente può utilizzarli tutti e tre.

Complimenti! Hai completato l'agente Graph RAG con la ricerca ibrida.

7. Testare l'agente con ADK Web

Il modo più semplice per testare l'agente è utilizzare il comando adk web, che lo avvia con un'interfaccia di chat integrata.

1. Esecuzione dell'agente

👉💻 Vai alla directory di backend (dove è definito l'agente) e avvia l'interfaccia web:

cd ~/way-back-home/level_2/backend
uv run adk web

Questo comando avvia l'agente definito in

agent/agent.py

e si apre un'interfaccia web per il test.

👉 Apri l'URL:

Il comando restituirà un URL locale (di solito http://127.0.0.1:8000 o simile). Apri questo link nel browser.

adk web

Dopo aver fatto clic sull'URL, vedrai la GUI web dell'ADK. Assicurati di selezionare l'agente nell'angolo in alto a sinistra.

adk_ui

2. Testare le funzionalità di ricerca

L'agente è progettato per indirizzare in modo intelligente le tue query. Prova i seguenti input nella finestra della chat per vedere diversi metodi di ricerca in azione.

Trova elementi in base al significato e al concetto, anche se le parole chiave non corrispondono.

Query di test: (scegli una delle seguenti)

Who can help with injuries?
What abilities are related to survival?

Cosa cercare:

  • Il ragionamento deve menzionare la ricerca semantica o RAG.
  • Dovresti vedere risultati concettualmente correlati (ad es. "Surgery" (intervento chirurgico) quando chiedi "First Aid" (pronto soccorso).
  • I risultati avranno l'icona 🧬.

Combina i filtri per parole chiave con la comprensione semantica per query complesse.

Query di test:(scegli una delle seguenti)

Find someone who can ply a plane in the volcanic area
Who has healing abilities in the FOSSILIZED?
Who has healing abilities in the mountains?

Cosa cercare:

  • Il ragionamento deve menzionare la ricerca ibrida.
  • I risultati devono corrispondere a ENTRAMBI i criteri (concetto + località/categoria).
  • I risultati trovati con entrambi i metodi avranno l'icona 🔀 e si troveranno in cima alla classifica.

👉💻 Al termine del test, termina la procedura premendo Ctrl+C nella riga di comando.

8. Esecuzione dell'applicazione completa

Panoramica dell'architettura full-stack

architecture_fullstack

Aggiungere SessionService e Runner

👉💻 Nel terminale, apri il file chat.py nell'editor di Cloud Shell eseguendo (assicurati di aver premuto "Ctrl+C" per terminare il processo precedente prima di procedere):

cloudshell edit ~/way-back-home/level_2/backend/api/routes/chat.py

👉 Nel file chat.py, individua il commento # TODO: REPLACE_INMEMORY_SERVICES, sostituisci l'intera riga con il seguente codice:

    session_service = InMemorySessionService()
    memory_service = InMemoryMemoryService()

👉 Nel file chat.py, individua il commento # TODO: REPLACE_RUNNER, sostituisci l'intera riga con il seguente codice:

runner = Runner(
    agent=root_agent, 
    session_service=session_service,
    memory_service=memory_service,
    app_name="survivor-network"
)

1. Avvia la domanda

Se il terminale precedente è ancora in esecuzione, termina l'operazione premendo Ctrl+C.

👉💻 Avvia app:

cd ~/way-back-home/level_2/
./start_app.sh

Quando il backend viene avviato correttamente, vedrai Local: http://localhost:5173/" come mostrato di seguito: fronted

👉 Fai clic su Local: http://localhost:5173/ dal terminale.

conversazione

Query:

Find skills similar to healing

chat

Cosa succede:

  • L'agente riconosce la richiesta di similarità
  • Genera l'embedding per "guarigione"
  • Utilizza la distanza del coseno per trovare competenze semanticamente simili
  • Resi: pronto soccorso (anche se i nomi non corrispondono a "guarigione")

Query:

Find medical skills in the mountains

Cosa succede:

  1. Componente parola chiave: filtra per category='medical'
  2. Componente semantico: incorpora "medico" e classifica in base alla somiglianza
  3. Unisci: combina i risultati, dando la priorità a quelli trovati con entrambi i metodi 🔀

Query(facoltativa):

Who is good at survival and in the forest?

Cosa succede:

  • Risultati per parola chiave: biome='forest'
  • Risultati semantici: competenze simili a "sopravvivenza"
  • La combinazione ibrida offre i risultati migliori

👉💻 Al termine del test, nel terminale, termina premendo Ctrl+C.

9. Pipeline multimodale - Livello di strumenti

Perché abbiamo bisogno di una pipeline multimodale?

La rete di sopravvivenza non è solo testo. I sopravvissuti sul campo inviano dati non strutturati direttamente tramite la chat:

  • 📸 Immagini: foto di risorse, pericoli o attrezzature
  • 🎥 Video: report sullo stato o trasmissioni SOS
  • 📄 Testo: note o log del campo

Quali file elaboriamo?

A differenza del passaggio precedente, in cui abbiamo cercato i dati esistenti, qui elaboriamo i file caricati dagli utenti. L'interfaccia chat.py gestisce gli allegati dei file in modo dinamico:

Origine

Contenuti

Obiettivo

User Attachment

Immagine/video/testo

Informazioni da aggiungere al grafico

Contesto della chat

"Ecco una foto delle forniture"

Intento e dettagli aggiuntivi

L'approccio pianificato: pipeline di agenti sequenziale

Utilizziamo un agente sequenziale (multimedia_agent.py) che collega insieme agenti specializzati:

architecture_uploading

Questo valore è definito in backend/agent/multimedia_agent.py come SequentialAgent.

Il livello degli strumenti fornisce le funzionalità che gli agenti possono richiamare. Gli strumenti gestiscono il "come": caricamento dei file, estrazione delle entità e salvataggio nel database.

1. Apri il file degli strumenti

👉💻 Apri un nuovo terminale. Nel terminale, apri il file nell'editor di Cloud Shell:

cloudshell edit ~/way-back-home/level_2/backend/agent/tools/extraction_tools.py

2. Implementa lo strumento upload_media

Questo strumento carica un file locale su Google Cloud Storage.

👉 In extraction_tools.py, individua il commento pass # TODO: REPLACE_UPLOAD_MEDIA_FUNCTION.

Sostituisci l'intera riga con il seguente codice:

    """
    Upload media file to GCS and detect its type.
    
    Args:
        file_path: Path to the local file
        survivor_id: Optional survivor ID to associate with upload
        
    Returns:
        Dict with gcs_uri, media_type, and status
    """
    try:
        if not file_path:
            return {"status": "error", "error": "No file path provided"}
        
        # Strip quotes if present
        file_path = file_path.strip().strip("'").strip('"')
        
        if not os.path.exists(file_path):
            return {"status": "error", "error": f"File not found: {file_path}"}
        
        gcs_uri, media_type, signed_url = gcs_service.upload_file(file_path, survivor_id)
        
        return {
            "status": "success",
            "gcs_uri": gcs_uri,
            "signed_url": signed_url,
            "media_type": media_type.value,
            "file_name": os.path.basename(file_path),
            "survivor_id": survivor_id
        }
    except Exception as e:
        logger.error(f"Upload failed: {e}")
        return {"status": "error", "error": str(e)}

3. Implementa lo strumento extract_from_media

Questo strumento è un router: controlla media_type e invia all'estrattore corretto (testo, immagine o video).

👉 In extraction_tools.py, individua il commento pass # TODO: REPLACE_EXTRACT_FROM_MEDIA.

Sostituisci l'intera riga con il seguente codice:

    """
    Extract entities and relationships from uploaded media.
    
    Args:
        gcs_uri: GCS URI of the uploaded file
        media_type: Type of media (text/image/video)
        signed_url: Optional signed URL for public/temporary access
        
    Returns:
        Dict with extraction results
    """
    try:
        if not gcs_uri:
             return {"status": "error", "error": "No GCS URI provided"}

        # Select appropriate extractor
        if media_type == MediaType.TEXT.value or media_type == "text":
            result = await text_extractor.extract(gcs_uri)
        elif media_type == MediaType.IMAGE.value or media_type == "image":
            result = await image_extractor.extract(gcs_uri)
        elif media_type == MediaType.VIDEO.value or media_type == "video":
            result = await video_extractor.extract(gcs_uri)
        else:
            return {"status": "error", "error": f"Unsupported media type: {media_type}"}
            
        # Inject signed URL into broadcast info if present
        if signed_url:
            if not result.broadcast_info:
                result.broadcast_info = {}
            result.broadcast_info['thumbnail_url'] = signed_url
        
        return {
            "status": "success",
            "extraction_result": result.to_dict(), # Return valid JSON dict instead of object
            "summary": result.summary,
            "entities_count": len(result.entities),
            "relationships_count": len(result.relationships),
            "entities": [e.to_dict() for e in result.entities],
            "relationships": [r.to_dict() for r in result.relationships]
        }
    except Exception as e:
        logger.error(f"Extraction failed: {e}")
        return {"status": "error", "error": str(e)}

Dettagli chiave dell'implementazione:

  • Input multimodale: trasmettiamo sia il prompt di testo (_get_extraction_prompt()) sia l'oggetto immagine a generate_content.
  • Output strutturato: response_mime_type="application/json" garantisce che l'LLM restituisca un JSON valido, fondamentale per la pipeline.
  • Collegamento visivo delle entità: il prompt include entità note, in modo che Gemini possa riconoscere personaggi specifici.

4. Implementa lo strumento save_to_spanner

Questo strumento salva le entità e le relazioni estratte nel database Spanner Graph.

👉 In extraction_tools.py, individua il commento pass # TODO: REPLACE_SPANNER_AGENT.

Sostituisci l'intera riga con il seguente codice:

    """
    Save extracted entities and relationships to Spanner Graph DB.
    
    Args:
        extraction_result: ExtractionResult object (or dict from previous step if passed as dict)
        survivor_id: Optional survivor ID to associate with the broadcast
        
    Returns:
        Dict with save statistics
    """
    try:
        # Handle if extraction_result is passed as the wrapper dict from extract_from_media
        result_obj = extraction_result
        if isinstance(extraction_result, dict) and 'extraction_result' in extraction_result:
             result_obj = extraction_result['extraction_result']
        
        # If result_obj is a dict (from to_dict()), reconstruct it
        if isinstance(result_obj, dict):
            from extractors.base_extractor import ExtractionResult
            result_obj = ExtractionResult.from_dict(result_obj)
        
        if not result_obj:
            return {"status": "error", "error": "No extraction result provided"}
            
        stats = spanner_service.save_extraction_result(result_obj, survivor_id)
        
        return {
            "status": "success",
            "entities_created": stats['entities_created'],
            "entities_existing": stats['entities_found_existing'],
            "relationships_created": stats['relationships_created'],
            "broadcast_id": stats['broadcast_id'],
            "errors": stats['errors'] if stats['errors'] else None
        }
    except Exception as e:
        logger.error(f"Spanner save failed: {e}")
        return {"status": "error", "error": str(e)}

Fornendo agli agenti strumenti di alto livello, garantiamo l'integrità dei dati sfruttando le capacità di ragionamento dell'agente.

5. Aggiornare il servizio GCS

GCSService gestisce il caricamento effettivo del file su Google Cloud Storage.

👉💻 Nel terminale, apri il file nell'editor di Cloud Shell:

cloudshell edit ~/way-back-home/level_2/backend/services/gcs_service.py

👉 Nel file gcs_service.py, individua il commento # TODO: REPLACE_SAVE_TO_GCS all'interno della funzione upload_file.

Sostituisci l'intera riga con il seguente codice:

        blob = self.bucket.blob(blob_name)
        blob.upload_from_filename(file_path)

Se viene eseguita l'astrazione in un servizio, l'agente non deve conoscere i bucket GCS, i nomi dei blob o la generazione di URL firmati. Chiede solo di "caricare".

6. (Sola lettura) Perché il flusso di lavoro con agenti > approcci tradizionali?

Il vantaggio dell'agente:

Funzionalità

Pipeline batch

Basato sugli eventi

Agentic Workflow

complessità

Bassa (1 script)

Alto (più di 5 servizi)

Basso (1 file Python: multimedia_agent.py)

Gestione dello stato

Variabili globali

Hard (disaccoppiato)

Unificato (stato dell'agente)

Gestione degli errori

Arresti anomali

Log silenziosi

Interattivo ("Non riesco a leggere il file")

Feedback degli utenti

Stampe della console

Necessità di polling

Immediato (parte della chat)

Adattabilità

Logica fissa

Funzioni rigide

Intelligente (il LLM decide il passaggio successivo)

Context Awareness

Nessuno

Nessuno

Completa (conosce l'intent dell'utente)

Perché è importante:utilizzando multimedia_agent.py (un SequentialAgent con 4 sub-agent: Upload → Extract → Save → Summary), sostituiamo l'infrastruttura complessa E gli script fragili con una logica applicativa intelligente e conversazionale.

10. Pipeline multimodale - Livello dell'agente

Il livello degli agenti definisce l'intelligenza, ovvero gli agenti che utilizzano strumenti per svolgere attività. Ogni agente ha un ruolo specifico e passa il contesto al successivo. Di seguito è riportato il diagramma dell'architettura del sistema multiagente.

agent_diagram

1. Apri il file dell'agente

👉💻 Nel terminale, apri il file nell'editor di Cloud Shell:

cloudshell edit ~/way-back-home/level_2/backend/agent/multimedia_agent.py

2. Definisci l'agente di caricamento

Questo agente estrae un percorso del file dal messaggio dell'utente e lo carica su GCS.

👉 Nel file multimedia_agent.py, individua il commento # TODO: REPLACE_UPLOAD_AGENT.

Sostituisci l'intera riga con il seguente codice:

upload_agent = LlmAgent(
    name="UploadAgent",
    model="gemini-2.5-flash",
    instruction="""Extract the file path from the user's message and upload it.

Use `upload_media(file_path, survivor_id)` to upload the file.
The survivor_id is optional - include it if the user mentions a specific survivor (e.g., "survivor Sarah" -> "Sarah").
If the user provides a path like "/path/to/file", use that.

Return the upload result with gcs_uri and media_type.""",
    tools=[upload_media],
    output_key="upload_result"
)

3. Definisci l'agente di estrazione

Questo agente "vede" i contenuti multimediali caricati ed estrae i dati strutturati utilizzando Gemini Vision.

👉 Nel file multimedia_agent.py, individua il commento # TODO: REPLACE_EXTRACT_AGENT.

Sostituisci l'intera riga con il seguente codice:

extraction_agent = LlmAgent(
    name="ExtractionAgent", 
    model="gemini-2.5-flash",
    instruction="""Extract information from the uploaded media.

Previous step result: {upload_result}

Use `extract_from_media(gcs_uri, media_type, signed_url)` with the values from the upload result.
The gcs_uri is in upload_result['gcs_uri'], media_type in upload_result['media_type'], and signed_url in upload_result['signed_url'].

Return the extraction results including entities and relationships found.""",
    tools=[extract_from_media],
    output_key="extraction_result"
)

Nota come instruction faccia riferimento a {upload_result}: in questo modo lo stato viene trasmesso tra gli agenti in ADK.

4. Definisci l'agente Spanner

Questo agente salva le entità e le relazioni estratte nel database del grafico.

👉 Nel file multimedia_agent.py, individua il commento # TODO: REPLACE_SPANNER_AGENT.

Sostituisci l'intera riga con il seguente codice:

spanner_agent = LlmAgent(
    name="SpannerAgent",
    model="gemini-2.5-flash", 
    instruction="""Save the extracted information to the database.

Upload result: {upload_result}
Extraction result: {extraction_result}

Use `save_to_spanner(extraction_result, survivor_id)` to save to Spanner.
Pass the WHOLE `extraction_result` object/dict from the previous step.
Include survivor_id if it was provided in the upload step.

Return the save statistics.""",
    tools=[save_to_spanner],
    output_key="spanner_result"
)

Questo agente riceve il contesto da entrambi i passaggi precedenti (upload_result e extraction_result).

5. Definisci l'agente di riepilogo

Questo agente sintetizza i risultati di tutti i passaggi precedenti in una risposta di facile utilizzo.

👉 Nel file multimedia_agent.py, individua il commento summary_instruction="" # TODO: REPLACE_SUMMARY_AGENT_PROMPT.

Sostituisci l'intera riga con il seguente codice:

USE_MEMORY_BANK = os.getenv("USE_MEMORY_BANK", "false").lower() == "true"
save_msg = "6. Mention that the data is also being synced to the memory bank." if USE_MEMORY_BANK else ""

summary_instruction = f"""Provide a user-friendly summary of the media processing.

Upload: {{upload_result}}
Extraction: {{extraction_result}}
Database: {{spanner_result}}

Summarize:
1. What file was processed (name and type)
2. Key information extracted (survivors, skills, needs, resources found) - list names and counts
3. Relationships identified
4. What was saved to the database (broadcast ID, number of entities)
5. Any issues encountered
{save_msg}

Be concise but informative."""

Questo agente non ha bisogno di strumenti: legge il contesto condiviso e genera un riepilogo pulito per l'utente.

🧠 Riepilogo dell'architettura

incorporato

File

Responsabilità

Strumenti

extraction_tools.py + gcs_service.py

Come: carica, estrai, salva

Agent

multimedia_agent.py

Cosa: orchestra la pipeline

11. Pipeline di dati multimodale - Orchestrazione

Il fulcro del nostro nuovo sistema è il MultimediaExtractionPipeline definito in backend/agent/multimedia_agent.py. Utilizza il pattern Sequential Agent di ADK (Agent Development Kit).

1. Perché Sequential?

L'elaborazione di un caricamento è una catena di dipendenze lineare:

  1. Non puoi estrarre i dati finché non hai il file (caricamento).
  2. Non puoi salvare i dati finché non li estrai (estrazione).
  3. Non puoi riassumere finché non hai i risultati (Salva).

Un SequentialAgent è perfetto per questo. Passa l'output di un agente come contesto/input al successivo.

2. Definizione dell'agente

Vediamo come viene assemblata la pipeline nella parte inferiore di multimedia_agent.py: 👉💻 Nel terminale, apri il file nell'editor di Cloud Shell eseguendo:

cloudshell edit ~/way-back-home/level_2/backend/agent/multimedia_agent.py

Riceve input da entrambi i passaggi precedenti. Individua il commento # TODO: REPLACE_ORCHESTRATION. Sostituisci l'intera riga con il seguente codice:

    sub_agents=[upload_agent, extraction_agent, spanner_agent, summary_agent]

3. Contatta l'agente principale

👉💻 Nel terminale, apri il file nell'editor di Cloud Shell eseguendo:

cloudshell edit ~/way-back-home/level_2/backend/agent/agent.py

Individua il commento # TODO: REPLACE_ADD_SUBAGENT. Sostituisci l'intera riga con il seguente codice:

    sub_agents=[multimedia_agent],

Questo singolo oggetto raggruppa in modo efficace quattro "esperti" in un'unica entità chiamabile.

4. Flusso di dati tra gli agenti

Ogni agente memorizza il proprio output in un contesto condiviso a cui possono accedere gli agenti successivi:

architecture_uploading

5. Apri l'applicazione (salta questo passaggio se l'app è ancora in esecuzione)

👉💻 Avvia app:

cd ~/way-back-home/level_2/
./start_app.sh

👉 Fai clic su Local: http://localhost:5173/ dal terminale.

6. Caricamento di prova dell'immagine

👉 Nell'interfaccia della chat, scegli una delle foto qui e caricala nella UI:

Nell'interfaccia di chat, comunica all'agente il tuo contesto specifico:

Here is the survivor note

Poi allega l'immagine qui.

upload_input

upload_result

👉💻 Nel terminale, al termine del test, premi "Ctrl+C" per terminare il processo.

6. Verifica il caricamento multimodale nel bucket GCS

gcs

  • Seleziona il bucket e fai clic su media.

media

  • Visualizza l'immagine caricata qui. uploaded_img

7. (Facoltativo) Verifica il caricamento multimodale in Spanner

Di seguito è riportato un output di esempio nell'interfaccia utente per test_photo1.

  • Apri Google Cloud Console Spanner.
  • Seleziona la tua istanza: Survivor Network
  • Seleziona il database: graph-db
  • Nella barra laterale sinistra, fai clic su Spanner Studio.

👉 In Spanner Studio, esegui una query sui nuovi dati:

SELECT 
  s.name AS Survivor,
  s.role AS Role,
  b.name AS Biome,
  r.name AS FoundResource,
  s.created_at
FROM Survivors s
LEFT JOIN SurvivorInBiome sib ON s.survivor_id = sib.survivor_id
LEFT JOIN Biomes b ON sib.biome_id = b.biome_id
LEFT JOIN SurvivorFoundResource sfr ON s.survivor_id = sfr.survivor_id
LEFT JOIN Resources r ON sfr.resource_id = r.resource_id
ORDER BY s.created_at DESC;

Possiamo verificarlo visualizzando il risultato di seguito:

spanner_verify

12. Memory Bank con Agent Engine

1. Come funziona la memoria

Il sistema utilizza un approccio a doppia memoria per gestire sia il contesto immediato sia l'apprendimento a lungo termine.

memory_bank

2. Che cosa sono gli argomenti dei ricordi?

Gli argomenti della memoria definiscono le categorie di informazioni che l'agente deve ricordare nelle conversazioni. Considerali come schedari per diversi tipi di preferenze degli utenti.

I nostri due argomenti:

  1. search_preferences: come l'utente preferisce effettuare le ricerche
    • Preferiscono la ricerca semantica o per parole chiave?
    • Quali competenze/biomi cercano spesso?
    • Esempio di memoria: "L'utente preferisce la ricerca semantica per le competenze mediche"
  2. urgent_needs_context: Quali crisi sta monitorando
    • Quali risorse monitorano?
    • Quali sopravvissuti preoccupano l'organizzazione?
    • Esempio di memoria: "L'utente sta monitorando la carenza di farmaci nel campo settentrionale"

3. Configurare gli argomenti dei ricordi

Gli argomenti della memoria personalizzata definiscono cosa deve ricordare l'agente. Questi vengono configurati durante il deployment di Agent Engine.

👉💻 Nel terminale, apri il file nell'editor di Cloud Shell eseguendo:

cloudshell edit ~/way-back-home/level_2/backend/deploy_agent.py

Si apre ~/way-back-home/level_2/backend/deploy_agent.py nell'editor.

Definiamo gli oggetti della struttura MemoryTopic per indicare all'LLM quali informazioni estrarre e salvare.

👉 Nel file deploy_agent.py, sostituisci # TODO: SET_UP_TOPIC con quanto segue:

# backend/deploy_agent.py

    custom_topics = [
        # Topic 1: Survivor Search Preferences
        MemoryTopic(
            custom_memory_topic=CustomMemoryTopic(
                label="search_preferences",
                description="""Extract the user's preferences for how they search for survivors. Include:
                - Preferred search methods (keyword, semantic, direct lookup)
                - Common filters used (biome, role, status)
                - Specific skills they value or frequently look for
                - Geographic areas of interest (e.g., "forest biome", "mountain outpost")
                
                Example: "User prefers semantic search for finding similar skills."
                Example: "User frequently checks for survivors in the Swamp Biome."
                """,
            )
        ),
        # Topic 2: Urgent Needs Context
        MemoryTopic(
            custom_memory_topic=CustomMemoryTopic(
                label="urgent_needs_context",
                description="""Track the user's focus on urgent needs and resource shortages. Include:
                - Specific resources they are monitoring (food, medicine, ammo)
                - Critical situations they are tracking
                - Survivors they are particularly concerned about
                
                Example: "User is monitoring the medicine shortage in the Northern Camp."
                Example: "User is looking for a doctor for the injured survivors."
                """,
            )
        )
    ]

4. Integrazione dell'agente

Il codice dell'agente deve conoscere la banca della memoria per salvare e recuperare le informazioni.

👉💻 Nel terminale, apri il file nell'editor di Cloud Shell eseguendo:

cloudshell edit ~/way-back-home/level_2/backend/agent/agent.py

Si apre ~/way-back-home/level_2/backend/agent/agent.py nell'editor.

Creazione dell'agente

Quando creiamo l'agente, passiamo after_agent_callback per assicurarci che le sessioni vengano salvate in memoria dopo le interazioni. La funzione add_session_to_memory viene eseguita in modo asincrono per evitare di rallentare la risposta della chat.

👉 Nel file agent.py, individua il commento # TODO: REPLACE_ADD_SESSION_MEMORY, sostituisci l'intera riga con il seguente codice:

async def add_session_to_memory(
        callback_context: CallbackContext
) -> Optional[types.Content]:
    """Automatically save completed sessions to memory bank in the background"""
    if hasattr(callback_context, "_invocation_context"):
        invocation_context = callback_context._invocation_context
        if invocation_context.memory_service:
            # Use create_task to run this in the background without blocking the response
            asyncio.create_task(
                invocation_context.memory_service.add_session_to_memory(
                    invocation_context.session
                )
            )
            logger.info("Scheduled session save to memory bank in background")

Salvataggio in background

👉 Nel file agent.py, individua il commento # TODO: REPLACE_ADD_MEMORY_BANK_TOOL, sostituisci l'intera riga con il seguente codice:

if USE_MEMORY_BANK:
    agent_tools.append(PreloadMemoryTool())

👉 Nel file agent.py, individua il commento # TODO: REPLACE_ADD_CALLBACK, sostituisci l'intera riga con il seguente codice:

    after_agent_callback=add_session_to_memory if USE_MEMORY_BANK else None

Configura il servizio di sessione Vertex AI

👉💻 Nel terminale, apri il file chat.py nell'editor di Cloud Shell eseguendo:

cloudshell edit ~/way-back-home/level_2/backend/api/routes/chat.py

👉 Nel file chat.py, individua il commento # TODO: REPLACE_VERTEXAI_SERVICES, sostituisci l'intera riga con il seguente codice:

    session_service = VertexAiSessionService(
        project=project_id,
        location=location,
        agent_engine_id=agent_engine_id
    )
    memory_service = VertexAiMemoryBankService(
        project=project_id,
        location=location,
        agent_engine_id=agent_engine_id
    )

4. Configurazione e deployment

Prima di testare le funzionalità di memoria, devi eseguire il deployment dell'agente con i nuovi argomenti della memoria e assicurarti che l'ambiente sia configurato correttamente.

Abbiamo fornito uno script di utilità per gestire questa procedura.

Esecuzione dello script di deployment

👉💻 Nel terminale, esegui lo script di deployment:

cd ~/way-back-home/level_2
./deploy_and_update_env.sh

Questo script esegue le seguenti azioni:

  • Esegue backend/deploy_agent.py per registrare l'agente e gli argomenti della memoria con Vertex AI.
  • Acquisisce il nuovo ID Agent Engine.
  • Aggiorna automaticamente il file .env con AGENT_ENGINE_ID.
  • Assicura che USE_MEMORY_BANK=TRUE sia impostato nel file .env.

[!IMPORTANT] Se apporti modifiche a custom_topics in deploy_agent.py, devi eseguire di nuovo questo script per aggiornare Agent Engine.

13. Verificare la banca della memoria con dati multimodali

Puoi verificare che la banca della memoria funzioni insegnando all'agente una preferenza e controllando se persiste nelle varie sessioni.

1. Apri l'applicazione (salta questo passaggio se l'applicazione è già in esecuzione)

Apri di nuovo l'applicazione seguendo le istruzioni riportate di seguito: se il terminale precedente è ancora in esecuzione, termina premendo Ctrls+C.

👉💻 Avvia app:

cd ~/way-back-home/level_2/
./start_app.sh

👉 Fai clic su Local: http://localhost:5173/ dal terminale.

2. Test di Memory Bank con testo

Nell'interfaccia di chat, comunica all'agente il tuo contesto specifico:

"I'm planning a medical rescue mission in the mountains. I need survivors with first aid and climbing skills."

👉 Attendi circa 30 secondi affinché la memoria venga elaborata in background.

2. Avviare una nuova sessione

Aggiorna la pagina per cancellare la cronologia della conversazione corrente (memoria a breve termine).

Fai una domanda che si basa sul contesto che hai fornito in precedenza:

"What kind of missions am I interested in?"

Risposta prevista:

"In base alle tue conversazioni precedenti, ti interessano:

  • Missioni di soccorso medico
  • Operazioni in montagna/ad alta quota
  • Abilità necessarie: primo soccorso, arrampicata

Vuoi che cerchi sopravvissuti che corrispondono a questi criteri?"

3. Test con il caricamento delle immagini

Carica un'immagine e chiedi:

remember this

Puoi scegliere una delle foto qui o una tua e caricarla nella UI:

4. Verifica in Vertex AI Agent Engine

Vai a Google Cloud Console Agent Engine

  1. Assicurati di selezionare il progetto dal selettore di progetti in alto a sinistra:selettore di progetti
  2. Verifica il motore dell'agente di cui hai appena eseguito il deployment dal comando precedente use_memory_bank.sh:motore agente fai clic sul motore dell'agente che hai appena creato.
  3. Fai clic sulla scheda Memories in questo agente di cui è stato eseguito il deployment per visualizzare tutte le memorie.visualizzare un ricordo

👉💻 Al termine del test, nel terminale fai clic su "Ctrl + C" per terminare la procedura.

🎉 Complimenti! Hai appena collegato la banca della memoria al tuo agente.

14. Esegui il deployment in Cloud Run

1. Esegui lo script di deployment

👉💻 Esegui lo script di deployment:

cd ~/way-back-home/level_2
./deploy_cloud_run.sh

Una volta eseguito il deployment, avrai l'URL. Questo è l'URL di deployment. deployment eseguito

👉💻 Prima di prendere l'URL, concedi l'autorizzazione eseguendo:

source .env && gcloud run services add-iam-policy-binding survivor-frontend --region $REGION --member=allUsers --role=roles/run.invoker && gcloud run services add-iam-policy-binding survivor-backend --region $REGION --member=allUsers --role=roles/run.invoker

Vai all'URL di cui hai eseguito il deployment e vedrai la tua applicazione live.

2. Informazioni sulla pipeline di build

Il file cloudbuild.yaml definisce i seguenti passaggi sequenziali:

  1. Backend Build: crea l'immagine Docker da backend/Dockerfile.
  2. Backend Deploy: esegue il deployment del container di backend in Cloud Run.
  3. Acquisisci URL: recupera il nuovo URL backend.
  4. Frontend Build:
    • Installa le dipendenze.
    • Crea la build dell'app React, inserendo VITE_API_URL=.
  5. Immagine frontend: crea l'immagine Docker da frontend/Dockerfile (pacchettizzazione degli asset statici).
  6. Frontend Deploy: esegue il deployment del container frontend.

3. Verifica il deployment

Una volta completata la build (controlla il link ai log fornito dallo script), puoi verificare:

  1. Vai alla console Cloud Run.
  2. Trova il servizio survivor-frontend.
  3. Fai clic sull'URL per aprire l'applicazione.
  4. Esegui una query di ricerca per assicurarti che il frontend possa comunicare con il backend.

4. (!SOLO PER I PARTECIPANTI AL WORKSHOP) Aggiornare la posizione

👉💻 Esegui lo script di completamento:

cd ~/way-back-home/level_2
./set_level_2.sh

Ora apri waybackhome.dev e vedrai che la tua posizione è stata aggiornata. Congratulazioni per aver completato il livello 2.

risultato finale

(FACOLTATIVO) 5. Deployment manuale

Se preferisci eseguire i comandi manualmente o comprendere meglio la procedura, ecco come utilizzare cloudbuild.yaml direttamente.

Scrittura di cloudbuild.yaml

Un file cloudbuild.yaml indica a Google Cloud Build quali passaggi eseguire.

  • passaggi: un elenco di azioni sequenziali. Ogni passaggio viene eseguito in un container (ad es. docker, gcloud, node, bash).
  • sostituzioni: variabili che possono essere trasmesse al momento della creazione (ad es. $_REGION).
  • workspace: una directory condivisa in cui i passaggi possono condividere file (come condividiamo backend_url.txt).

Esecuzione del deployment

Per eseguire il deployment manualmente senza lo script, utilizza il comando gcloud builds submit. DEVI trasmettere le variabili di sostituzione richieste.

# Load your env vars first or replace these values manually
export PROJECT_ID=your-project-id
export REGION=us-central1

gcloud builds submit --config cloudbuild.yaml \
    --project "$PROJECT_ID" \
    --substitutions _REGION="us-central1",_GOOGLE_API_KEY="",_AGENT_ENGINE_ID="your-agent-id",_USE_MEMORY_BANK="TRUE",_GOOGLE_GENAI_USE_VERTEXAI="TRUE"

15. Conclusione

1. Cosa hai creato

Graph Database: Spanner con nodi (sopravvissuti, competenze) e archi (relazioni)
AI Search: ricerca semantica, ibrida e per parole chiave con incorporamenti
Pipeline multimodale: estrai entità da immagini/video con Gemini
Sistema multi-agente: flusso di lavoro coordinato con ADK
Memory Bank: personalizzazione a lungo termine con Vertex AI
Deployment di produzione: Cloud Run + Agent Engine

2. Riepilogo dell'architettura

architecture_fullstack

3. Nozioni principali

  1. RAG basato su grafici: combina la struttura del database a grafo con gli embedding semantici per una ricerca intelligente
  2. Pattern multi-agente: pipeline sequenziali per workflow complessi in più fasi
  3. AI multimodale: estrai dati strutturati da contenuti multimediali non strutturati (immagini/video)
  4. Agenti stateful: Memory Bank consente la personalizzazione tra le sessioni

4. Contenuti del workshop

5. Risorse