Base de données en tant qu'outil : RAG agentif avec ADK, MCP Toolbox et Cloud SQL

1. Introduction

L'utilité des agents d'IA dépend des données auxquelles ils peuvent accéder. La plupart des données réelles se trouvent dans des bases de données. Pour connecter des agents à des bases de données, il faut généralement écrire du code d'agent pour la gestion des connexions, la logique des requêtes et les pipelines d'intégration. Chaque agent qui a besoin d'accéder à une base de données répète ce travail, et chaque modification de requête nécessite de redéployer l'agent.

Cet atelier de programmation présente une approche différente. Vous déclarez vos outils de base de données dans un fichier YAML (requêtes SQL standards, recherche de similarité vectorielle, voire génération automatique d'embeddings), et MCP Toolbox for Databases gère toutes les opérations de base de données en tant que serveur MCP. Votre code d'agent reste minimal : chargez les outils et laissez Gemini décider lequel appeler.

Objectifs de l'atelier

Un concierge de restaurant pour "Foodie Finds" : un agent ADK optimisé par Gemini qui aide les clients à parcourir le menu d'un restaurant à l'aide de filtres standards (catégorie, type de cuisine) et à découvrir des plats grâce à des descriptions en langage naturel comme "Je veux quelque chose de pimenté et de végétarien". L'agent lit et écrit dans une base de données Cloud SQL PostgreSQL entièrement via MCP Toolbox for Databases, qui gère tous les accès à la base de données, y compris la génération automatique d'embeddings pour la recherche vectorielle. À la fin, la boîte à outils et l'agent s'exécutent sur Cloud Run.

53a7afb736a8fc8c.jpeg

Points abordés

  • Comment le protocole MCP (Model Context Protocol) standardise l'accès aux outils pour les agents d'IA et comment MCP Toolbox for Databases l'applique aux opérations de base de données
  • Configurer MCP Toolbox for Databases en tant que middleware entre un agent ADK et Cloud SQL PostgreSQL
  • Définissez les outils de base de données de manière déclarative dans tools.yaml : aucun code de base de données dans votre agent
  • Créer un agent ADK qui charge les outils à partir d'un serveur Toolbox en cours d'exécution à l'aide de ToolboxToolset
  • Générez des embeddings vectoriels à l'aide de la fonction embedding() intégrée de Cloud SQL et activez la recherche sémantique avec pgvector.
  • Utiliser la fonctionnalité valueFromParam pour l'ingestion automatique de vecteurs lors des opérations d'écriture
  • Déployer le serveur Toolbox et l'agent ADK sur Cloud Run

Prérequis

  • Un compte Google Cloud avec un compte de facturation d'essai
  • Connaître les bases de Python et de SQL
  • Une expérience préalable avec Cloud Database et l'ADK sera utile.

2. Configurer votre environnement

Cette étape prépare votre environnement Cloud Shell, configure votre projet Google Cloud et clone le dépôt de référence.

Ouvrir Cloud Shell

Ouvrez Cloud Shell dans votre navigateur. Cloud Shell fournit un environnement préconfiguré avec tous les outils dont vous avez besoin pour cet atelier de programmation. Cliquez sur Autoriser lorsque vous y êtes invité.

Cliquez ensuite sur Afficher > Terminal pour ouvrir le terminal.Votre interface devrait ressembler à ceci :

86307fac5da2f077.png

Ce sera notre interface principale, avec l'IDE en haut et le terminal en bas.

Configurer votre répertoire de travail

Créez votre répertoire de travail. Tout le code que vous écrivez dans cet atelier de programmation se trouve ici :

mkdir -p ~/build-agent-adk-toolbox-cloudsql
cloudshell workspace ~/build-agent-adk-toolbox-cloudsql && cd ~/build-agent-adk-toolbox-cloudsql

Ensuite, préparons plusieurs répertoires pour gérer des éléments tels que les scripts de seeding et les journaux.

mkdir -p ~/build-agent-adk-toolbox-cloudsql/scripts
mkdir -p ~/build-agent-adk-toolbox-cloudsql/logs

Configurer un projet Google Cloud

Créez le fichier .env avec les variables de localisation :

# For Vertex AI / Gemini API calls
echo "GOOGLE_CLOUD_LOCATION=global" > .env
# For Cloud SQL, Cloud Run, Artifact Registry
echo "REGION=us-central1" >> .env

Pour simplifier la configuration du projet dans votre terminal, téléchargez ce script de configuration du projet dans votre répertoire de travail :

curl -sL https://raw.githubusercontent.com/alphinside/cloud-trial-project-setup/main/setup_verify_trial_project.sh -o setup_verify_trial_project.sh

Exécutez le script. Il valide votre compte de facturation d'essai, crée un projet (ou en valide un existant), enregistre l'ID de votre projet dans un fichier .env du répertoire actuel et définit le projet actif dans gcloud.

bash setup_verify_trial_project.sh && source .env

Le script va :

  1. Vérifier que vous disposez d'un compte de facturation d'essai actif
  2. Recherchez un projet existant dans .env (le cas échéant).
  3. Créez un projet ou réutilisez-en un existant.
  4. Associer le compte de facturation d'essai à votre projet
  5. Enregistrez l'ID du projet dans .env.
  6. Définir le projet comme projet gcloud actif

Vérifiez que le projet est correctement défini en examinant le texte jaune à côté de votre répertoire de travail dans l'invite du terminal Cloud Shell. L'ID de votre projet devrait s'afficher.

dcba35ce1389f313.png

Activer l'API requise

Ensuite, nous devons activer plusieurs API pour le produit avec lequel nous allons interagir :

gcloud services enable \
  aiplatform.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com
  • API Vertex AI (aiplatform.googleapis.com) : votre agent utilise les modèles Gemini, et Toolbox utilise l'API d'embedding pour la recherche vectorielle.
  • API Cloud SQL Admin (sqladmin.googleapis.com) : vous provisionnez et gérez une instance PostgreSQL.
  • L'API Compute Engine (compute.googleapis.com) est requise pour créer des instances Cloud SQL.
  • Cloud Run, Cloud Build, Artifact Registry : utilisés lors de l'étape de déploiement plus loin dans cet atelier

3. Préparer des scripts pour l'initialisation de la base de données

Cette étape lance la création de l'instance Cloud SQL et exécute un script de configuration automatisé qui attend que l'instance soit prête, puis crée la base de données, l'alimente avec des offres d'emploi et génère des embeddings, le tout en une seule opération.

Commençons par ajouter le mot de passe de la base de données à votre fichier .env et à le recharger :

echo "DB_PASSWORD=restaurant-pwd" >> .env
echo "DB_INSTANCE=restaurant-instance" >> .env
echo "DB_NAME=restaurant_db" >> .env
source .env

Créer un script Bash pour créer une instance et une base de données

Créez ensuite le script scripts/setup_database.sh à l'aide de la commande suivante :

mkdir -p ~/build-agent-adk-toolbox-cloudsql/scripts
cloudshell edit scripts/setup_database.sh

Ensuite, copiez le code suivant dans le fichier scripts/setup_database.sh.

#!/bin/bash
set -e
source .env

echo "================================================"
echo "Database Setup"
echo "================================================"
echo ""

# Step 1: Create Cloud SQL instance
echo "[1/5] Creating Cloud SQL instance..."

# Check if instance already exists
if gcloud sql instances describe "$DB_INSTANCE" --quiet >/dev/null 2>&1; then
    echo "      Instance already exists"
else
    echo "      Creating instance (takes 5-10 minutes)..."
    gcloud sql instances create "$DB_INSTANCE" \
        --database-version=POSTGRES_17 \
        --tier=db-custom-1-3840 \
        --edition=ENTERPRISE \
        --region="$REGION" \
        --root-password="$DB_PASSWORD" \
        --enable-google-ml-integration \
        --database-flags cloudsql.enable_google_ml_integration=on \
        --quiet
fi
echo "      ✓ Instance ready"
echo ""

# Step 2: Verify instance is ready
echo "[2/5] Verifying instance state..."

STATE=$(gcloud sql instances describe "$DB_INSTANCE" --format='value(state)')

if [ "$STATE" != "RUNNABLE" ]; then
    echo "ERROR: Instance not ready (state: $STATE)"
    exit 1
fi
echo "      ✓ Instance is RUNNABLE"
echo ""

# Step 3: Grant IAM permissions
echo "[3/5] Granting Vertex AI permissions..."

SERVICE_ACCOUNT=$(gcloud sql instances describe "$DB_INSTANCE" \
    --format='value(serviceAccountEmailAddress)')

if [ -z "$SERVICE_ACCOUNT" ]; then
    echo "ERROR: Could not retrieve service account"
    exit 1
fi

gcloud projects add-iam-policy-binding "$GOOGLE_CLOUD_PROJECT" \
    --member="serviceAccount:$SERVICE_ACCOUNT" \
    --role="roles/aiplatform.user" \
    --quiet

echo "      ✓ Permissions granted"
echo ""

# Step 4: Create database
echo "[4/5] Creating database..."

# Check if database already exists
if gcloud sql databases describe "$DB_NAME" \
    --instance="$DB_INSTANCE" --quiet >/dev/null 2>&1; then
    echo "      Database already exists"
else
    gcloud sql databases create "$DB_NAME" \
        --instance="$DB_INSTANCE" \
        --quiet
fi

echo "      ✓ Database '$DB_NAME' ready"
echo ""

# Step 5: Seed database and generate embeddings
echo "[5/5] Seeding database and generating embeddings..."

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SETUP_SCRIPT="${SCRIPT_DIR}/setup_restaurant_db.py"

if [ ! -f "$SETUP_SCRIPT" ]; then
    echo "ERROR: Setup script not found: $SETUP_SCRIPT"
    exit 1
fi

uv run "$SETUP_SCRIPT"

echo ""
echo "================================================"
echo "Setup complete!"
echo "================================================"
echo ""

Créer un script Python pour l'amorçage des données

Créez ensuite le fichier Python de script de seeding scripts/setup_restaurant_db.py à l'aide de la commande ci-dessous.

cloudshell edit scripts/setup_restaurant_db.py

Copiez ensuite le code suivant dans le fichier scripts/setup_restaurant_db.py.

import os
import sys
from pathlib import Path
from dotenv import load_dotenv
from google.cloud.sql.connector import Connector
import pg8000
import time

# Load environment variables from .env file
env_path = Path(__file__).parent.parent / '.env'
load_dotenv(env_path)
EMBEDDING_MODEL='gemini-embedding-001'

# Verify required environment variables
required_vars = ['GOOGLE_CLOUD_PROJECT', 'REGION', 'DB_PASSWORD']
missing_vars = [var for var in required_vars if not os.environ.get(var)]

if missing_vars:
    print(f"ERROR: Missing required environment variables: {', '.join(missing_vars)}", file=sys.stderr)
    print(f"", file=sys.stderr)
    print(f"Expected .env file location: {env_path}", file=sys.stderr)
    if not env_path.exists():
        print(f"✗ File not found at that location", file=sys.stderr)
    else:
        print(f"✓ File exists but is missing the variables above", file=sys.stderr)
    print(f"", file=sys.stderr)
    print(f"Make sure your .env file contains:", file=sys.stderr)
    for var in missing_vars:
        print(f"  {var}=<value>", file=sys.stderr)
    sys.exit(1)

# Menu items data
MENU_ITEMS = [
    ("Truffle Mushroom Risotto", "Italian", "Main Course",
     "Arborio rice, truffle oil, porcini mushrooms, parmesan, white wine",
     "$28", "Vegetarian, Gluten-Free", True,
     "A creamy, luxurious risotto made with arborio rice slow-cooked in white wine and mushroom broth, finished with shaved black truffle and aged parmesan. The porcini mushrooms add a deep, earthy flavor that pairs beautifully with the delicate truffle oil drizzled on top."),
    ("Spicy Tuna Tartare", "Japanese", "Appetizer",
     "Ahi tuna, sriracha, sesame oil, avocado, crispy wonton",
     "$22", "Gluten-Free, Dairy-Free", True,
     "Fresh ahi tuna diced and tossed with sriracha aioli, toasted sesame oil, and lime juice, served atop creamy avocado slices with crispy wonton chips. A perfect balance of heat, richness, and crunch inspired by modern Japanese fusion cuisine."),
    ("Lamb Kofta Kebab", "Middle Eastern", "Main Course",
     "Ground lamb, cumin, coriander, yogurt sauce, flatbread",
     "$24", "Halal", True,
     "Hand-formed spiced lamb kebabs grilled over charcoal, seasoned with cumin, coriander, and sumac. Served with warm flatbread, tangy yogurt-cucumber sauce, and a fresh herb salad. A classic Middle Eastern street food elevated with premium ingredients."),
    ("Pad Thai", "Thai", "Main Course",
     "Rice noodles, shrimp, tamarind, peanuts, bean sprouts, lime",
     "$19", "Gluten-Free, Dairy-Free", True,
     "Stir-fried rice noodles with tiger shrimp, scrambled egg, and a sweet-sour tamarind sauce, topped with crushed peanuts, fresh bean sprouts, and a squeeze of lime. This classic Thai street food dish balances sweet, sour, salty, and umami in every bite."),
    ("Margherita Pizza", "Italian", "Main Course",
     "San Marzano tomatoes, fresh mozzarella, basil, olive oil",
     "$18", "Vegetarian", True,
     "A Neapolitan-style pizza with a thin, charred crust topped with crushed San Marzano tomatoes, creamy buffalo mozzarella, fresh basil leaves, and a drizzle of extra virgin olive oil. Simple, classic, and made with imported Italian ingredients."),
    ("Miso Glazed Black Cod", "Japanese", "Main Course",
     "Black cod, white miso, mirin, sake, pickled ginger",
     "$36", "Gluten-Free, Dairy-Free", True,
     "Buttery black cod marinated for 72 hours in a sweet white miso glaze with mirin and sake, then broiled until caramelized. Served with pickled ginger and steamed bok choy. A signature dish inspired by Nobu's iconic preparation."),
    ("Caesar Salad", "American", "Appetizer",
     "Romaine lettuce, parmesan, croutons, anchovy dressing",
     "$14", "Contains Gluten", True,
     "Crisp romaine hearts tossed with a house-made anchovy-garlic dressing, shaved parmesan, and golden sourdough croutons. A timeless salad that serves as the perfect light starter or side dish with grilled proteins."),
    ("Chicken Tikka Masala", "Indian", "Main Course",
     "Chicken thigh, tomato cream sauce, garam masala, basmati rice",
     "$21", "Gluten-Free", True,
     "Tender chunks of tandoori-marinated chicken simmered in a rich, creamy tomato sauce spiced with garam masala, cumin, and fenugreek. Served over fragrant basmati rice with warm garlic naan on the side."),
    ("Chocolate Lava Cake", "French", "Dessert",
     "Dark chocolate, butter, eggs, vanilla, powdered sugar",
     "$15", "Vegetarian", True,
     "A warm, individual-sized chocolate cake with a molten dark chocolate center that flows when you break through the delicate outer shell. Made with 70% Belgian dark chocolate and served with a scoop of vanilla bean ice cream."),
    ("Pho Bo", "Vietnamese", "Main Course",
     "Rice noodles, beef brisket, star anise, cinnamon, bean sprouts, Thai basil",
     "$17", "Gluten-Free, Dairy-Free", True,
     "A deeply aromatic beef broth simmered for 12 hours with star anise, cinnamon, and charred ginger, ladled over rice noodles and thinly sliced beef brisket. Served with fresh Thai basil, bean sprouts, jalapeño, and lime for the table to customize."),
    ("Lobster Bisque", "French", "Appetizer",
     "Lobster, heavy cream, cognac, tarragon, cayenne",
     "$19", "Gluten-Free", True,
     "A velvety smooth soup made from roasted lobster shells, finished with heavy cream, a splash of cognac, and fresh tarragon. Each bowl is garnished with tender lobster meat and a pinch of cayenne for subtle warmth."),
    ("Falafel Plate", "Middle Eastern", "Main Course",
     "Chickpeas, herbs, tahini, pickled vegetables, hummus",
     "$16", "Vegan, Gluten-Free", True,
     "Crispy-on-the-outside, fluffy-on-the-inside chickpea fritters seasoned with fresh parsley, cilantro, and cumin. Served with creamy tahini sauce, house-made hummus, pickled turnips, and warm pita bread."),
    ("Crème Brûlée", "French", "Dessert",
     "Heavy cream, vanilla bean, egg yolks, caramelized sugar",
     "$13", "Vegetarian, Gluten-Free", True,
     "A classic French custard made with Madagascar vanilla bean and farm-fresh egg yolks, topped with a perfectly torched layer of caramelized sugar that cracks with a satisfying snap. Rich, creamy, and elegantly simple."),
    ("Korean BBQ Short Ribs", "Korean", "Main Course",
     "Beef short ribs, soy sauce, sesame, garlic, pear marinade",
     "$32", "Dairy-Free", False,
     "Premium beef short ribs marinated overnight in a sweet and savory blend of soy sauce, Asian pear, garlic, and toasted sesame. Grilled tableside over charcoal and served with lettuce wraps, pickled daikon, and gochujang dipping sauce."),
    ("Tiramisu", "Italian", "Dessert",
     "Mascarpone, espresso, ladyfingers, cocoa, Marsala wine",
     "$14", "Vegetarian, Contains Gluten", True,
     "Layers of espresso-soaked ladyfingers and whipped mascarpone cream flavored with Marsala wine, dusted with premium Dutch cocoa powder. Made fresh daily and chilled for 24 hours to develop rich, complex flavors."),
]


def get_connection():
    """Create a connection to Cloud SQL using the connector."""
    project = os.environ['GOOGLE_CLOUD_PROJECT']
    region = os.environ['REGION']
    password = os.environ['DB_PASSWORD']
    instance = os.environ['DB_INSTANCE']
    database = os.environ['DB_NAME']

    connector = Connector()
    conn = connector.connect(
        f"{project}:{region}:{instance}",
        "pg8000",
        user="postgres",
        password=password,
        db=database
    )
    return conn, connector


def create_schema(cursor):
    """Create extensions and menu_items table."""
    cursor.execute("CREATE EXTENSION IF NOT EXISTS google_ml_integration")
    cursor.execute("CREATE EXTENSION IF NOT EXISTS vector")
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS menu_items (
            id SERIAL PRIMARY KEY,
            name VARCHAR NOT NULL,
            cuisine_type VARCHAR NOT NULL,
            category VARCHAR NOT NULL,
            ingredients VARCHAR NOT NULL,
            price VARCHAR NOT NULL,
            dietary_tags VARCHAR NOT NULL,
            available BOOLEAN NOT NULL DEFAULT TRUE,
            description TEXT NOT NULL,
            description_embedding vector(3072)
        )
    """)


def seed_menu_items(cursor, conn):
    """Insert menu items."""
    cursor.execute("SELECT COUNT(*) FROM menu_items")
    existing_count = cursor.fetchone()[0]

    if existing_count > 0:
        print(f"      {existing_count} menu items already exist, skipping seed")
        return 0

    cursor.executemany("""
        INSERT INTO menu_items (name, cuisine_type, category, ingredients, price, dietary_tags, available, description)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
    """, MENU_ITEMS)
    conn.commit()
    return len(MENU_ITEMS)


def generate_embeddings(cursor, conn):
    """Generate embeddings using Cloud SQL's embedding() function."""
    cursor.execute("SELECT COUNT(*) FROM menu_items WHERE description_embedding IS NULL")
    null_count = cursor.fetchone()[0]

    if null_count == 0:
        print("      All menu items already have embeddings")
        return 0

    cursor.execute(f"""
        UPDATE menu_items
        SET description_embedding = embedding('{EMBEDDING_MODEL}', description)::vector
        WHERE description_embedding IS NULL
    """)
    rows_updated = cursor.rowcount
    conn.commit()
    return rows_updated


def main():
    conn, connector = get_connection()
    cursor = conn.cursor()

    try:
        create_schema(cursor)
        conn.commit()

        seeded = seed_menu_items(cursor, conn)
        if seeded > 0:
            print(f"      ✓ Inserted {seeded} menu items")

        # Waiting for vertex role propagation
        time.sleep(60)
        embedded = generate_embeddings(cursor, conn)
        if embedded > 0:
            print(f"      ✓ Generated {embedded} embeddings")

    except Exception as e:
        print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)
    finally:
        cursor.close()
        conn.close()
        connector.close()


