đŸ€–Â CrĂ©er un agent d'IA multimodal avec Graph RAG, ADK et Memory Bank

1. Introduction

reprise

1. Le défi

Dans les scénarios de réponse aux catastrophes, la coordination des survivants ayant des compétences, des ressources et des besoins différents dans plusieurs lieux nécessite des capacités intelligentes de gestion et de recherche de données. Cet atelier vous apprend à créer un systÚme d'IA de production qui combine :

  1. đŸ—„ïžÂ Base de donnĂ©es graphiques (Spanner) : stockez les relations complexes entre les survivants, les compĂ©tences et les ressources.
  2. 🔍 Recherche optimisĂ©e par l'IA : recherche hybride sĂ©mantique et par mot clĂ© Ă  l'aide d'embeddings
  3. 📾 Traitement multimodal : extraire des donnĂ©es structurĂ©es Ă  partir d'images, de texte et de vidĂ©os
  4. đŸ€–Â Orchestration multi-agent : coordonnez des agents spĂ©cialisĂ©s pour les workflows complexes.
  5. 🧠 MĂ©moire Ă  long terme : personnalisation avec Vertex AI Memory Bank

conversation

2. Objectifs de l'atelier

Une base de données de graphes du réseau de survivants avec :

  • đŸ—ș Visualisation interactive en 3D des relations entre les survivants
  • 🔍 Recherche intelligente (par mots clĂ©s, sĂ©mantique et hybride)
  • 📾 Pipeline d'importation multimodal (extraire des entitĂ©s Ă  partir d'images/vidĂ©os)
  • đŸ€–Â SystĂšme multi-agents pour l'orchestration de tĂąches complexes
  • 🧠 IntĂ©gration de Memory Bank pour des interactions personnalisĂ©es

3. Technologie principale

Composant

Technologie

Objectif

Database (Base de données)

Cloud Spanner Graph

Stocker les nƓuds (survivants, compĂ©tences) et les arĂȘtes (relations)

AI Search

Gemini + Embeddings

Compréhension sémantique et recherche par similarité

Framework de l'agent

ADK (Agent Development Kit)

Orchestrer des workflows d'IA

Mémoire

Vertex AI Memory Bank

Stockage à long terme des préférences utilisateur

Frontend

React + Three.js

Visualisation interactive de graphiques 3D

2. PrĂ©paration de l'environnement (Ă  ignorer si vous ĂȘtes dans l'atelier)

PremiÚre partie : Activer le compte de facturation

  • Revendiquez votre compte de facturation avec un crĂ©dit de 5 $, dont vous aurez besoin pour votre dĂ©ploiement. Assurez-vous d'utiliser votre compte Gmail.

DeuxiÚme partie : Environnement ouvert

  1. 👉 Cliquez sur ce lien pour accĂ©der directement Ă  l'Ă©diteur Cloud Shell.
  2. 👉 Si vous ĂȘtes invitĂ© Ă  autoriser l'accĂšs Ă  un moment donnĂ© aujourd'hui, cliquez sur Autoriser pour continuer. Cliquez pour autoriser Cloud Shell.
  3. 👉 Si le terminal ne s'affiche pas en bas de l'Ă©cran, ouvrez-le :
    • Cliquez sur Afficher.
    • Cliquez sur TerminalOuvrir un nouveau terminal dans l'Ă©diteur Cloud Shell.
  4. đŸ‘‰đŸ’»Â Dans le terminal, vĂ©rifiez que vous ĂȘtes dĂ©jĂ  authentifiĂ© et que le projet est dĂ©fini sur votre ID de projet Ă  l'aide de la commande suivante :
    gcloud auth list
    
  5. đŸ‘‰đŸ’»Â Clonez le projet bootstrap depuis GitHub :
    git clone https://github.com/google-americas/way-back-home.git
    

3. Configuration de l'environnement

1. Initialiser

Dans le terminal de l'éditeur Cloud Shell, si le terminal n'apparaßt pas en bas de l'écran, ouvrez-le :

  • Cliquez sur Afficher.
  • Cliquez sur Terminal

Ouvrir un nouveau terminal dans l'éditeur Cloud Shell

đŸ‘‰đŸ’»Â Dans le terminal, rendez le script d'initialisation exĂ©cutable et exĂ©cutez-le :

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

2. Configurer le projet

đŸ‘‰Â đŸ’» DĂ©finissez votre ID de projet :

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

đŸ‘‰đŸ’»Â Activez les API requises (cela prend environ deux Ă  trois minutes) :

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. Exécuter le script de configuration

đŸ‘‰đŸ’»Â ExĂ©cutez le script d'installation :

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

Cela créera .env pour vous. Dans votre Cloud Shell, ouvrez le projet way_back_home. Sous le dossier level_2, vous pouvez voir que le fichier .env a été créé pour vous. Si vous ne le trouvez pas, cliquez sur View > Toggle Hidden File pour l'afficher. open_project

4. Charger des exemples de données

đŸ‘‰đŸ’»Â AccĂ©dez au backend et installez les dĂ©pendances :

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

đŸ‘‰đŸ’»Â Chargez les donnĂ©es initiales des survivants :

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

Cela crée :

  • Instance Spanner (survivor-network)
  • Base de donnĂ©es (graph-db)
  • Toutes les tables de nƓuds et d'arĂȘtes
  • Graphiques de propriĂ©tĂ©s pour les requĂȘtes RĂ©sultat attendu :
============================================================
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

Si vous cliquez sur le lien aprÚs Access your database at dans le résultat, vous pouvez ouvrir la console Google Cloud Spanner.

open_spanner

