Déployer un agent d'IA RAG dans Streamlit à l'aide de Google ADK et Cloud Run

1. Introduction

Dans cet atelier de programmation, vous allez créer un agent Barista IA interactif pour un café. À l'aide du Agent Development Kit (ADK) Open Source de Google et du modèle Gemini 3.5 Flash, vous implémenterez la génération augmentée par récupération (RAG) pour ancrer les recommandations de l'agent dans un ensemble de données de menu fictif. Enfin, vous allez envelopper l'agent dans une interface utilisateur Streamlit et le déployer sur Cloud Run.

Objectifs de l'atelier

  • Créez une source de données RAG (menu.json) contenant des articles de café, des tags et des allergènes.
  • Créez un agent d'IA à l'aide de l'ADK LlmAgent et connectez un outil Python pour charger les données du menu.
  • Encapsulez l'agent dans une application de chat Streamlit qui gère l'historique des conversations.
  • Déployez l'application Streamlit sur Cloud Run à l'aide du déploiement basé sur la source.
  • Testez l'ancrage RAG et la sensibilisation aux allergènes.

Schéma de l'architecture

Prérequis

  • Un navigateur Web tel que Chrome.
  • Un projet Google Cloud avec facturation activée.
  • Connaître les bases de Python

Cet atelier de programmation s'adresse aux développeurs de tous niveaux, y compris aux débutants.

Coût estimé : moins de 1 USD.

2. Avant de commencer

Créer un projet Google Cloud

  1. Dans la console Google Cloud, sélectionnez ou créez un projet Google Cloud.
  2. Assurez-vous que la facturation est activée pour votre projet Cloud.

Démarrer Cloud Shell

  1. Cliquez sur Activer Cloud Shell en haut de la console Google Cloud.

Activer Cloud Shell

  1. Vérifiez l'authentification :

Autoriser Cloud Shell

  gcloud auth list
  1. Vérifiez que votre projet actif est défini :
  gcloud config get project

Si l'ID de projet affiché est incorrect ou si aucun ID n'est défini, exécutez la commande suivante :

  gcloud config set project <YOUR_PROJECT_ID>

Activer les API

Exécutez la commande suivante pour activer toutes les API requises :

gcloud services enable \
 run.googleapis.com \
 aiplatform.googleapis.com \
 cloudbuild.googleapis.com

3. Configurer votre projet

Au cours de cette étape, vous allez initialiser les variables d'environnement de votre projet et créer un répertoire de travail pour celui-ci.

  1. Dans votre session Cloud Shell active, initialisez les variables d'environnement du projet suivantes :
  export PROJECT_ID=$(gcloud config get-value project)

Remarque : Utilisez la région la plus proche.

Trouvez la région la plus proche et remplacez insert-region-here par celle-ci dans la commande suivante :

  export REGION=[insert-region-here]
  1. Créez un répertoire de projet nommé coffee-barista-agent et accédez-y :
  mkdir coffee-barista-agent && cd coffee-barista-agent

4. Créer la source de données du menu fictif

Pour ancrer l'IA Barista et l'empêcher d'halluciner des éléments inexistants, vous allez créer un ensemble de données de menu local. L'agent lira ce fichier au moment de l'exécution à l'aide d'un outil personnalisé.

  1. Créez et ouvrez menu.json dans l'éditeur Cloud Shell :
  cloudshell edit menu.json
  1. Collez le contenu JSON suivant dans l'éditeur et enregistrez le fichier :