if __name__ == "__main__":
    main()

Passons maintenant à l'étape suivante.

4. Créer et initialiser la base de données

Nos scripts sont maintenant prêts à être exécutés. Nous aurons besoin de Python pour exécuter notre script préparé. Préparons-le d'abord.

Configurer le projet Python

uv est un gestionnaire de packages et de projets Python rapide écrit en Rust ( documentation uv ). Cet atelier de programmation l'utilise pour la rapidité et la simplicité de la maintenance du projet Python.

Initialisez un projet Python et ajoutez les dépendances requises :

uv init
uv add cloud-sql-python-connector --extra pg8000
uv add python-dotenv

Notez que nous utilisons ici le SDK Python cloud-sql-python-connector pour initialiser une connexion sécurisée avec notre instance de base de données, qui est authentifiée à l'aide des identifiants par défaut de l'application.

Exécuter le script de configuration

Nous pouvons maintenant exécuter le script d'installation en arrière-plan et inspecter la sortie de la console qui sera écrite dans le fichier logs/atabase_setup.log à l'aide de la commande suivante. Vous pouvez passer à la section suivante en attendant la fin de cette opération.

mkdir -p ~/build-agent-adk-toolbox-cloudsql/logs
bash scripts/setup_database.sh > logs/database_setup.log 2>&1 &