Vous verrez Spanner dans la console Google Cloud.

spanner

4. Visualiser des données graphiques dans Spanner Studio

Ce guide vous aide à visualiser les données du graphique Survivor Network et à interagir avec elles directement dans la console Google Cloud à l'aide de Spanner Studio. C'est un excellent moyen de vérifier vos données et de comprendre la structure du graphique avant de créer votre agent d'IA.

1. Accéder à Spanner Studio

  1. À la derniĂšre Ă©tape, veillez Ă  cliquer sur le lien et Ă  ouvrir Spanner Studio.

spanner_studio

2. Comprendre la structure du graphique ("vue d'ensemble")

Considérez l'ensemble de données Survivor Network comme un puzzle logique ou un état de jeu :

Entité

RĂŽle dans le systĂšme

Analogie

Survivants

Les agents/joueurs

Joueurs

Biomes

OĂč se trouvent-ils ?

Zones de carte

Compétences

Ce qu'ils peuvent faire

Fonctionnalités

Besoins

Ce qui leur manque (crises)

QuĂȘtes/Missions

Ressources

ÉlĂ©ments trouvĂ©s dans le monde

Loot

Objectif : l'agent d'IA doit associer des compétences (solutions) à des besoins (problÚmes), en tenant compte des biomes (contraintes de localisation).