[
  {
    "name": "Espresso Solo",
    "description": "A single shot of rich, bold espresso.",
    "price": 2.50,
    "tags": ["strong", "hot", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Oat Milk Honey Latte",
    "description": "Creamy steamed oat milk with espresso and a touch of honey.",
    "price": 5.00,
    "tags": ["sweet", "hot", "dairy-free"],
    "allergens": []
  },
  {
    "name": "Cold Brew Coffee",
    "description": "Smooth, slow-steeped cold brew served over ice.",
    "price": 4.00,
    "tags": ["strong", "cold", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Seasonal Pumpkin Latte",
    "description": "Spiced pumpkin sauce, espresso, and steamed milk, topped with whipped cream.",
    "price": 5.50,
    "tags": ["sweet", "hot", "seasonal"],
    "allergens": ["dairy"]
  },
  {
    "name": "Classic Croissant",
    "description": "Flaky, buttery traditional French pastry.",
    "price": 3.50,
    "tags": ["bakery", "savory"],
    "allergens": ["wheat", "dairy"]
  },
  {
    "name": "Vegan Blueberry Muffin",
    "description": "Soft, sweet muffin packed with real blueberries, entirely plant-based.",
    "price": 3.75,
    "tags": ["bakery", "sweet", "dairy-free", "vegan"],
    "allergens": ["wheat"]
  },
  {
    "name": "Nitro Cold Brew",
    "description": "Cold brew infused with nitrogen for a super smooth, creamy head.",
    "price": 4.50,
    "tags": ["strong", "cold", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Iced Caramel Macchiato",
    "description": "Chilled milk and vanilla syrup marked with espresso and caramel drizzle.",
    "price": 5.25,
    "tags": ["sweet", "cold"],
    "allergens": ["dairy"]
  }
]
  1. Vérifiez que le fichier JSON est correctement formaté :
  cat menu.json | python3 -m json.tool > /dev/null && echo "Valid JSON!"

💬 Discussion : JSON local ou bases de données en direct

Pourquoi utilisons-nous un simple fichier menu.json local au lieu d'une base de données en direct ?

Pour un tutoriel ou un prototype rapide, un fichier JSON local élimine le temps et la complexité de la configuration initiale de la base de données. Toutefois, dans une application de production d'entreprise réelle, vous connecteriez l'agent à une base de données gérée telle que Cloud Firestore, AlloyDB ou Cloud SQL.

L'utilisation d'une base de données en direct permet aux responsables de cafés d'ajouter des articles saisonniers, de modifier les prix ou d'ajuster les tags d'allergènes de manière dynamique, sans avoir à reconstruire l'image de conteneur ni à redéployer le code de l'application. Nous utiliserons une base de données en direct comme étape facultative plus loin dans l'atelier de programmation.

5. Créer l'agent ADK

Vous allez maintenant installer les packages requis et créer la logique de l'agent ADK principal. Vous définirez un outil get_menu() et le transmettrez à un LlmAgent.

  1. Créez et ouvrez requirements.txt dans l'éditeur Cloud Shell :
  cloudshell edit requirements.txt
  1. Collez les dépendances suivantes dans l'éditeur et enregistrez le fichier :
google-adk==2.2.0
streamlit==1.58.0
  1. Créez et ouvrez agent.py dans l'éditeur Cloud Shell :
  cloudshell edit agent.py
  1. Collez le code suivant dans agent.py :
# agent.py
import json

from google.adk.agents import LlmAgent

# [START get_menu]
def get_menu() -> str:
    """Retrieves the coffee shop menu from menu.json.

    Returns:
        str: A JSON string representing the list of menu items.
    """
    try:
        with open("menu.json", "r") as f:
            menu_data = json.load(f)
            return json.dumps(menu_data)
    except Exception as e:
        return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]

# Create the barista agent
barista_agent = LlmAgent(
    name="barista_agent",
    model="gemini-3.5-flash",
    instruction="""You are a friendly barista at ☕ Coffee Shop.
Your job is to recommend drinks and pastries to customers based on their preferences.

Rules you MUST follow:
1.  You must recommend items ONLY from the menu returned by get_menu().
2.  Do NOT recommend or suggest any item that is not present in the menu.
3.  If a user's preference is vague or unclear, ask exactly ONE friendly clarifying question to narrow down what they want (e.g., cold or hot, sweet or strong, coffee or pastry).
4.  Be warm and welcoming, but remain professional.
5.  Ground your recommendations in the actual tags, descriptions, and allergens listed in the menu (e.g., if a user is dairy-free, recommend ONLY items tagged 'dairy-free' or with no dairy allergens).
""",
    tools=[get_menu]
)

from google.adk.apps import App

# Define the App object
app = App(
    name="coffee_barista_app",
    root_agent=barista_agent
)
  1. Créez et ouvrez app.py dans l'éditeur Cloud Shell :
  cloudshell edit app.py
  1. Collez le code suivant dans app.py :
# app.py
import streamlit as st
import json

# Set page config for a premium look
st.set_page_config(
    page_title="☕ Coffee Shop - Barista Bot",
    page_icon="☕",
    layout="wide",
    initial_sidebar_state="expanded"
)

# Custom CSS to make the header sticky (adapts to light/dark themes)
st.markdown("""
<style>
    div[data-testid="element-container"]:has(.header-container),
    div.element-container:has(.header-container) {
        position: sticky;
        top: 2.875rem;
        z-index: 999;
        background-color: transparent;
        padding-bottom: 10px;
    }
</style>
""", unsafe_allow_html=True)

# App Header (using inline styles for the permanent coffee theme look)
st.markdown("""
<div class="header-container" style="text-align: center; padding: 20px; background: linear-gradient(135deg, #8B5E3C, #6F4E37); color: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
    <h1 style="margin: 0; font-size: 2.5rem; font-weight: 700; color: white;">☕ ☕ Coffee Shop</h1>
    <p style="margin: 5px 0 0 0; font-size: 1.1rem; opacity: 0.9; color: white;">Your friendly AI Barista is ready to help you find the perfect drink or pastry!</p>
</div>
""", unsafe_allow_html=True)

# Load Menu for the sidebar
# [START load_menu]
try:
    with open("menu.json", "r") as f:
        menu_items = json.load(f)
except Exception as e:
    st.error(f"Error loading menu: {e}")
    menu_items = []
# [END load_menu]

# Sidebar Menu & Configuration
with st.sidebar:
    st.markdown("## ☕ Coffee Shop Menu")
    st.markdown("Explore our offerings and ask the barista for recommendations.")
    st.markdown("---")

    for item in menu_items:
        with st.container(border=True):
            st.markdown(f"**{item['name']}**  •  **${item['price']:.2f}**")
            st.caption(item['description'])

            # Tags & Allergens as native badges
            tags = " ".join([f"`{t}`" for t in item.get("tags", [])])
            if tags:
                st.markdown(tags)

            allergens = ", ".join(item.get("allergens", []))
            if allergens:
                st.markdown(f"⚠️ *Allergens: {allergens}*")

# Chat Interface
if "session_id" not in st.session_state:
    import uuid
    st.session_state.session_id = str(uuid.uuid4())

if "runner" not in st.session_state:
    from google.adk.runners import InMemoryRunner
    from agent import app
    st.session_state.runner = InMemoryRunner(app=app)

if "messages" not in st.session_state:
    st.session_state.messages = [
        {"role": "assistant", "content": "Welcome to ☕ Coffee Shop! What can I get started for you today?"}
    ]

# Display existing messages
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

# User Input
if prompt := st.chat_input("Ask for recommendations (e.g., 'What dairy-free pastries do you have?')"):
    # Display user message
    with st.chat_message("user"):
        st.markdown(prompt)
    st.session_state.messages.append({"role": "user", "content": prompt})

    # Generate response
    with st.chat_message("assistant"):
        try:
            import asyncio

            # Run the ADK runner asynchronously using asyncio.run
            async def fetch_response():
                return await st.session_state.runner.run_debug(
                    prompt,
                    session_id=st.session_state.session_id
                )

            res_events = asyncio.run(fetch_response())

            response_text = "".join([
                part.text
                for event in res_events
                if event.content and event.content.parts
                for part in event.content.parts
                if part.text
            ])

            st.markdown(response_text)
            st.session_state.messages.append({"role": "assistant", "content": response_text})
        except Exception as e:
            st.error(f"Apologies, I ran into an error: {e}")

💬 Discussion : compromis du modèle et efficacité des jetons de récupération

Pourquoi appeler un outil de fonction pour récupérer le menu au lieu de simplement coller l'intégralité du texte du menu dans les instructions système de l'agent ?

L'économie des jetons ! Placer huit éléments dans le prompt est peu coûteux, mais que se passe-t-il si le café propose 500 articles, y compris des ingrédients personnalisés ? Si vous collez de grands ensembles de données directement dans l'invite système, le nombre de jetons de votre requête augmente, ce qui accroît les coûts de transaction et la latence de réponse de l'API pour chaque requête.

En utilisant un outil ADK, l'agent demande dynamiquement à lire le menu UNIQUEMENT lorsque cela est nécessaire. Le LLM ne reçoit que les données de menu pertinentes comme contexte, ce qui minimise la taille des jetons du prompt.

💬 Discussion : magasins d'état et de production de mémoire

L'historique des discussions stocké dans st.session_state de Streamlit est-il conservé lorsqu'un utilisateur ferme l'onglet de son navigateur ?

Non. st.session_state est entièrement en mémoire et unique à la connexion de navigateur active. Si un utilisateur actualise la page ou ferme l'onglet, l'historique de ses conversations avec le barista est perdu.

Pour une application de production, vous devez connecter l'exécuteur ADK à un backend de stockage persistant tel que Cloud Firestore ou Redis. ADK fournit des abstractions de service intégrées (comme SessionService) qui permettent d'enregistrer et de reprendre facilement l'historique des discussions lors des rechargements de page et sur différents appareils.

6. Déployer l'agent sur Cloud Run

Vous allez déployer l'application Streamlit directement à partir de la source à l'aide des buildpacks intégrés de Cloud Run. Pour respecter le principe du moindre privilège, vous allez créer et déployer un compte de service personnalisé dédié au lieu d'utiliser le compte de service Compute Engine par défaut.

  1. Créez un compte de service dédié :
  gcloud iam service-accounts create barista-agent-sa \
    --description="Service account for Coffee Barista ADK agent on Cloud Run" \
    --display-name="Barista Agent Service Account"
  1. Attribuez le rôle utilisateur Gemini Enterprise Agent Platform (roles/aiplatform.user) au nouveau compte de service :
  gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/aiplatform.user"
  1. Déployez le service à l'aide de gcloud run deploy, en transmettant la nouvelle adresse e-mail du compte de service via l'option --service-account :
gcloud run deploy coffee-barista \
  --source . \
  --region $REGION \
  --allow-unauthenticated \
  --labels dev-tutorial=codelab-streamlit-rag-adk \
  --command "/cnb/lifecycle/launcher" \
  --args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
  --service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
  --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global
  1. Une fois le service déployé, recherchez son URL dans le résultat de la commande.

💬 Discussion : Déployer des conteneurs ou des sources, et sécurité IAM

Nous avons déployé l'application sur Cloud Run à l'aide de la commande gcloud run deploy --source, sans créer de fichier Dockerfile ni de fichier Procfile. Comment Cloud Run a-t-il fait pour compiler et exécuter notre application Python ?

Cloud Run utilise des buildpacks en arrière-plan pour analyser votre dépôt. Lorsqu'il détecte la présence de fichiers sourcesrequirements.txt et Python, le moteur compile et package automatiquement un conteneur d'exécution Python.

Écrire un Dockerfile personnalisé vous permet de contrôler entièrement les packages système et les couches de base de votre conteneur. Un Procfile est un moyen plus simple de déclarer la commande de démarrage sans configurer entièrement un conteneur. Toutefois, pour les déploiements rapides, le déploiement à partir de la source (--source) est très efficace.

Pourquoi avons-nous créé un compte de service personnalisé barista-agent-sa au lieu d'utiliser le compte de service Compute Engine par défaut ?

La sécurité avant tout ! Le compte de service Compute Engine par défaut dispose d'autorisations d'éditeur extrêmement étendues par défaut. Si nous exécutons notre conteneur Cloud Run sous le compte de service par défaut, un pirate informatique pourrait potentiellement lire, écrire ou supprimer d'autres ressources de notre projet Google Cloud si notre application présente un bug de sécurité.

En créant un compte de service dédié et en ne lui attribuant que le rôle roles/aiplatform.user, nous respectons le principe du moindre privilège : l'application dispose exactement de l'accès dont elle a besoin pour appeler Gemini, et rien de plus.

7. Tester le comportement du RAG

Ouvrez l'URL du service Cloud Run dans un navigateur Web et posez des questions à l'AI Barista pour tester ses contraintes d'ancrage et de sécurité.

  1. Demande dans le menu : demandez "Recommande-moi une boisson forte et chaude." Résultat attendu : l'agent recommande un expresso.
  2. Piège hors menu : demande : "Avez-vous un frappuccino au matcha ?" ; réponse attendue : l'agent refuse poliment et explique que ce produit ne figure pas au menu.
  3. Demande tenant compte des allergènes : demandez "Je suis intolérant au lactose, que puis-je prendre ?". Résultat attendu : l'agent ne recommande que des plats sans produits laitiers (comme le latte au lait d'avoine, l'expresso ou le café infusé à froid). Il ne recommande PAS Cappuccino ni Croissant.

Tester le comportement du RAG

8. Facultatif : Ancrez votre agent dans Firestore à l'aide de Vector Search

Dans un scénario de production, il n'est pas idéal de stocker vos éléments de menu dans un fichier menu.json local, car toute modification du menu nécessite de reconstruire l'image de conteneur et de redéployer le service Cloud Run.

Pour rendre l'application dynamique et évolutive, vous pouvez migrer les données de votre menu vers Cloud Firestore et utiliser la recherche vectorielle pour récupérer uniquement les éléments de menu les plus pertinents en fonction de la similarité sémantique.

Intégrer Firestore à l'aide de la recherche vectorielle

1. Activer l'API Firestore et initialiser la base de données

Exécutez les commandes suivantes pour activer l'API Firestore et créer une base de données Firestore nommée coffee-menu en mode natif :

gcloud services enable firestore.googleapis.com

gcloud firestore databases create --database="coffee-menu" --location=$REGION

Remarque : L'activation de l'API peut prendre une à deux minutes. Si la commande de création de base de données vous invite à saisir API [firestore.googleapis.com] not enabled on project... Would you like to enable and retry?, saisissez Y pour continuer, ou patientez une minute et réexécutez la commande.

2. Initialiser Firestore avec les données du menu

Pour amorcer rapidement votre base de données Firestore avec les éléments de menu de votre fichier menu.json, vous pouvez exécuter un script Python localement dans Cloud Shell.

  1. Installez localement les bibliothèques clientes Firestore et GenAI dans Cloud Shell pour exécuter le script de seeding :
pip3 install google-cloud-firestore==2.27.0 google-genai==2.11.0
  1. Créez un script de seeding seed.py :
cloudshell edit seed.py
  1. Collez le code suivant dans seed.py :
# seed.py
import json
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

db = firestore.Client(database="coffee-menu")
client = genai.Client(
   vertexai=True,
   project=os.environ.get("PROJECT_ID"),
   location=os.environ.get("REGION", "us-central1")
)

with open("menu.json", "r") as f:
   menu_items = json.load(f)

for item in menu_items:
   # Use the name as the document ID
   doc_id = item["name"].lower().replace(" ", "-")

   # Generate text embedding using Vertex AI text-embedding-004 model
   text_to_embed = f"{item['name']}: {item['description']}"
   response = client.models.embed_content(
       model="text-embedding-004",
       contents=text_to_embed,
   )
   embedding = response.embeddings[0].values

   # Add embedding vector to the menu item data
   item["embedding"] = Vector(embedding)

   db.collection("menu").document(doc_id).set(item)

print("Firestore menu collection seeded with vector embeddings successfully!")
  1. Exécutez le script :
python3 seed.py

3. Créer un index vectoriel Firestore

Pour effectuer des recherches vectorielles sur vos éléments de menu, vous devez créer un index vectoriel composite sur le champ embedding de votre base de données Firestore.

Exécutez la commande suivante dans le terminal Cloud Shell :

gcloud firestore indexes composite create \
 --collection-group=menu \
 --query-scope=COLLECTION \
 --database="coffee-menu" \
 --field-config=field-path=embedding,vector-config='{"dimension":"768", "flat": "{}"}'

Remarque : La création d'index Firestore s'exécute en arrière-plan et peut prendre quelques minutes. Vous pouvez passer aux étapes suivantes de l'atelier de programmation pendant que l'index est en cours de création.

4. Accorder l'accès à Firestore au compte de service

Pour que votre service Cloud Run puisse interroger Firestore, vous devez attribuer le rôle Utilisateur Cloud Datastore (roles/datastore.user) à son compte de service :

gcloud projects add-iam-policy-binding $PROJECT_ID \
 --member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
 --role="roles/datastore.user"

Remarque : Bien que nous utilisions Cloud Firestore en mode natif, Google Cloud utilise les rôles IAM Cloud Datastore unifiés (roles/datastore.viewer ou roles/datastore.user) pour gérer le contrôle des accès.

5. Mettre à jour le code

Mettez à jour votre code pour récupérer le menu à partir de Firestore au lieu de le lire à partir de menu.json.

  1. Ouvrez requirements.txt dans l'éditeur Cloud Shell :
cloudshell edit requirements.txt
  1. Ajoutez les bibliothèques clientes Firestore et GenAI à la fin du fichier, puis enregistrez-le :
google-cloud-firestore==2.27.0
google-genai==2.11.0
  1. Ouvrez agent.py dans l'éditeur Cloud Shell :
cloudshell edit agent.py
  1. Recherchez le bloc # [START get_menu] dans agent.py et remplacez-le entièrement (de # [START get_menu] à # [END get_menu]) par l'implémentation Firestore suivante :
# [START get_menu]
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

def get_menu(query: str) -> str:
   """Retrieves coffee shop menu items matching the user's query.

   Args:
       query: The search query or preference to find matching menu items.

   Returns:
       str: A JSON string representing the list of top matching menu items.
   """
   try:
       # Initialize clients
       db = firestore.Client(database="coffee-menu")
       client = genai.Client()

       # Generate embedding for the search query
       response = client.models.embed_content(
           model="text-embedding-004",
           contents=query,
       )
       query_vector = response.embeddings[0].values

       # Search the Firestore database using Vector Search
       results = db.collection("menu").find_nearest(
           vector_field="embedding",
           query_vector=Vector(query_vector),
           distance_measure=DistanceMeasure.COSINE,
           limit=3,
       ).stream()

       menu_data = []
       for doc in results:
           item = doc.to_dict()
           # Remove embedding field to save tokens
           item.pop("embedding", None)
           menu_data.append(item)

       return json.dumps(menu_data)
   except Exception as e:
       return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]
  1. Ouvrez app.py dans l'éditeur Cloud Shell :
cloudshell edit app.py
  1. Recherchez le bloc # [START load_menu] dans app.py et remplacez-le entièrement (de # [START load_menu] à # [END load_menu]) par la logique de chargement Firestore suivante :
# [START load_menu]
from google.cloud import firestore

try:
   db = firestore.Client(database="coffee-menu")
   docs = db.collection("menu").stream()
   menu_items = []
   for doc in docs:
       item = doc.to_dict()
       item.pop("embedding", None)
       menu_items.append(item)
except Exception as e:
   st.error(f"Error loading menu from Firestore: {e}")
   menu_items = []
# [END load_menu]

6. Redéployer sur Cloud Run

Déployez l'application mise à jour :

gcloud run deploy coffee-barista \
 --source . \
 --region $REGION \
 --allow-unauthenticated \
 --command "/cnb/lifecycle/launcher" \
 --args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
 --service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
 --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global

7. Vérifier l'intégration de Firestore

Pour tester la connexion de l'agent à Firestore, ajoutez un tout nouvel élément de menu directement dans Firestore et vérifiez que l'agent le recommande.

  1. Exécutez la commande suivante dans Cloud Shell pour écrire un nouveau document dans la collection menu de Firestore à l'aide de Python :
python3 -c "
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

db = firestore.Client(database='coffee-menu')
client = genai.Client(
   vertexai=True,
   project=os.environ.get('PROJECT_ID'),
   location=os.environ.get('REGION', 'us-central1')
)

name = 'Matcha Green Tea Latte'
desc = 'Creamy steamed milk infused with premium Japanese matcha powder.'
res = client.models.embed_content(
   model='text-embedding-004',
   contents=f'{name}: {desc}'
)
embedding = res.embeddings[0].values

db.collection('menu').document('matcha-latte').set({
   'name': name,
   'description': desc,
   'price': 5.50,
   'tags': ['sweet', 'hot', 'dairy-free'],
   'allergens': [],
   'embedding': Vector(embedding)
})
print('Successfully added Matcha Latte with vector embeddings!')
"
  1. Actualisez votre application Streamlit dans le navigateur pour effacer la session de chat et charger le nouvel état de la base de données.
  2. Remarque :
    • Matcha Green Tea Latte s'affiche automatiquement dans le menu latéral.
    • Demandez au chatbot : "Avez-vous des boissons au matcha ?"
    • L'agent doit recommander le nouveau Matcha Green Tea Latte avec la description et le prix que vous venez d'ajouter. Cela confirme que l'agent est ancré sur les requêtes directement dans votre base de données Firestore active.

9. Effectuer un nettoyage

Pour éviter que des frais ne soient facturés sur votre compte de facturation Google Cloud, supprimez le service Cloud Run déployé et le compte de service personnalisé.

Supprimez le service Cloud Run :

gcloud run services delete coffee-barista --region $REGION --quiet

Supprimez le compte de service personnalisé :

gcloud iam service-accounts delete barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com --quiet

(Facultatif) Supprimez la base de données Firestore (si elle a été créée) :

gcloud firestore databases delete --database="coffee-menu" --quiet

Étape facultative : Supprimez l'intégralité du projet. ⚠️ Ne procédez ainsi que si vous avez créé un projet dédié pour cet atelier.

gcloud projects delete $PROJECT_ID

10. Félicitations

Félicitations ! Vous avez créé et déployé un agent Barista d'IA avec génération augmentée par récupération (RAG) à l'aide de l'ADK de Google et de Cloud Run.

Connaissances acquises

  • Construire des outils RAG simples en Python.
  • Utilisez les valeurs LlmAgent et InMemoryRunner de l'ADK.
  • Créer des expériences de chat avec état dans Streamlit.
  • Déployer Streamlit sur Cloud Run à l'aide de compilations basées sur la source.

Documents de référence