Télécharger le fichier binaire de la boîte à outils

Dans ce tutoriel, nous allons utiliser MCP Toolbox. Heureusement, il est fourni avec un fichier binaire prédéfini qui est prêt à être utilisé dans l'environnement Linux. Maintenant, téléchargeons-le en arrière-plan, car cela prendra un certain temps. Exécutez la commande suivante pour télécharger le fichier binaire et inspecter le journal de sortie sur le logs/toolbox_dl.log . Vous pouvez passer à la section suivante en attendant la fin de cette opération.

cd ~/build-agent-adk-toolbox-cloudsql
curl -O https://storage.googleapis.com/mcp-toolbox-for-databases/v1.1.0/linux/amd64/toolbox > logs/toolbox_dl.log 2>&1 &

Comprendre le script de configuration scripts/setup_database.sh

Essayons maintenant de comprendre le script de configuration que nous avons configuré précédemment. Il effectue les opérations suivantes :

  1. La toute première commande que nous exécutons est la commande gcloud sql instances create avec l'option suivante :
  • db-custom-1-3840 est le plus petit niveau Cloud SQL à cœur dédié (1 vCPU, 3,75 Go de RAM) de l'édition ENTERPRISE.Pour en savoir plus, cliquez ici. Un cœur dédié est requis pour l'intégration de Vertex AI ML. Les niveaux à cœur partagé (db-f1-micro, db-g1-small) ne sont pas compatibles.
  • --root-password définit le mot de passe de l'utilisateur postgres par défaut.
  • --enable-google-ml-integration active l'intégration intégrée de Cloud SQL à Vertex AI, ce qui vous permet d'appeler des modèles d'embedding directement depuis SQL à l'aide de la fonction embedding().
  1. Vérifiez si l'instance est déjà à l'état RUNNABLE.
  2. Accordez au compte de service de l'instance Cloud SQL l'autorisation d'appeler Vertex AI à l'aide de la commande gcloud projects add-iam-policy-binding. Cette opération est requise pour la fonction embedding() intégrée que nous utiliserons pour initialiser la base de données.
  3. Créer la base de données
  4. Exécuter le script d'amorçage setup_restaurant_db.py