🔗 ArĂȘtes (relations) :

  • SurvivorInBiome : suivi de la position
  • SurvivorHasSkill : Inventaire des capacitĂ©s
  • SurvivorHasNeed : liste des problĂšmes actifs.
  • SurvivorFoundResource : inventaire des articles
  • SurvivorCanHelp : relation infĂ©rĂ©e (l'IA la calcule).

3. Interroger le graphique

ExĂ©cutons quelques requĂȘtes pour dĂ©couvrir l'histoire que racontent les donnĂ©es.

Spanner Graph utilise le langage GQL (Graph Query Language). Pour exĂ©cuter une requĂȘte, utilisez GRAPH SurvivorNetwork suivi de votre modĂšle de correspondance.

👉 RequĂȘte 1 : Le roster mondial (qui est oĂč ?) C'est votre base : comprendre la localisation est essentiel pour les opĂ©rations de sauvetage.

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

Le rĂ©sultat devrait ĂȘtre le suivant : query1

👉 RequĂȘte 2 : La matrice de compĂ©tences (capacitĂ©s) Maintenant que vous savez oĂč se trouve chaque personne, dĂ©couvrez ce qu'elle peut faire.

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

Le rĂ©sultat devrait ĂȘtre le suivant : query2

👉 RequĂȘte 3 : Qui est en crise ? (Le tableau des missions) : dĂ©couvrez les survivants qui ont besoin d'aide et ce dont ils ont besoin.

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

Le rĂ©sultat devrait ĂȘtre le suivant : query3

🔎 Avancé : mise en relation – Qui peut aider qui ?

C'est lĂ  que le graphique devient puissant. Cette requĂȘte permet de trouver les survivants qui possĂšdent des compĂ©tences permettant de rĂ©pondre aux besoins des autres survivants.

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

Le rĂ©sultat devrait ĂȘtre le suivant : query4

aside positive Que fait cette requĂȘte ?

Au lieu de simplement afficher "Les premiers secours traitent les brĂ»lures" (ce qui est Ă©vident d'aprĂšs le schĂ©ma), cette requĂȘte trouve :

  • Dr. Elena Frost (qui a une formation mĂ©dicale) → peut soigner → Capitaine Tanaka (qui a des brĂ»lures)
  • David Chen (qui a suivi une formation de secouriste) → peut soigner → Lt. Park (qui a une entorse Ă  la cheville)

Pourquoi cette fonctionnalité est-elle efficace ?

Ce que votre agent d'IA fera :

Lorsqu'un utilisateur pose la question Qui peut soigner les brûlures ?, l'agent :

  1. ExĂ©cuter une requĂȘte de graphe similaire
  2. Réponse : "Dr. Frost a une formation médicale et peut aider le capitaine Tanaka"
  3. L'utilisateur n'a pas besoin de connaßtre les tables ni les relations intermédiaires.

5. Embeddings optimisés par l'IA dans Spanner

1. Pourquoi utiliser des embeddings ? (Aucune action, lecture seule)

Dans le scénario de survie, le temps est essentiel. Lorsqu'un survivant signale une urgence, comme I need someone who can treat burns ou Looking for a medic, il ne peut pas perdre de temps à deviner les noms exacts des compétences dans la base de données.

ScĂ©nario rĂ©el : Survivor : Captain Tanaka has burns—we need medical help NOW!

Recherche traditionnelle par mot clĂ© pour "mĂ©decin" → 0 rĂ©sultat ❌

Recherche sĂ©mantique avec des embeddings : trouve "Formation mĂ©dicale" et "Premiers secours" ✅

C'est exactement ce dont les agents ont besoin : une recherche intelligente et humaine qui comprend l'intention, et pas seulement les mots clés.

2. Créer un modÚle d'embedding

spanner_embedding

Nous allons maintenant créer un modÚle qui convertit le texte en embeddings à l'aide de text-embedding-004 de Google.

👉 Dans Spanner Studio, exĂ©cutez cette requĂȘte SQL (remplacez $YOUR_PROJECT_ID par l'ID de votre projet) :

‌ Dans l'Ă©diteur Cloud Shell, ouvrez File > Open Folder > way-back-home/level_2 pour afficher l'intĂ©gralitĂ© du projet.

project_id

👉 ExĂ©cutez cette requĂȘte dans Spanner Studio en copiant et en collant la requĂȘte ci-dessous, puis en cliquant sur le bouton "ExĂ©cuter" :

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

Fonctionnement :

  • CrĂ©e un modĂšle virtuel dans Spanner (aucune pondĂ©ration de modĂšle n'est stockĂ©e en local).
  • Pointe vers text-embedding-004 de Google sur Vertex AI
  • DĂ©finit le contrat : l'entrĂ©e est un texte, la sortie est un tableau flottant de 768 dimensions.

Pourquoi "OPTIONS À DISTANCE" ?

  • Spanner n'exĂ©cute pas le modĂšle lui-mĂȘme
  • Il appelle Vertex AI via l'API lorsque vous utilisez ML.PREDICT.
  • Zero-ETL : pas besoin d'exporter les donnĂ©es vers Python, de les traiter et de les rĂ©importer

Cliquez sur le bouton Run. Une fois l'opération réussie, vous pouvez voir le résultat ci-dessous :

spanner_result

3. Ajouter une colonne d'embedding

👉 Ajoutez une colonne pour stocker les embeddings :

ALTER TABLE Skills ADD COLUMN skill_embedding ARRAY<FLOAT32>;

Cliquez sur le bouton Run. Une fois l'opération réussie, vous pouvez voir le résultat ci-dessous :

embedding_result

4. Générer des embeddings

👉 Utilisez l'IA pour crĂ©er des embeddings vectoriels pour chaque compĂ©tence :

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

Cliquez sur le bouton Run. Une fois l'opération réussie, vous pouvez voir le résultat ci-dessous :

skills_result

Ce qui se passe : chaque nom d'application (par exemple, "premiers secours") est convertie en un vecteur de 768 dimensions représentant sa signification sémantique.

5. Valider les embeddings

👉 VĂ©rifiez que les embeddings ont Ă©tĂ© créés :

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

Résultat attendu :

spanner_result

Nous allons maintenant tester le cas d'utilisation exact de notre scénario : trouver des compétences médicales à l'aide du terme "médecin".

👉 Trouver des compĂ©tences semblables Ă  "mĂ©decin" :

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;
  • Convertit le terme de recherche "mĂ©decin " de l'utilisateur en embedding
  • Stocke les donnĂ©es dans la table temporaire query_embedding

Résultats attendus (plus la distance est faible, plus les éléments sont semblables) :

spanner_result

7. Créer un modÚle Gemini pour l'analyse

spanner_gemini

👉 CrĂ©ez une rĂ©fĂ©rence de modĂšle d'IA gĂ©nĂ©rative (remplacez $YOUR_PROJECT_ID par l'ID rĂ©el de votre projet) :

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

Différence par rapport au modÚle d'embeddings :

  • Embeddings : texte → vecteur (pour la recherche de similaritĂ©s)
  • Gemini : texte → texte gĂ©nĂ©rĂ© (pour le raisonnement/l'analyse)

spanner_result

8. Utiliser Gemini pour l'analyse de la compatibilité

👉 Analysez les paires de survivants pour vĂ©rifier leur compatibilitĂ© avec la mission :

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

Résultat attendu :

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. Créer votre agent Graph RAG avec la recherche hybride

1. Présentation de l'architecture du systÚme

Cette section explique comment crĂ©er un systĂšme de recherche multimĂ©thode qui permet Ă  votre agent de gĂ©rer diffĂ©rents types de requĂȘtes de maniĂšre flexible. Le systĂšme comporte trois couches : couche Agent, couche Outil et couche Service.

architecture_hybrid_search

Pourquoi trois couches ?

  • SĂ©paration des prĂ©occupations : l'agent se concentre sur l'intention, les outils sur l'interface et le service sur l'implĂ©mentation.
  • Flexibilité : l'agent peut forcer des mĂ©thodes spĂ©cifiques ou laisser l'IA effectuer le routage automatique.
  • Optimisation : possibilitĂ© d'ignorer l'analyse d'IA coĂ»teuse lorsque la mĂ©thode est connue

Dans cette section, vous allez principalement implémenter la recherche sémantique (RAG), qui consiste à trouver des résultats en fonction de leur signification et pas seulement de leurs mots clés. Nous expliquerons plus tard comment la recherche hybride fusionne plusieurs méthodes.

2. Implémentation du service RAG

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante :

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

Recherchez le commentaire # TODO: REPLACE_SQL.

Remplacez toute cette ligne par le code suivant :

        # 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. Définition de l'outil de recherche sémantique

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante :

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

Dans hybrid_search_tools.py, localisez le commentaire # TODO: REPLACE_SEMANTIC_SEARCH_TOOL.

👉 Remplacez toute cette ligne par le code suivant :

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

Quand l'agent utilise :

  • RequĂȘtes demandant une similaritĂ© ("trouve des Ă©lĂ©ments semblables Ă  X")
  • RequĂȘtes conceptuelles ("capacitĂ©s de guĂ©rison")
  • Lorsque la comprĂ©hension du sens est essentielle

4. Guide de décision de l'agent (instructions)

Dans la définition de l'agent, copiez-collez la partie liée à la recherche sémantique dans l'instruction.

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante :

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

L'agent utilise cette instruction pour sélectionner l'outil approprié :

👉 Dans le fichier agent.py, recherchez le commentaire # TODO: REPLACE_SEARCH_LOGIC, Replace this whole line (Remplacez toute cette ligne) et remplacez-le par le code suivant :

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

👉 Localisez le commentaire # TODO: ADD_SEARCH_TOOLReplace this whole line (Remplacez toute cette ligne) et remplacez-le par le code suivant :

    semantic_search,         # Force RAG

5. Comprendre le fonctionnement de la recherche hybride (lecture seule, aucune action requise)

Dans les Ă©tapes 2 à 4, vous avez implĂ©mentĂ© la recherche sĂ©mantique (RAG), la mĂ©thode de recherche principale qui trouve des rĂ©sultats par signification. Mais vous avez peut-ĂȘtre remarquĂ© que le systĂšme s'appelle "Recherche hybride". Voici comment tout cela s'imbrique :

Fonctionnement de la fusion hybride :

Dans le fichier way-back-home/level_2/backend/services/hybrid_search_service.py, lorsque hybrid_search() est appelé, le service exécute LES DEUX recherches et fusionne les résultats :

# 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

Pour cet atelier de programmation, vous avez implémenté le composant de recherche sémantique (RAG), qui constitue la base. Les méthodes par mots clés et hybrides sont déjà implémentées dans le service. Votre agent peut utiliser les trois.

Félicitations ! Vous avez terminé de créer votre agent Graph RAG avec la recherche hybride.

7. Tester votre agent avec ADK Web

Le moyen le plus simple de tester votre agent est d'utiliser la commande adk web, qui lance votre agent avec une interface de chat intégrée.

1. Exécuter l'agent

đŸ‘‰đŸ’»Â AccĂ©dez au rĂ©pertoire backend (oĂč votre agent est dĂ©fini) et lancez l'interface Web :

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

Cette commande démarre l'agent défini dans .

agent/agent.py

et ouvre une interface Web pour les tests.

👉 Ouvrez l'URL :

La commande génÚre une URL locale (généralement http://127.0.0.1:8000 ou similaire). Ouvrez-le dans votre navigateur.

adk web

Une fois que vous avez cliqué sur l'URL, l'interface utilisateur Web de l'ADK s'affiche. Assurez-vous de sélectionner l'agent en haut à gauche.

adk_ui

2. Tester les fonctionnalités de recherche

L'agent est conçu pour acheminer intelligemment vos requĂȘtes. Essayez les entrĂ©es suivantes dans la fenĂȘtre de chat pour voir diffĂ©rentes mĂ©thodes de recherche en action.

Trouve des Ă©lĂ©ments en fonction de leur signification et de leur concept, mĂȘme si les mots clĂ©s ne correspondent pas.

RequĂȘtes de test : (choisissez l'une des options ci-dessous)

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

ÉlĂ©ments Ă  prendre en compte :

  • Le raisonnement doit mentionner la recherche sĂ©mantique ou RAG.
  • Vous devriez obtenir des rĂ©sultats conceptuellement liĂ©s (par exemple, "Chirurgie" lorsque vous demandez "Premiers secours").
  • Les rĂ©sultats seront accompagnĂ©s de l'icĂŽne 🧬.

Combine les filtres de mots clĂ©s avec la comprĂ©hension sĂ©mantique pour les requĂȘtes complexes.

RequĂȘtes de test : (choisissez l'une des options ci-dessous)

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?

ÉlĂ©ments Ă  prendre en compte :

  • La justification doit mentionner la recherche hybride.
  • Les rĂ©sultats doivent correspondre aux DEUX critĂšres (concept + lieu/catĂ©gorie).
  • Les rĂ©sultats trouvĂ©s par les deux mĂ©thodes sont accompagnĂ©s de l'icĂŽne 🔀 et sont classĂ©s en premier.

đŸ‘‰đŸ’»Â Une fois les tests terminĂ©s, mettez fin au processus en appuyant sur Ctrl+C dans votre ligne de commande.

8. Exécuter l'application complÚte

Présentation de l'architecture Full Stack

architecture_fullstack

Ajouter SessionService et Runner

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier chat.py dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante (assurez-vous d'avoir appuyĂ© sur Ctrl+C pour mettre fin au processus prĂ©cĂ©dent avant de continuer) :

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

👉 Dans le fichier chat.py, recherchez le commentaire # TODO: REPLACE_INMEMORY_SERVICES, Replace this whole line (Remplacez toute cette ligne) et remplacez-le par le code suivant :

    session_service = InMemorySessionService()
    memory_service = InMemoryMemoryService()

👉 Dans le fichier chat.py, recherchez le commentaire # TODO: REPLACE_RUNNER, Replace this whole line (Remplacez toute cette ligne) et remplacez-le par le code suivant :

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

1. Commencer l'inscription

Si le terminal précédent est toujours en cours d'exécution, mettez-y fin en appuyant sur Ctrl+C.

đŸ‘‰đŸ’»Â DĂ©marrer l'application :

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

Lorsque le backend démarre correctement, Local: http://localhost:5173/" s'affiche, comme suit : fronted

👉 Cliquez sur Local : http://localhost:5173/ dans le terminal.

conversation

RequĂȘte :

Find skills similar to healing

chat

Ce qui se passe :

  • L'agent reconnaĂźt la demande de similaritĂ©
  • GĂ©nĂšre un embedding pour "guĂ©rison"
  • Utilise la distance de cosinus pour trouver des compĂ©tences sĂ©mantiquement similaires
  • RĂ©sultats : premiers secours (mĂȘme si les noms ne correspondent pas Ă  "soins")

RequĂȘte :

Find medical skills in the mountains

Ce qui se passe :

  1. Composant de mot clé : filtrez sur category='medical'
  2. Composant sémantique : intégrer "médical" et classer par similarité
  3. Fusionner : combine les rĂ©sultats en privilĂ©giant ceux trouvĂ©s par les deux mĂ©thodes 🔀

RequĂȘte(facultatif) :

Who is good at survival and in the forest?

Ce qui se passe :

  • Mots clĂ©s trouvĂ©s : biome='forest'
  • RĂ©sultats sĂ©mantiques : compĂ©tences similaires Ă  "survie"
  • La mĂ©thode hybride combine les deux pour obtenir les meilleurs rĂ©sultats.

đŸ‘‰đŸ’»Â Lorsque vous avez terminĂ© les tests, appuyez sur Ctrl+C dans le terminal pour y mettre fin.

9. Pipeline multimodal : couche d'outillage

Pourquoi avons-nous besoin d'un pipeline multimodal ?

Le réseau de survie n'est pas qu'un texte. Les survivants sur le terrain envoient des données non structurées directement par chat :

  • 📾 Images : photos de ressources, de dangers ou d'Ă©quipements
  • đŸŽ„Â VidĂ©os : rapports d'Ă©tat ou diffusions SOS
  • 📄 Texte : notes ou journaux de terrain

Quels fichiers traitons-nous ?

Contrairement Ă  l'Ă©tape prĂ©cĂ©dente, oĂč nous avons recherchĂ© des donnĂ©es existantes, nous allons ici traiter les fichiers importĂ©s par l'utilisateur. L'interface chat.py gĂšre les piĂšces jointes de maniĂšre dynamique :

Source

Contenu

Objectif

Association d'utilisateurs

Image/Vidéo/Texte

Informations Ă  ajouter au graphique

Contexte du chat

"Voici une photo des fournitures"

Intention et informations supplémentaires

Approche prévue : pipeline d'agents séquentiels

Nous utilisons un agent séquentiel (multimedia_agent.py) qui enchaßne des agents spécialisés :

architecture_uploading

Ce paramÚtre est défini dans backend/agent/multimedia_agent.py en tant que SequentialAgent.

La couche d'outillage fournit les capacités que les agents peuvent invoquer. Les outils gÚrent le "comment" : importer des fichiers, extraire des entités et enregistrer dans la base de données.

1. Ouvrir le fichier d'outils

đŸ‘‰đŸ’»Â Ouvrez un nouveau terminal. Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell :

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

2. Implémenter l'outil upload_media

Cet outil importe un fichier local dans Google Cloud Storage.

👉 Dans extraction_tools.py, recherchez le commentaire pass # TODO: REPLACE_UPLOAD_MEDIA_FUNCTION.

Remplacez toute cette ligne par le code suivant :

    """
    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. Implémenter l'outil extract_from_media

Cet outil est un routeur : il vérifie le media_type et l'envoie à l'extracteur approprié (texte, image ou vidéo).

👉 Dans extraction_tools.py, localisez le commentaire pass # TODO: REPLACE_EXTRACT_FROM_MEDIA.

Remplacez toute cette ligne par le code suivant :

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

Informations clés sur l'implémentation :

  • EntrĂ©e multimodale : nous transmettons Ă  generate_content Ă  la fois la requĂȘte textuelle (_get_extraction_prompt()) et l'objet image.
  • Sortie structurĂ©e : response_mime_type="application/json" garantit que le LLM renvoie un JSON valide, ce qui est essentiel pour le pipeline.
  • Association d'entitĂ©s visuelles : la requĂȘte inclut des entitĂ©s connues afin que Gemini puisse reconnaĂźtre des personnages spĂ©cifiques.

4. Implémenter l'outil save_to_spanner

Cet outil conserve les entités et les relations extraites dans la base de données Spanner Graph.

👉 Dans extraction_tools.py, localisez le commentaire pass # TODO: REPLACE_SPANNER_AGENT.

Remplacez toute cette ligne par le code suivant :

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

En fournissant aux agents des outils de haut niveau, nous assurons l'intégrité des données tout en tirant parti de leurs capacités de raisonnement.

5. Mettre Ă  jour le service GCS

GCSService gÚre l'importation du fichier vers Google Cloud Storage.

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell :

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

👉 Dans le fichier gcs_service.py, recherchez le commentaire # TODO: REPLACE_SAVE_TO_GCS Ă  l'intĂ©rieur de la fonction upload_file.

Remplacez toute cette ligne par le code suivant :

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

En abstrayant cela dans un service, l'agent n'a pas besoin de connaßtre les buckets GCS, les noms de blobs ni la génération d'URL signées. Il vous demande simplement d'importer le fichier.

6. (Lecture seule) Pourquoi le workflow agentique > les approches traditionnelles ?

L'avantage de l'agentivité :

Fonctionnalité

Pipeline par lot

En fonction des événements

Workflow agentif

Complexité

Faible (1 script)

ÉlevĂ©e (5 services ou plus)

Faible (1 fichier Python : multimedia_agent.py)

Gestion de l'état

Variables globales

ÉlevĂ©e (dĂ©couplĂ©e)

Unifié (état de l'agent)

Gestion des erreurs

Plantages

Journaux silencieux

Interactive ("Je n'ai pas pu lire ce fichier")

Commentaires des utilisateurs

Impressions sur console

Recherche nécessaire

Immédiat (partie du chat)

Adaptabilité

Logique fixe

Fonctions rigides

Intelligente (le LLM décide de l'étape suivante)

Conscience du contexte

Aucun

Aucun

ComplĂšte (connaĂźt l'intention de l'utilisateur)

Pourquoi est-ce important ? En utilisant multimedia_agent.py (un SequentialAgent avec quatre sous-agents : Upload → Extract → Save → Summary), nous remplaçons une infrastructure complexe ET des scripts fragiles par une logique d'application intelligente et conversationnelle.

10. Pipeline multimodal : couche de l'agent

La couche d'agent définit l'intelligence, c'est-à-dire les agents qui utilisent des outils pour accomplir des tùches. Chaque agent a un rÎle spécifique et transmet le contexte à l'agent suivant. Vous trouverez ci-dessous un schéma d'architecture pour un systÚme multi-agents.

agent_diagram

1. Ouvrir le fichier de l'agent

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell :

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

2. Définir l'agent d'importation

Cet agent extrait un chemin d'accĂšs Ă  un fichier du message de l'utilisateur et l'importe dans GCS.

👉 Dans le fichier multimedia_agent.py, recherchez le commentaire # TODO: REPLACE_UPLOAD_AGENT.

Remplacez toute cette ligne par le code suivant :

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. Définir l'agent d'extraction

Cet agent "voit" le contenu multimédia importé et extrait des données structurées à l'aide de Gemini Vision.

👉 Dans le fichier multimedia_agent.py, recherchez le commentaire # TODO: REPLACE_EXTRACT_AGENT.

Remplacez toute cette ligne par le code suivant :

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

Notez que instruction fait référence à {upload_result}. C'est ainsi que l'état est transmis entre les agents dans ADK.

4. Définir l'agent Spanner

Cet agent enregistre les entités et les relations extraites dans la base de données de graphes.

👉 Dans le fichier multimedia_agent.py, recherchez le commentaire # TODO: REPLACE_SPANNER_AGENT.

Remplacez toute cette ligne par le code suivant :

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

Cet agent reçoit le contexte des deux étapes précédentes (upload_result et extraction_result).

5. Définir l'agent Summary

Cet agent synthétise les résultats de toutes les étapes précédentes dans une réponse conviviale.

👉 Dans le fichier multimedia_agent.py, recherchez le commentaire summary_instruction="" # TODO: REPLACE_SUMMARY_AGENT_PROMPT.

Remplacez toute cette ligne par le code suivant :

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

Cet agent n'a pas besoin d'outils. Il se contente de lire le contexte partagé et de générer un résumé clair pour l'utilisateur.

🧠 RĂ©sumĂ© de l'architecture

intégrée

Fichier

Responsabilité

Outils

extraction_tools.py + gcs_service.py

Comment faire ? : importer, extraire, enregistrer

Agent

multimedia_agent.py

Quoi : orchestrer le pipeline

11. Pipeline de données multimodales : orchestration

Le cƓur de notre nouveau systĂšme est le MultimediaExtractionPipeline dĂ©fini dans backend/agent/multimedia_agent.py. Il utilise le modĂšle Sequential Agent de l'ADK (Agent Development Kit).

1. Pourquoi utiliser les campagnes séquentielles ?

Le traitement d'un import est une chaßne de dépendances linéaires :

  1. Vous ne pouvez pas extraire de données tant que vous n'avez pas le fichier (importation).
  2. Vous ne pouvez pas enregistrer de données tant que vous ne les avez pas extraites.
  3. Vous ne pouvez pas résumer tant que vous n'avez pas les résultats (Enregistrer).

Un SequentialAgent est idéal pour cela. Il transmet la sortie d'un agent en tant que contexte/entrée à l'agent suivant.

2. Définition de l'agent

Examinons comment le pipeline est assemblĂ© en bas de multimedia_agent.py : đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante :

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

Il reçoit des entrées des deux étapes précédentes. Recherchez le commentaire # TODO: REPLACE_ORCHESTRATION. Remplacez toute cette ligne par le code suivant :

    sub_agents=[upload_agent, extraction_agent, spanner_agent, summary_agent]

3. Contacter l'agent racine

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante :

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

Recherchez le commentaire # TODO: REPLACE_ADD_SUBAGENT. Remplacez toute cette ligne par le code suivant :

    sub_agents=[multimedia_agent],

Cet objet unique regroupe quatre "experts" en une seule entité appelable.

4. Flux de données entre les agents

Chaque agent stocke sa sortie dans un contexte partagé auquel les agents suivants peuvent accéder :

architecture_uploading

5. Ouvrez l'application (ignorez cette étape si l'application est toujours en cours d'exécution).

đŸ‘‰đŸ’»Â DĂ©marrer l'application :

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

👉 Cliquez sur Local : http://localhost:5173/ dans le terminal.

6. Tester l'importation d'images

👉 Dans l'interface de chat, choisissez l'une des photos ci-dessous et importez-la dans l'interface utilisateur :

Dans l'interface de chat, expliquez à l'agent votre contexte spécifique :

Here is the survivor note

Joignez ensuite l'image ici.

upload_input

upload_result

đŸ‘‰đŸ’»Â Dans le terminal, une fois les tests terminĂ©s, appuyez sur "Ctrl+C" pour mettre fin au processus.

6. Vérifier l'importation multimodale dans un bucket GCS

gcs

  • SĂ©lectionnez votre bucket, puis cliquez sur media.

media

  • Affichez l'image que vous avez importĂ©e ici. uploaded_img

7. Vérifier l'importation multimodale dans Spanner (facultatif)

Vous trouverez ci-dessous un exemple de résultat dans l'UI pour test_photo1.

  • Ouvrez Google Cloud Console Spanner.
  • SĂ©lectionnez votre instance : Survivor Network
  • SĂ©lectionnez votre base de donnĂ©es : graph-db
  • Dans la barre latĂ©rale de gauche, cliquez sur Spanner Studio.

👉 Dans Spanner Studio, interrogez les nouvelles donnĂ©es :

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;

Nous pouvons le vérifier en consultant le résultat ci-dessous :

spanner_verify

12. Banque de mémoire avec Agent Engine

1. Fonctionnement de la mémoire

Le systÚme utilise une approche à double mémoire pour gérer à la fois le contexte immédiat et l'apprentissage à long terme.

memory_bank

2. Que sont les thÚmes de souvenirs ?

Les thÚmes de mémoire définissent les catégories d'informations que l'agent doit mémoriser au cours des conversations. Considérez-les comme des classeurs pour différents types de préférences utilisateur.

Nos deux thÚmes :

  1. search_preferences : la façon dont l'utilisateur aime effectuer des recherches
    • PrĂ©fĂšrent-ils la recherche par mots clĂ©s ou la recherche sĂ©mantique ?
    • Quelles compĂ©tences/quels biomes recherchent-ils souvent ?
    • Exemple de mĂ©moire : "L'utilisateur prĂ©fĂšre la recherche sĂ©mantique pour les compĂ©tences mĂ©dicales"
  2. urgent_needs_context : les crises qu'ils suivent
    • Quelles ressources surveillent-ils ?
    • Quels survivants sont concernĂ©s ?
    • Exemple de mĂ©moire : "L'utilisateur suit la pĂ©nurie de mĂ©dicaments dans le camp nord"

3. Configurer des thÚmes de mémoire

Les thÚmes de mémoire personnalisés définissent ce que l'agent doit retenir. Ils sont configurés lors du déploiement d'Agent Engine.

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante :

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

~/way-back-home/level_2/backend/deploy_agent.py s'ouvre dans votre éditeur.

Nous définissons des objets de structure MemoryTopic pour indiquer au LLM les informations à extraire et à enregistrer.

👉 Dans le fichier deploy_agent.py, remplacez # TODO: SET_UP_TOPIC par ce qui suit :

# 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. Intégration de l'agent

Le code de l'agent doit connaßtre la Memory Bank pour enregistrer et récupérer des informations.

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante :

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

~/way-back-home/level_2/backend/agent/agent.py s'ouvre dans votre éditeur.

Création d'agents

Lorsque nous créons l'agent, nous transmettons after_agent_callback pour nous assurer que les sessions sont enregistrées en mémoire aprÚs les interactions. La fonction add_session_to_memory s'exécute de maniÚre asynchrone pour éviter de ralentir la réponse du chat.

👉 Dans le fichier agent.py, recherchez le commentaire # TODO: REPLACE_ADD_SESSION_MEMORY, Replace this whole line (Remplacez toute cette ligne) et remplacez-le par le code suivant :

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

Enregistrement en arriĂšre-plan

👉 Dans le fichier agent.py, recherchez le commentaire # TODO: REPLACE_ADD_MEMORY_BANK_TOOL, Replace this whole line (Remplacez toute cette ligne) et remplacez-le par le code suivant :

if USE_MEMORY_BANK:
    agent_tools.append(PreloadMemoryTool())

👉 Dans le fichier agent.py, recherchez le commentaire # TODO: REPLACE_ADD_CALLBACK, Replace this whole line (Remplacez toute cette ligne) et remplacez-le par le code suivant :

    after_agent_callback=add_session_to_memory if USE_MEMORY_BANK else None

Configurer le service de session Vertex AI

đŸ‘‰đŸ’»Â Dans le terminal, ouvrez le fichier chat.py dans l'Ă©diteur Cloud Shell en exĂ©cutant la commande suivante :

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

👉 Dans le fichier chat.py, recherchez le commentaire # TODO: REPLACE_VERTEXAI_SERVICES, Replace this whole line (Remplacez toute cette ligne) et remplacez-le par le code suivant :

    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. Configuration et déploiement

Avant de tester les fonctionnalités de mémoire, vous devez déployer l'agent avec les nouveaux thÚmes de mémoire et vous assurer que votre environnement est correctement configuré.

Nous avons fourni un script pratique pour gérer ce processus.

Exécuter le script de déploiement

đŸ‘‰đŸ’»Â Dans le terminal, exĂ©cutez le script de dĂ©ploiement :

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

Ce script effectue les actions suivantes :

  • ExĂ©cute backend/deploy_agent.py pour enregistrer les thĂšmes de l'agent et de la mĂ©moire auprĂšs de Vertex AI.
  • Capture le nouvel ID du moteur d'agent.
  • Mise Ă  jour automatique de votre fichier .env avec AGENT_ENGINE_ID.
  • Assurez-vous que USE_MEMORY_BANK=TRUE est dĂ©fini dans votre fichier .env.

[!IMPORTANT] Si vous apportez des modifications à custom_topics dans deploy_agent.py, vous devez réexécuter ce script pour mettre à jour Agent Engine.

13. Valider la banque de données avec des données multimodales

Vous pouvez vérifier que la banque de mémoire fonctionne en enseignant une préférence à l'agent et en vérifiant si elle persiste au fil des sessions.

1. Ouvrez l'application (ignorez cette étape si elle est déjà en cours d'exécution).

Ouvrez à nouveau l'application en suivant les instructions ci-dessous : si le terminal précédent est toujours en cours d'exécution, mettez-y fin en appuyant sur Ctrls+C.

đŸ‘‰đŸ’»Â DĂ©marrer l'application :

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

👉 Cliquez sur Local : http://localhost:5173/ dans le terminal.

2. Tester la banque de mémoire avec du texte

Dans l'interface de chat, expliquez à l'agent votre contexte spécifique :

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

👉 Patientez environ 30 secondes pour que la mĂ©moire soit traitĂ©e en arriĂšre-plan.

2. Démarrer une nouvelle session

Actualisez la page pour effacer l'historique des conversations en cours (mémoire à court terme).

Posez une question qui s'appuie sur le contexte que vous avez fourni précédemment :

"What kind of missions am I interested in?"

Réponse attendue :

"D'aprÚs vos conversations précédentes, vous vous intéressez à :

  • Missions de sauvetage mĂ©dical
  • OpĂ©rations en montagne/en haute altitude
  • CompĂ©tences requises : premiers secours, escalade

Voulez-vous que je trouve des survivants correspondant à ces critÚres ?"

3. Tester avec l'importation d'images

Importez une image et posez la question suivante :

remember this

Vous pouvez choisir l'une des photos ici ou la vÎtre, puis l'importer dans l'UI :

4. Vérifier dans Vertex AI Agent Engine

Accédez à Agent Engine dans la console Google Cloud.

  1. Assurez-vous de sélectionner le projet dans le sélecteur de projets en haut à gauche :Sélecteur de projet
  2. Vérifiez le moteur d'agent que vous venez de déployer à partir de la commande précédente use_memory_bank.sh :moteur d&#39;agentCliquez sur le moteur d'agent que vous venez de créer.
  3. Cliquez sur l'onglet Memories de cet agent déployé pour afficher toute la mémoire.afficher le souvenir

đŸ‘‰đŸ’»Â Lorsque vous avez terminĂ© de tester, cliquez sur "Ctrl+C" dans votre terminal pour mettre fin au processus.

🎉 FĂ©licitations ! Vous venez d'associer la Memory Bank Ă  votre agent.

14. Déployer dans Cloud Run

1. Exécuter le script de déploiement

đŸ‘‰đŸ’»Â ExĂ©cutez le script de dĂ©ploiement :

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

Une fois le déploiement réussi, vous obtiendrez l'URL de déploiement : déployé.

đŸ‘‰đŸ’»Â Avant de rĂ©cupĂ©rer l'URL, accordez l'autorisation en exĂ©cutant la commande suivante :

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

Accédez à l'URL déployée pour voir votre application en direct.

2. Comprendre le pipeline de compilation

Le fichier cloudbuild.yaml définit les étapes séquentielles suivantes :

  1. Compilation du backend : compile l'image Docker à partir de backend/Dockerfile.
  2. Déploiement du backend : déploie le conteneur de backend sur Cloud Run.
  3. Capture URL : obtient la nouvelle URL du backend.
  4. Compilation du frontend :
    • Installe les dĂ©pendances.
    • CrĂ©e l'application React en injectant VITE_API_URL=.
  5. Image de l'interface : crée l'image Docker à partir de frontend/Dockerfile (en empaquetant les éléments statiques).
  6. Frontend Deploy : déploie le conteneur de l'interface.

3. Vérifier le déploiement

Une fois la compilation terminée (consultez le lien vers les journaux fourni par le script), vous pouvez vérifier les points suivants :

  1. Accédez à la console Cloud Run.
  2. Recherchez le service survivor-frontend.
  3. Cliquez sur l'URL pour ouvrir l'application.
  4. ExĂ©cutez une requĂȘte de recherche pour vous assurer que le frontend peut communiquer avec le backend.

4. (!UNIQUEMENT POUR LES PARTICIPANTS À L'ATELIER) Mettre à jour votre position

đŸ‘‰Â đŸ’»Â ExĂ©cutez le script de finalisation :

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

Ouvrez waybackhome.dev. Vous verrez que votre position a été mise à jour. Bravo, vous avez terminé le niveau 2 !

résultat final

(FACULTATIF) 5. Déploiement manuel

Si vous préférez exécuter les commandes manuellement ou mieux comprendre le processus, voici comment utiliser cloudbuild.yaml directement.

Écriture cloudbuild.yaml

Un fichier cloudbuild.yaml indique à Google Cloud Build les étapes à exécuter.

  • steps : il s'agit d'une liste d'actions sĂ©quentielles. Chaque Ă©tape s'exĂ©cute dans un conteneur (par exemple, docker, gcloud, node, bash).
  • substitutions : variables pouvant ĂȘtre transmises au moment de la compilation (par exemple, $_REGION).
  • workspace : rĂ©pertoire partagĂ© dans lequel les Ă©tapes peuvent partager des fichiers (comme nous partageons backend_url.txt).

Exécuter le déploiement

Pour effectuer le déploiement manuellement sans le script, utilisez la commande gcloud builds submit. Vous DEVEZ transmettre les variables de substitution requises.

# 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. Conclusion

1. Ce que vous avez créé

✅ Base de donnĂ©es graphiques : Spanner avec des nƓuds (survivants, compĂ©tences) et des arĂȘtes (relations)
✅ Recherche IA : recherche par mots clĂ©s, sĂ©mantique et hybride avec des embeddings
✅ Pipeline multimodal : extraction d'entitĂ©s Ă  partir d'images/vidĂ©os avec Gemini
✅ SystĂšme multi-agents : workflow coordonnĂ© avec ADK
✅ Banque de mĂ©moire : personnalisation Ă  long terme avec Vertex AI
✅ DĂ©ploiement en production : Cloud Run + Agent Engine

2. Résumé de l'architecture

architecture_fullstack

3. Points clés

  1. Graph RAG : combine la structure de la base de données graphiques avec des embeddings sémantiques pour une recherche intelligente
  2. ModÚles multi-agents : pipelines séquentiels pour les workflows complexes en plusieurs étapes
  3. IA multimodale : extraire des données structurées à partir de contenus multimédias non structurés (images/vidéos)
  4. Agents avec état : la banque de mémoire permet la personnalisation entre les sessions

4. Contenu de l'atelier

5. Ressources