Comprendre le script d'amorçage scripts/setup_restaurant_db.py

Passons maintenant au script de seeding. Il effectue les opérations suivantes :

  1. Initialiser la connexion à l'instance de base de données
  2. Installe deux extensions PostgreSQL :
  • google_ml_integration : fournit la fonction SQL embedding(), qui appelle les modèles d'embedding Vertex AI directement à partir de SQL. Il s'agit d'une extension au niveau de la base de données qui rend les fonctions de ML disponibles dans restaurant_db. Le flag au niveau de l'instance (--enable-google-ml-integration) que vous définissez lors de la création de l'instance permet à la VM Cloud SQL d'accéder à Vertex AI. L'extension rend les fonctions SQL disponibles dans cette base de données spécifique.
  • vector (pgvector) : ajoute le type de données vector et les opérateurs de distance pour stocker et interroger les embeddings.
  1. Créez la table et notez que la colonne description_embedding est vector(3072), c'est-à-dire une colonne pgvector qui stocke des vecteurs de dimension 3072.
  2. Déplacer les données des éléments de menu initiaux
  3. Générez les données d'embedding à partir du champ description et remplissez description_embedding à l'aide de l'intégration Vertex intégrée via la fonction embedding().
  • embedding('gemini-embedding-001', description) : appelle le modèle d'embedding Gemini de Vertex AI directement depuis SQL, en transmettant le texte description de chaque offre d'emploi. Il s'agit de l'extension google_ml_integration que vous avez installée dans le script de seed.
  • ::vector : convertit le tableau float renvoyé au type vector de pgvector afin qu'il puisse être stocké et interrogé avec des opérateurs de distance.
  • UPDATE s'exécute sur les 15 lignes, générant un embedding de dimension 3072 par description de poste.

Cela préparera les données initiales auxquelles notre agent accédera.

5. Configurer MCP Toolbox for Databases

Cette étape présente MCP Toolbox for Databases, le configure pour qu'il se connecte à votre instance Cloud SQL et définit deux outils de requête SQL standards.

Qu'est-ce que MCP et pourquoi utiliser Toolbox ?

e7b9be2e1c98b4db.png

MCP (Model Context Protocol) est un protocole ouvert qui standardise la façon dont les agents d'IA découvrent les outils externes et interagissent avec eux. Il définit un modèle client-serveur : l'agent héberge un client MCP et les outils sont exposés par les serveurs MCP. N'importe quel client compatible avec MCP peut utiliser n'importe quel serveur compatible avec MCP. L'agent n'a pas besoin de code d'intégration personnalisé pour chaque outil.

5bf26eeecad2277d.png

MCP Toolbox for Databases est un serveur MCP Open Source conçu spécifiquement pour l'accès aux bases de données. Sans cela, vous devriez écrire des fonctions Python qui ouvrent des connexions à la base de données, gèrent les pools de connexions, construisent des requêtes paramétrées pour éviter les injections SQL, gèrent les erreurs et intègrent tout ce code dans votre agent. Chaque agent ayant besoin d'accéder à la base de données répète cette opération. Pour modifier une requête, vous devez redéployer l'agent.

Avec Toolbox, vous écrivez un fichier YAML. Chaque outil correspond à une instruction SQL paramétrée. La boîte à outils gère le regroupement des connexions, les requêtes paramétrées, l'authentification et l'observabilité. Les outils sont dissociés de l'agent. Vous pouvez mettre à jour une requête en modifiant tools.yaml et en redémarrant la boîte à outils, sans toucher au code de l'agent. Les mêmes outils fonctionnent avec ADK, LangGraph, LlamaIndex ou tout framework compatible avec MCP.

Écrire la configuration des outils

Nous devons maintenant créer un fichier nommé tools.yaml dans l'éditeur Cloud Shell pour configurer nos outils.

cloudshell edit tools.yaml

Le fichier utilise le format YAML multidocument : chaque bloc séparé par --- est une ressource autonome. Chaque ressource comporte un kind qui déclare ce qu'elle est (sources pour les connexions à la base de données, tools pour les actions appelables par l'agent) et un type qui spécifie le backend (cloud-sql-postgres pour la source, postgres-sql pour les outils basés sur SQL). Un outil fait référence à sa source par name, ce qui permet à Toolbox de savoir quel pool de connexions exécuter. Les variables d'environnement utilisent la syntaxe ${VAR_NAME} et sont résolues au démarrage.

Copions d'abord les scripts suivants dans le fichier tools.yaml.

# tools.yaml

# --- Data Source ---
kind: source
name: restaurant-db
type: cloud-sql-postgres
project: ${GOOGLE_CLOUD_PROJECT}
region: ${REGION}
instance: ${DB_INSTANCE}
database: ${DB_NAME}
user: postgres
password: ${DB_PASSWORD}

---

Ce script définit la ressource suivante :

  • Source (restaurant-db) : indique à Toolbox comment se connecter à votre instance Cloud SQL pour PostgreSQL. Le type cloud-sql-postgres utilise le connecteur Cloud SQL en interne, en gérant automatiquement l'authentification et les connexions sécurisées. Les espaces réservés ${GOOGLE_CLOUD_PROJECT} , ${REGION} et ${DB_PASSWORD} sont résolus à partir des variables d'environnement au démarrage.

Ensuite, ajoutez le script suivant sous le symbole --- dans tools.yaml.

 
# --- Tool 1: Search menu items by category and/or cuisine type ---
kind: tool
name: search-menu
type: postgres-sql
source: restaurant-db
description: >-
  Search for menu items by category and/or cuisine type.
  Use this tool when the user wants to browse menu items
  by category (e.g., Main Course, Appetizer, Dessert) or find dishes
  from a specific cuisine. Both parameters accept an
  empty string to match all values.
statement: |
  SELECT name, cuisine_type, category, ingredients, price, dietary_tags, available
  FROM menu_items
  WHERE ($1 = '' OR LOWER(category) = LOWER($1))
  AND ($2 = '' OR LOWER(cuisine_type) LIKE '%' || LOWER($2) || '%')
  ORDER BY name
  LIMIT 10
parameters:
  - name: category
    type: string
    description: "The menu category to filter by (e.g., 'Main Course', 'Appetizer', 'Dessert'). Use empty string for all categories."
  - name: cuisine_type
    type: string
    description: "A cuisine type to search for (partial match, e.g., 'Italian', 'Japanese'). Use empty string for all cuisines."

---

# --- Tool 2: Get full details for a specific menu item ---
kind: tool
name: get-item-details
type: postgres-sql
source: restaurant-db
description: >-
  Get full details for a specific menu item including its description,
  price, dietary tags, and availability. Use this tool when the
  user asks about a particular dish by name or cuisine.
statement: |
  SELECT name, cuisine_type, category, ingredients, price, dietary_tags, available, description
  FROM menu_items
  WHERE LOWER(name) LIKE '%' || LOWER($1) || '%'
  OR LOWER(cuisine_type) LIKE '%' || LOWER($1) || '%'
parameters:
  - name: search_term
    type: string
    description: "The dish name or cuisine type to look up (partial match supported)."

---

Ce script définit la ressource suivante :

  • Outils 1 et 2 (search-menu, get-item-details) : outils de requête SQL standard. Chacun mappe un nom d'outil (ce que l'agent voit) à une instruction SQL paramétrée (ce que la base de données exécute). Les paramètres utilisent des espaces réservés positionnels $1 et $2. Toolbox les exécute en tant qu'instructions préparées, ce qui empêche l'injection SQL.

Continuons. Ajoutez le script suivant sous le symbole --- dans le fichier tools.yaml.

 
# --- Embedding Model ---
kind: embeddingModel
name: gemini-embedding
type: gemini
model: gemini-embedding-001
project: ${GOOGLE_CLOUD_PROJECT}
location: ${GOOGLE_CLOUD_LOCATION}
dimension: 3072

---

Ce script définit la ressource suivante :

  • Modèle d'embedding (gemini-embedding) : configure la boîte à outils pour appeler le modèle gemini-embedding-001 de Gemini afin de générer des embeddings de texte à 3 072 dimensions. La boîte à outils utilise les identifiants par défaut de l'application (ADC) pour l'authentification. Aucune clé API n'est requise dans Cloud Shell ni dans Cloud Run. Note that this dimension configured here must be the same with previously we config to seed the database

Continuons. Ajoutez le script suivant sous le symbole --- dans le fichier tools.yaml.

 
# --- Tool 3: Semantic search by description ---
kind: tool
name: search-menu-by-description
type: postgres-sql
source: restaurant-db
description: >-
  Find menu items that match a natural language description of what the user
  is looking for. Use this tool when the user describes their ideal dish
  using flavors, textures, dietary preferences, or cravings rather than a
  specific category or cuisine. Examples: "I want something spicy and creamy,"
  "a light vegetarian appetizer," "something rich and chocolatey for dessert."
statement: |
  SELECT name, cuisine_type, category, ingredients, price, dietary_tags, description
  FROM menu_items
  WHERE description_embedding IS NOT NULL
  ORDER BY description_embedding <=> $1
  LIMIT 5
parameters:
  - name: search_query
    type: string
    description: "A natural language description of the kind of dish the user is looking for."
    embeddedBy: gemini-embedding

---

Ce script définit la ressource suivante :

  • Outil 3 (search-menu-by-description) : outil de recherche vectorielle. Le paramètre search_query a la valeur embeddedBy: gemini-embedding, ce qui indique à Toolbox d'intercepter le texte brut, de l'envoyer au modèle d'embedding et d'utiliser le vecteur résultant dans l'instruction SQL. L'opérateur <=> est la distance cosinus de pgvector. Plus les valeurs sont petites, plus les descriptions sont similaires.

Enfin, ajoutez le dernier outil sous le symbole --- dans tools.yaml.

 
# --- Tool 4: Add a new menu item with automatic embedding ---
kind: tool
name: add-menu-item
type: postgres-sql
source: restaurant-db
description: >-
  Add a new menu item to the restaurant. Use this tool when a user asks
  to add a dish that is not currently on the menu.
statement: |
  INSERT INTO menu_items (name, cuisine_type, category, ingredients, price, dietary_tags, available, description, description_embedding)
  VALUES ($1, $2, $3, $4, $5, $6, CAST($7 AS BOOLEAN), $8, $9)
  RETURNING name, cuisine_type
parameters:
  - name: name
    type: string
    description: "The dish name (e.g., 'Truffle Mushroom Risotto')."
  - name: cuisine_type
    type: string
    description: "The cuisine type (e.g., 'Italian', 'Japanese', 'Thai')."
  - name: category
    type: string
    description: "The menu category (e.g., 'Main Course', 'Appetizer', 'Dessert')."
  - name: ingredients
    type: string
    description: "Comma-separated list of key ingredients (e.g., 'salmon, miso, ginger')."
  - name: price
    type: string
    description: "The price (e.g., '$24')."
  - name: dietary_tags
    type: string
    description: "Dietary information (e.g., 'Vegetarian, Gluten-Free')."
  - name: available
    type: string
    description: "Whether the dish is currently available (true or false)."
  - name: description
    type: string
    description: "A short description of the dish (2-3 sentences)."
  - name: description_vector
    type: string
    description: "Auto-generated embedding vector for the dish description."
    valueFromParam: description
    embeddedBy: gemini-embedding

Ce script définit la ressource suivante :

  • Outil 4 (add-menu-item) : montre l'ingestion de vecteurs. Le paramètre description_vector comporte deux champs spéciaux :
  • valueFromParam: description : la boîte à outils copie la valeur du paramètre description dans celui-ci. Le LLM ne voit jamais ce paramètre.
  • embeddedBy: gemini-embedding : la boîte à outils intègre le texte copié dans un vecteur avant de le transmettre au code SQL.

Résultat : un appel d'outil stocke à la fois le texte de description brut et son embedding vectoriel, sans que l'agent n'ait aucune connaissance des embeddings.

Le format YAML multidocument sépare chaque ressource par ---. Chaque document comporte des champs kind, name et type qui définissent ce qu'il est. En résumé, nous avons déjà configuré tous les éléments suivants :

  • Définir la base de données source
  • Définir des outils ( outil 1 et outil 2) pour interroger la base de données avec un filtre standard
  • Définir le modèle d'embedding
  • Définir l'outil de recherche vectorielle ( outil 3) pour la base de données
  • Définissez l'outil d'ingestion de données vectorielles ( outil 4) dans la base de données.

6. Exécuter le serveur MCP Toolbox

À l'étape précédente, nous avons déjà défini la configuration nécessaire pour notre MCP Toolbox. Nous sommes maintenant prêts à exécuter le serveur.

Vérifier les données d'amorçage

Avant de démarrer Toolbox, vérifions que la configuration de la base de données est terminée. Créez un script Python scripts/verify_database.py à l'aide de la commande suivante :

cloudshell edit scripts/verify_seed.py

Copiez ensuite le code suivant dans le fichier scripts/verify_seed.py.

#!/usr/bin/env python3
"""Verify the database has 15 menu items with embeddings."""

import os
import sys
from pathlib import Path
from dotenv import load_dotenv
from google.cloud.sql.connector import Connector
import pg8000

# Load environment variables
env_path = Path(__file__).parent.parent / '.env'
load_dotenv(env_path)

# Verify required environment variables
required_vars = ['GOOGLE_CLOUD_PROJECT', 'REGION', 'DB_PASSWORD', 'DB_INSTANCE', 'DB_NAME']
missing_vars = [var for var in required_vars if not os.environ.get(var)]

if missing_vars:
    print(f"ERROR: Missing environment variables: {', '.join(missing_vars)}", file=sys.stderr)
    sys.exit(1)


def verify_database():
    """Check that 15 menu items exist with embeddings."""
    connector = Connector()

    try:
        project = os.environ['GOOGLE_CLOUD_PROJECT']
        region = os.environ['REGION']
        password = os.environ['DB_PASSWORD']
        instance = os.environ['DB_INSTANCE']
        database = os.environ['DB_NAME']

        conn = connector.connect(
            f"{project}:{region}:{instance}",
            "pg8000",
            user="postgres",
            password=password,
            db=database
        )
        cursor = conn.cursor()

        # Count menu items and embeddings
        cursor.execute("SELECT COUNT(*) FROM menu_items")
        item_count = cursor.fetchone()[0]

        cursor.execute("SELECT COUNT(*) FROM menu_items WHERE description_embedding IS NOT NULL")
        embedding_count = cursor.fetchone()[0]

        print(f"Menu Items: {item_count}/15")
        print(f"Embeddings: {embedding_count}/15")

        cursor.close()
        conn.close()

        if item_count == 15 and embedding_count == 15:
            print("\n✓ Database ready!")
            return True
        else:
            print("\n✗ Database not ready")
            return False

    except Exception as e:
        print(f"\nERROR: {e}", file=sys.stderr)
        return False
    finally:
        connector.close()


if __name__ == "__main__":
    success = verify_database()
    sys.exit(0 if success else 1)

Ce script vérifie le nombre de données sur les articles de menu et leur intégration. Exécutez le script à l'aide de la commande suivante :

uv run scripts/verify_seed.py

Si le résultat suivant s'affiche dans le terminal, cela signifie que les données sont prêtes.

Menu Items: 15/15
Embeddings: 15/15

✓ Database ready!

Démarrer le serveur de la boîte à outils

Lors de l'étape de configuration précédente, nous avons déjà téléchargé l'exécutable toolbox. Assurez-vous que ce fichier binaire existe et qu'il a été téléchargé correctement. Si ce n'est pas le cas, téléchargez-le et attendez la fin du téléchargement.

cd ~/build-agent-adk-toolbox-cloudsql
if [ ! -f toolbox ]; then
  curl -O https://storage.googleapis.com/mcp-toolbox-for-databases/v1.1.0/linux/amd64/toolbox
fi
chmod +x toolbox

Nous devrons exposer nos variables .env au processus enfant exécuté par la boîte à outils MCP. Exécutez la commande suivante pour démarrer le serveur de la boîte à outils et consigner la sortie de la console dans le fichier logs/mcp_toolbox.log.

set -a; source .env; set +a
./toolbox --config tools.yaml --enable-api > logs/mcp_toolbox.log 2>&1 &

Dans le fichier logs/mcp_toolbox.log, vous devriez voir un résultat confirmant que le serveur est prêt, comme indiqué ci-dessous :

... INFO "Initialized 1 sources: restaurant-db"
... INFO "Initialized 0 authServices: "
... INFO "Using Vertex AI backend for Gemini embedding" 
... INFO "Initialized 1 embeddingModels: gemini-embedding" 
... INFO "Initialized 4 tools: search-menu-by-description, add-menu-item, search-menu, get-item-details"
...
... INFO "Server ready to serve!"

Vérifier les outils

Interrogez l'API Toolbox pour lister tous les outils enregistrés :

curl -s http://localhost:5000/api/toolset | uv run -m json.tool

Vous devriez voir les outils avec leurs descriptions et leurs paramètres. Comme indiqué ci-dessous

...
       "search-menu-by-description": {
            "description": "Find menu items that match a natural language description of what the user is looking for. Use this tool when the user describes their ideal dish using flavors, textures, dietary preferences, or cravings rather than a specific category or cuisine. Examples: \"I want something spicy and creamy,\" \"a light vegetarian appetizer,\" \"something rich and chocolatey for dessert.\"",
            "parameters": [
                {
                    "name": "search_query",
                    "type": "string",
                    "required": true,
                    "description": "A natural language description of the kind of dish the user is looking for.",
                    "authServices": []
                }
            ],
            "authRequired": []
        }
...

Testez directement l'outil search-menu :

curl -s -X POST http://localhost:5000/api/tool/search-menu/invoke \\   -H "Content-Type: application/json" \\   -d '{"category": "Main Course", "cuisine_type": "Italian"}' | jq '.result | fromjson'

La réponse doit contenir les plats principaux italiens de vos données de départ.

[
  {
    "name": "Margherita Pizza",
    "cuisine_type": "Italian",
    "category": "Main Course",
    "ingredients": "San Marzano tomatoes, fresh mozzarella, basil, olive oil",
    "price": "$18",
    "dietary_tags": "Vegetarian",
    "available": true
  },
  {
    "name": "Truffle Mushroom Risotto",
    "cuisine_type": "Italian",
    "category": "Main Course",
    "ingredients": "Arborio rice, truffle oil, porcini mushrooms, parmesan, white wine",
    "price": "$28",
    "dietary_tags": "Vegetarian, Gluten-Free",
    "available": true
  }
]

7. Créer l'agent ADK

Nous allons maintenant utiliser ADK en Python pour ce projet. Ajoutons les dépendances requises :

uv add google-adk==1.29.0 toolbox-adk==1.0.0
  • google-adk : Agent Development Kit de Google, y compris le SDK Gemini
  • toolbox-adk : intégration ADK pour MCP Toolbox for Databases.

Créer la structure de répertoires de l'agent

L'ADK s'attend à une structure de dossier spécifique : un répertoire nommé d'après votre agent contenant __init__.py, agent.py et .env. Pour vous aider, il dispose d'une commande intégrée permettant d'établir rapidement la structure :

uv run adk create restaurant_agent \
    --model gemini-3.5-flash \
    --project ${GOOGLE_CLOUD_PROJECT} \
    --region ${GOOGLE_CLOUD_LOCATION}

Votre répertoire devrait maintenant se présenter comme suit :

build-agent-adk-toolbox-cloudsql/
├── restaurant_agent/
│   ├── __init__.py
│   ├── agent.py
│   └── .env
├── logs
├── scripts
└── ...

Nous devons ensuite intégrer l'agent ADK au serveur Toolbox en cours d'exécution et tester les quatre outils : requêtes standards, recherche sémantique et ingestion de vecteurs. Le code de l'agent est minimal : toute la logique de la base de données se trouve dans tools.yaml.

Configurer l'environnement de l'agent

L'ADK lit GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_CLOUD_PROJECT et GOOGLE_CLOUD_LOCATION à partir de l'environnement de shell, que vous avez déjà défini à l'étape précédente. La seule variable spécifique à l'agent est TOOLBOX_URL. Ajoutez-la au fichier .env de l'agent :

echo -e "\nTOOLBOX_URL=http://127.0.0.1:5000" >> restaurant_agent/.env

Mettre à jour le module d'agent

Ouvrez restaurant_agent/agent.py dans l'éditeur Cloud Shell.

cloudshell edit restaurant_agent/agent.py

et remplacez le contenu par le code suivant :

# restaurant_agent/agent.py
import os

from google.adk.agents import LlmAgent
from toolbox_adk import ToolboxToolset

TOOLBOX_URL = os.environ.get("TOOLBOX_URL", "http://127.0.0.1:5000")

toolbox = ToolboxToolset(TOOLBOX_URL)

root_agent = LlmAgent(
    name="restaurant_agent",
    model="gemini-3.5-flash",
    instruction="""You are a friendly and knowledgeable concierge at "Foodie Finds," a restaurant. Your job:
- Help diners browse the menu by category or cuisine type.
- Provide full details about specific dishes, including ingredients, price, and dietary information.
- Recommend dishes based on natural language descriptions of what the diner is craving.
- Add new menu items when asked.

When a diner asks about a specific dish by name or cuisine, use the get-item-details tool.
When a diner asks for a specific category or cuisine type, use the search-menu tool.
When a diner describes what kind of food they want — by flavor, texture, dietary needs, or cravings — use the search-menu-by-description tool for semantic search.

When in doubt between search-menu and search-menu-by-description, prefer search-menu-by-description — it searches dish descriptions and finds more relevant matches.
If a dish is not available (available is false), let the diner know and suggest similar alternatives from the search results.
Be conversational, knowledgeable, and concise.""",
    tools=[toolbox],
)

Notez qu'il n'y a pas de code de base de données ici : ToolboxToolset se connecte au serveur de la boîte à outils au démarrage et charge tous les outils disponibles. L'agent appelle les outils par leur nom. Toolbox traduit ces appels en requêtes SQL sur Cloud SQL.

La variable d'environnement TOOLBOX_URL est définie par défaut sur http://127.0.0.1:5000 pour le développement local. Lorsque vous déploierez sur Cloud Run ultérieurement, vous remplacerez cette valeur par l'URL Cloud Run du service Toolbox. Aucune modification de code n'est nécessaire.

Tester l'agent

Démarrez l'UI de développement ADK :

cd ~/build-agent-adk-toolbox-cloudsql
uv run adk web --allow_origins "regex:https://.*\.cloudshell\.dev"

Ouvrez l'URL affichée dans le terminal (généralement http://localhost:8000) à l'aide de la fonctionnalité Aperçu sur le Web de Cloud Shell ou en cliquant sur l'URL tout en appuyant sur la touche Ctrl. Sélectionnez restaurant_agent dans le menu déroulant des agents en haut à gauche.

Tester les requêtes standards

Essayez ces requêtes pour vérifier les outils SQL standard :

What Italian dishes do you have?
Tell me about the Miso Glazed Black Cod

99807eceeb0212f6.png

834580f0aa4c5106.png

Essayez des descriptions en langage naturel qui ne correspondent pas à un rôle ou à une pile technologique spécifiques :

I want something spicy and creamy
Something rich and chocolatey for dessert
I'm in the mood for something light and healthy

L'agent essaiera de choisir le bon outil en fonction du type de requête : les filtres structurés passent par search-menu, tandis que les descriptions en langage naturel passent par search-menu-by-description.

2b9ed5d90c5990c6.png

Tester l'ingestion de vecteurs

Demandez à l'agent d'ajouter un job :

Add a new dish: 'Seared Duck Breast' cuisine type French, category Main Course, ingredients: duck breast, cherry reduction, roasted root vegetables, thyme, price $34, dietary tags: Gluten-Free Dairy-Free, available true. Description: A perfectly seared duck breast with crispy skin, served with a tart cherry reduction sauce and a medley of roasted root vegetables. The duck is cooked sous vide for tender, pink meat,
then
finished in a cast iron skillet for maximum crispiness.

f7a96ca8c37e7c56.png

Essayez maintenant de le rechercher :

Find me something with rich, gamey flavors and fruit sauce

L'embedding a été généré automatiquement lors de l'insertion. Aucune étape distincte n'est nécessaire.

be78e692c056596e.png

Vous disposez désormais d'une application RAG agentique entièrement fonctionnelle utilisant ADK, MCP Toolbox et Cloud SQL. Félicitations ! Allons plus loin et déployons ces applications sur Cloud Run.

Maintenant, arrêtons l'UI de développement en arrêtant le processus en appuyant deux fois sur Ctrl+C avant de continuer.

8. Déployer dans Cloud Run

L'agent et la boîte à outils fonctionnent en local. Cette étape déploie les deux en tant que services Cloud Run afin qu'ils soient accessibles sur Internet. Le service Toolbox s'exécute en tant que serveur MCP sur Cloud Run, auquel se connecte le service d'agent.

Préparer la boîte à outils pour le déploiement

Créez un répertoire de déploiement pour le service Toolbox :

cd ~/build-agent-adk-toolbox-cloudsql
mkdir -p deploy-toolbox
cp toolbox tools.yaml deploy-toolbox/

Créez le fichier Dockerfile pour la boîte à outils. Ouvrez deploy-toolbox/Dockerfile dans l'éditeur Cloud Shell :

cloudshell edit deploy-toolbox/Dockerfile

Copiez-y le script suivant :

# deploy-toolbox/Dockerfile
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY toolbox tools.yaml ./
RUN chmod +x toolbox
EXPOSE 8080
CMD ["./toolbox", "--config", "tools.yaml", "--enable-api", "--address", "0.0.0.0", "--port", "8080"]

Le binaire Toolbox et tools.yaml sont inclus dans une image Debian minimale. Cloud Run achemine le trafic vers le port 8080.

Déployer le service de boîte à outils

cd ~/build-agent-adk-toolbox-cloudsql
gcloud run deploy toolbox-service \
  --source deploy-toolbox/ \
  --region $REGION \
  --set-env-vars "DB_PASSWORD=$DB_PASSWORD,DB_INSTANCE=$DB_INSTANCE,DB_NAME=$DB_NAME,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,REGION=$REGION,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION" \
  --allow-unauthenticated \
  --quiet > logs/deploy_toolbox.log 2>&1 &

Cette commande envoie la source à Cloud Build, crée une image de conteneur, la transmet à Artifact Registry et la déploie sur Cloud Run. Cela prendra quelques minutes. Nous pouvons inspecter le journal du processus de déploiement dans le fichier logs/deploy_toolbox.log.

Préparer l'agent pour le déploiement

Pendant la compilation de la boîte à outils, configurez les fichiers de déploiement de l'agent.

Créez un fichier Dockerfile à la racine du projet. Ouvrez Dockerfile dans l'éditeur Cloud Shell :

cloudshell edit Dockerfile

Copiez ensuite le contenu suivant :

# Dockerfile
FROM ghcr.io/astral-sh/uv:python3.12-trixie-slim
WORKDIR /app
COPY pyproject.toml ./
COPY uv.lock ./
RUN uv sync --no-dev
COPY restaurant_agent/ restaurant_agent/
EXPOSE 8080
CMD ["uv", "run", "adk", "web", "--host", "0.0.0.0", "--port", "8080"]

Ce fichier Dockerfile utilise ghcr.io/astral-sh/uv comme image de base, qui inclut Python et uv préinstallés. Il n'est donc pas nécessaire d'installer uv séparément via pip.

Créez un fichier .dockerignore pour exclure les fichiers inutiles de l'image de conteneur :

cloudshell edit .dockerignore

Copiez ensuite le script suivant dans le fichier.

# .dockerignore
.venv/
__pycache__/
*.pyc
.env
restaurant_agent/.env
toolbox
tools.yaml
deploy-toolbox/

Déployer le service d'agent

Attendez que le déploiement de Toolbox soit terminé. Vérifiez à nouveau le processus de déploiement sur logs/deploy_toolbox.log pour le valider. Récupérez ensuite son URL Cloud Run à l'aide de la commande suivante :

TOOLBOX_URL=$(gcloud run services describe toolbox-service \
  --region=$REGION \
  --format='value(status.url)')
echo "Toolbox URL: $TOOLBOX_URL"

Vous obtiendrez un résultat semblable à celui-ci :

Toolbox URL: https://toolbox-service-xxxxxx-xx.a.run.app

Vérifions ensuite que la boîte à outils déployée fonctionne :

curl -s "$TOOLBOX_URL/api/toolset" | python3 -m json.tool | head -5

Si le résultat est semblable à cet exemple, le déploiement a déjà réussi.

{
    "serverVersion": "1.1.0+binary.linux.amd64.da6f5f8",
    "tools": {
        "add-menu-item": {
            "description": "Add a new menu item to the restaurant. Use this tool when a user asks to add a dish that is not currently on the menu.",

Ensuite, déployons l'agent en transmettant l'URL de la boîte à outils en tant que variable d'environnement :

cd ~/build-agent-adk-toolbox-cloudsql
gcloud run deploy restaurant-agent \
  --source . \
  --region $REGION \
  --set-env-vars "TOOLBOX_URL=$TOOLBOX_URL,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION,GOOGLE_GENAI_USE_VERTEXAI=TRUE" \
  --allow-unauthenticated \
  --quiet

Le code de l'agent lit TOOLBOX_URL à partir de l'environnement (que vous avez configuré précédemment). Localement, il pointe vers http://127.0.0.1:5000. Sur Cloud Run, il pointe vers l'URL du service Toolbox. Aucune modification de code n'est nécessaire.

Tester l'agent déployé

Récupérez l'URL Cloud Run de l'agent :

AGENT_URL=$(gcloud run services describe restaurant-agent \
  --region=$REGION \
  --format='value(status.url)')
echo "Agent URL: $AGENT_URL"

Ouvrez l'URL dans votre navigateur. L'UI de développement ADK se charge. Il s'agit de la même interface que celle que vous avez utilisée en local, mais elle s'exécute désormais sur Cloud Run.

Sélectionnez restaurant_agent dans le menu déroulant et testez :

What Italian dishes do you have?
I want something spicy and creamy

Les deux requêtes fonctionnent via les services déployés : l'agent sur Cloud Run appelle la boîte à outils sur Cloud Run, qui interroge Cloud SQL.

9. Félicitations / Nettoyage

Vous avez créé et déployé un assistant de menu de restaurant intelligent qui utilise MCP Toolbox for Databases pour faire le lien entre un agent ADK et Cloud SQL PostgreSQL, à la fois avec des requêtes SQL standards et une recherche vectorielle sémantique.

Connaissances acquises

  • Comment MCP standardise l'accès aux outils pour les agents d'IA et comment MCP Toolbox for Databases applique cela spécifiquement aux opérations de base de données, en remplaçant le code de base de données personnalisé par une configuration YAML déclarative
  • Configurer Cloud SQL PostgreSQL comme source de données Boîte à outils à l'aide du type de source cloud-sql-postgres
  • Définir des outils de requête SQL standard avec des instructions paramétrées qui empêchent l'injection SQL
  • Activer la recherche vectorielle à l'aide de pgvector et de gemini-embedding-001, avec le paramètre embeddedBy pour l'embedding automatique des requêtes
  • Comment valueFromParam permet l'ingestion automatique de vecteurs : le LLM fournit une description textuelle, et la boîte à outils copie, intègre et stocke silencieusement le vecteur à côté du texte.
  • Comment ToolboxToolset d'ADK charge-t-il les outils à partir d'un serveur Toolbox en cours d'exécution, en gardant le code de l'agent minimal et la logique de la base de données entièrement découplée ?
  • Déployer le serveur MCP Toolbox et l'agent ADK sur Cloud Run en tant que services distincts

Nettoyage

Pour éviter que les ressources créées dans cet atelier de programmation ne soient facturées sur votre compte Google Cloud, vous pouvez supprimer les ressources individuelles ou l'intégralité du projet.

Le moyen le plus simple de libérer de l'espace consiste à supprimer le projet. Toutes les ressources associées au projet sont supprimées.

gcloud projects delete $GOOGLE_CLOUD_PROJECT

Option 2 : Supprimer des ressources individuelles

Si vous souhaitez conserver le projet, mais supprimer uniquement les ressources créées dans cet atelier de programmation :

gcloud run services delete restaurant-agent --region=$REGION --quiet
gcloud run services delete toolbox-service --region=$REGION --quiet
gcloud sql instances delete restaurant-instance --quiet
gcloud artifacts repositories delete cloud-run-source-deploy --location=$REGION --quiet 2>/dev/null