Six recettes pour atteindre une précision de près de 100 % dans la conversion texte-en-SQL dans AlloyDB – atelier de programmation

1. Introduction

Dans cet atelier de programmation, vous allez apprendre à obtenir les meilleurs résultats à l'aide de l'API QueryData. Vous découvrirez comment générer des résultats précis et reproductibles lorsque vous envoyez des requêtes en langage naturel à votre base de données. Pour vous aider à y parvenir, ce guide vous présente six recettes réparties sur plusieurs chapitres, qui explorent différents aspects du langage naturel vers SQL (NL2SQL) et ses applications pratiques.

Prérequis

  • Connaissances de base de Google Cloud et de la console Google Cloud
  • Compétences de base concernant l'interface de ligne de commande et Cloud Shell

Points abordés

  • Déployer un cluster et une instance principale AlloyDB
  • Activer QueryData, les fonctions d'IA et les vues sécurisées paramétrées
  • Créer un ensemble de contextes QueryData
  • Utiliser différents modèles et contextes dans l'API QueryData
  • Rendre les modèles QueryData plus flexibles à l'aide de facettes
  • Résoudre l'ambiguïté des données avec la recherche de valeurs et l'indexation par trigrammes
  • Utiliser les fonctions d'IA pour la recherche sémantique avec des modèles
  • Utiliser des vues sécurisées paramétrées avec l'API QueryData

Prérequis

  • Un compte Google Cloud et un projet Google Cloud
  • Un navigateur Web tel que Chrome, compatible avec la console Google Cloud et Cloud Shell

2. Préparation

Configuration du projet

  1. Connectez-vous à la console Google Cloud. (Si vous ne possédez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.)

Utilisez un compte personnel au lieu d'un compte professionnel ou scolaire.

  1. Créez un projet ou réutilisez-en un existant. Pour créer un projet dans la console Google Cloud, cliquez sur le bouton "Sélectionner un projet" dans l'en-tête pour ouvrir une fenêtre pop-up.

295004821bab6a87.png

Dans la fenêtre "Sélectionner un projet", cliquez sur "Nouveau projet". Une boîte de dialogue s'ouvre.

37d264871000675d.png

Dans la boîte de dialogue, saisissez un nom de projet et choisissez l'emplacement.

96d86d3d5655cdbe.png

  • Le nom du projet est le nom à afficher pour les participants au projet. Le nom de projet n'est utilisé par aucune API Google et peut être modifié à tout moment.
  • L'ID du projet est unique parmi tous les projets Google Cloud et non modifiable une fois défini. La console Google Cloud génère automatiquement un ID unique, mais vous pouvez le personnaliser. Si l'ID généré ne vous convient pas, vous pouvez en générer un autre de manière aléatoire ou en fournir un pour vérifier sa disponibilité. Dans la plupart des ateliers de programmation, vous devrez indiquer l'ID de votre projet, généralement identifié par l'espace réservé PROJECT_ID.
  • Pour information, il existe une troisième valeur (le numéro de projet) que certaines API utilisent. Pour en savoir plus sur ces trois valeurs, consultez la documentation.

Activer la facturation

Configurer un compte de facturation personnel

Si vous configurez la facturation à l'aide de crédits Google Cloud, vous pouvez ignorer cette étape.

Pour configurer un compte de facturation personnel, cliquez ici pour activer la facturation dans la console Cloud.

Remarques :

  • Cet atelier devrait vous coûter moins de 5 USD en ressources Cloud.
  • Vous pouvez suivre les étapes à la fin de cet atelier pour supprimer les ressources et éviter ainsi des frais supplémentaires.
  • Les nouveaux utilisateurs peuvent bénéficier d'un essai sans frais pour un crédit de 300 $.

Démarrer Cloud Shell

Bien que Google Cloud puisse être utilisé à distance depuis votre ordinateur portable, nous allons nous servir de Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.

Dans la console Google Cloud, cliquez sur l'icône Cloud Shell dans la barre d'outils supérieure :

Activer Cloud Shell

Vous pouvez également appuyer sur G, puis sur S. Cette séquence activera Cloud Shell si vous êtes dans la console Google Cloud ou si vous utilisez ce lien.

Le provisionnement et la connexion à l'environnement prennent quelques instants seulement. Une fois l'opération terminée, le résultat devrait ressembler à ceci :

Capture d'écran du terminal Google Cloud Shell montrant que l'environnement est connecté

Cette machine virtuelle contient tous les outils de développement nécessaires. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Vous pouvez effectuer toutes les tâches de cet atelier de programmation dans un navigateur. Vous n'avez rien à installer.

3. Avant de commencer

Activer une API

Pour utiliser AlloyDB, Compute Engine, les services réseau et Agent Platform, activez leurs API respectives dans votre projet Google Cloud.

Dans le terminal Cloud Shell, assurez-vous que l'ID de votre projet est configuré :

gcloud config get-value project

La commande doit renvoyer l'ID de votre projet Google Cloud.

Définissez la variable d'environnement PROJECT_ID :

PROJECT_ID=$(gcloud config get-value project)

Activez les API requises :

gcloud services enable alloydb.googleapis.com \
                       compute.googleapis.com \
                       cloudresourcemanager.googleapis.com \
                       servicenetworking.googleapis.com \
                       geminidataanalytics.googleapis.com \
                       cloudaicompanion.googleapis.com \
                       aiplatform.googleapis.com \
                       dataplex.googleapis.com

Résultat attendu :

student@cloudshell:~ (test-project-001-402417)$ gcloud config set project test-project-001-402417
Updated property [core/project].
student@cloudshell:~ (test-project-001-402417)$ PROJECT_ID=$(gcloud config get-value project)
Your active configuration is: [cloudshell-14650]
student@cloudshell:~ (test-project-001-402417)$ 
student@cloudshell:~ (test-project-001-402417)$ gcloud services enable alloydb.googleapis.com \
                       compute.googleapis.com \
                       cloudresourcemanager.googleapis.com \
                       servicenetworking.googleapis.com \
                       geminidataanalytics.googleapis.com \
                       cloudaicompanion.googleapis.com \
                       aiplatform.googleapis.com \
                       dataplex.googleapis.com
Operation "operations/acat.p2-4470404856-1f44ebd8-894e-4356-bea7-b84165a57442" finished successfully.

Pour en savoir plus sur chaque API activée, consultez la documentation.

4. Déployer AlloyDB

Créez un cluster et une instance principale AlloyDB. Vous pouvez le déployer à l'aide d'un script préparé qui déploiera toutes les ressources nécessaires, ou vous pouvez le faire vous-même étape par étape en suivant la documentation AlloyDB.

Déployer AlloyDB à l'aide d'un script automatisé

Cette approche utilise un script automatisé pour déployer le cluster AlloyDB et fournit les informations de configuration nécessaires pour commencer à travailler avec les ressources déployées.

Dans le terminal Cloud Shell, exécutez la commande permettant de cloner le script de déploiement à partir du dépôt :

REPO_NAME="codelabs"
REPO_URL="https://github.com/GoogleCloudPlatform/$REPO_NAME"
SOURCE_DIR="alloydb-ai-nla"

git clone --no-checkout --filter=blob:none --depth=1 $REPO_URL

cd $REPO_NAME
git sparse-checkout set $SOURCE_DIR
git checkout
cd $SOURCE_DIR

Exécutez le script de déploiement :

./deploy_alloydb.sh

L'exécution du script prend entre cinq et sept minutes. Le résultat affiche les détails du déploiement de votre cluster. Veuillez noter que votre mot de passe sera différent. Enregistrez-le pour une utilisation ultérieure.

...
<redacted>
...
Creating primary instance: alloydb-aip-01-pr (8 vCPUs for TRIAL cluster)
Operation ID: operation-1765988049916-646282264938a-bddce198-9f248715
Creating instance...done.                                                                                                                                                                                                             
----------------------------------------
Deployment Process Completed
Cluster:  alloydb-aip-01 (TRIAL)
Instance: alloydb-aip-01-pr
Region:   us-central1
Initial Password: JBBoDTgixzYwYpkF (if new cluster)
----------------------------------------
 

Affichez votre nouveau cluster et votre nouvelle instance sur la page Clusters AlloyDB de la console Google Cloud.

4271eb55bcc9ec84.png

5. Préparer la base de données

Créez une base de données, activez l'intégration de Gemini Enterprise Agent Platform, créez des objets de base de données et ajoutez des exemples de données.

Accorder les autorisations nécessaires à AlloyDB

Attribuez les rôles IAM requis à l'agent de service AlloyDB :

Ouvrez un nouvel onglet Cloud Shell en cliquant sur Ajouter (+).

abc505ac4d41f24e.png

Dans le nouvel onglet, exécutez :

PROJECT_ID=$(gcloud config get-value project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:service-$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")@gcp-sa-alloydb.iam.gserviceaccount.com" \
  --role="roles/aiplatform.user"

Résultat attendu sur la console :

student@cloudshell:~ (test-project-001-402417)$ PROJECT_ID=$(gcloud config get-value project)
Your active configuration is: [cloudshell-11039]
student@cloudshell:~ (test-project-001-402417)$ gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:service-$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")@gcp-sa-alloydb.iam.gserviceaccount.com" \
  --role="roles/aiplatform.user"
Updated IAM policy for project [test-project-001-402417].
bindings:
- members:
  - serviceAccount:service-4470404856@gcp-sa-alloydb.iam.gserviceaccount.com
  role: roles/aiplatform.user
- members:
...
etag: BwYIEbe_Z3U=
version: 1
 

Activer l'API Data Access

Activez l'API Data Access sur votre cluster AlloyDB.

Dans le même onglet de terminal, exécutez la commande suivante :

PROJECT_ID=$(gcloud config get-value project)
REGION=us-central1
ADBCLUSTER=alloydb-aip-01
curl -X PATCH \
 -H "Authorization: Bearer $(gcloud auth print-access-token)" \
 -H "Content-Type: application/json" \
 https://alloydb.googleapis.com/v1alpha/projects/$PROJECT_ID/locations/$REGION/clusters/$ADBCLUSTER/instances/$ADBCLUSTER-pr?updateMask=dataApiAccess \
 -d '{
   "dataApiAccess": "ENABLED",
 }'

Mettre à jour les options de base de données

Avant d'activer les indicateurs au niveau de l'instance, attendez la fin de l'étape de l'API Data Access. Assurez-vous que l'état de votre instance affiche une coche verte, comme illustré dans l'image ci-dessous.

6284731b8eff74c.jpeg

Pour utiliser les extensions d'IA et l'authentification IAM pour les outils agentiques, vous devez activer des indicateurs de base de données spécifiques sur l'instance et vous ajouter en tant qu'utilisateur de base de données. Activez les options suivantes :

  • alloydb.iam_authentication
  • password.enforce_complexity
  • google_ml_integration.enable_ai_query_engine
  • parameterized_views

Dans le même onglet de terminal, exécutez les commandes suivantes :

PROJECT_ID=$(gcloud config get-value project)
REGION=us-central1
ADBCLUSTER=alloydb-aip-01
gcloud beta alloydb instances update $ADBCLUSTER-pr \
   --database-flags password.enforce_complexity=on,alloydb.iam_authentication=on,google_ml_integration.enable_ai_query_engine=on,parameterized_views.enabled=on \
   --region=$REGION \
   --cluster=$ADBCLUSTER \
   --project=$PROJECT_ID \
   --update-mode=FORCE_APPLY

Créez un utilisateur IAM AlloyDB pour votre compte :

REGION=us-central1
ADBCLUSTER=alloydb-aip-01
gcloud alloydb users create $(gcloud config get-value account) \
--cluster=$ADBCLUSTER \
--superuser=true \
--region=$REGION \
--type=IAM_BASED

Fermez l'onglet en exécutant la commande exit dans le terminal :

exit

Se connecter à AlloyDB Studio

Dans les chapitres suivants, vous pouvez exécuter toutes les commandes SQL qui nécessitent une connexion à une base de données dans AlloyDB Studio.

Pour ouvrir AlloyDB Studio, procédez comme suit :

  1. Dans la console Google Cloud, accédez à la page Clusters pour AlloyDB pour PostgreSQL.
  2. Sélectionnez votre instance principale pour ouvrir son interface Web.

1d7298e7096e7313.png

  1. Cliquez ensuite sur AlloyDB Studio à gauche :

a33131c72ad29478.png

  1. Sélectionnez la base de données postgres, choisissez "Authentification IAM", puis cliquez sur "Authentifier".

8ddfb250344ca749.jpeg

Cette action ouvre l'interface AlloyDB Studio.

  1. Sélectionnez l'onglet "Requête sans titre" pour ouvrir l'éditeur SQL.

6696bc771fab9983.png

Un éditeur s'ouvre, dans lequel vous pouvez exécuter des commandes SQL, comme illustré dans l'image suivante :

ae34288e5bf237c7.png

Créer une base de données

Créer la base de données du guide de démarrage rapide

Dans l'éditeur AlloyDB Studio, exécutez la commande suivante pour créer la base de données :

CREATE DATABASE quickstart_db

Résultat attendu :

Statement executed successfully

Se connecter à quickstart_db

Cliquez sur "Changer de base de données/d'utilisateur" dans AlloyDB Studio.

6f5c98f5d0b3d550.png

Sélectionnez quickstart_db dans la liste des bases de données, puis sélectionnez "Authentification IAM" et authentifiez-vous.

a1940c26c36ff840.jpeg

Une nouvelle connexion s'ouvre, vous permettant de travailler avec des objets de la base de données quickstart_db.

Activer les extensions

Vérifiez que toutes les extensions requises sont activées dans votre base de données et qu'elles disposent des versions minimales correctes.

Vérifiez que toutes les extensions requises existent dans votre base de données :

SELECT extname, extversion FROM pg_extension WHERE extname IN ('vector', 'google_ml_integration', 'parameterized_views');

Résultat attendu :

        extname        |   extversion   
-----------------------+----------------
 google_ml_integration | 1.5.9
 parameterized_views   | 1.0
 vector                | 0.8.2.google-1

Si elle ne renvoie pas toutes les lignes ci-dessus, cela signifie que certaines extensions ne sont pas activées.

Avant d'activer les extensions, nous devons nous assurer que tous les indicateurs de base de données google_ml_integration.enable_ai_query_engine et parameterized_views.enabled sont activés sur l'instance.

Dans AlloyDB Studio, lorsque vous êtes connecté à quickstart_db, exécutez la commande suivante :

show google_ml_integration.enable_ai_query_engine;
show parameterized_views.enabled;

Si l'une d'elles est désactivée, réessayez d'exécuter l'étape "Mettre à jour les indicateurs de base de données", puis vérifiez à nouveau l'état à l'aide des commandes "show...".

Activez les extensions requises.

Dans AlloyDB Studio, lorsque vous êtes connecté à quickstart_db, exécutez la commande suivante :

CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS parameterized_views;

Vérifiez les versions des extensions parameterized_views et google_ml.

Dans AlloyDB Studio, lorsque vous êtes connecté à quickstart_db, exécutez la commande suivante :

SELECT extname, extversion FROM pg_extension WHERE extname IN ('vector', 'google_ml_integration', 'parameterized_views');

Résultat attendu :

        extname        |   extversion   
-----------------------+----------------
 google_ml_integration | 1.5.9
 parameterized_views   | 1.0
 vector                | 0.8.2.google-1

Si la version est inférieure à celle requise, essayez de mettre à jour l'extension parameterized_views.

Pour les vues paramétrées, essayez :

ALTER EXTENSION parameterized_views UPDATE;

Vérifiez à nouveau la version.

Dans AlloyDB Studio, lorsque vous êtes connecté à quickstart_db, exécutez la commande suivante :

SELECT extname, extversion FROM pg_extension WHERE extname IN ('vector', 'google_ml_integration', 'parameterized_views');

Résultat attendu :

        extname        |   extversion   
-----------------------+----------------
 google_ml_integration | 1.5.9
 parameterized_views   | 1.0
 vector                | 0.8.2.google-1

6. Exemples de données

Vous devez maintenant créer des objets dans la base de données et charger des données. Vous allez utiliser un ensemble de données fictif d'une société immobilière. Il contient des données fictives sur les ventes de biens immobiliers et les agents. Les données ont été fournies dans le script SQL real_estate_data.sql du dépôt cloné.

Créer un bucket de stockage

Utilisez le SDK Google (gcloud) pour importer des données de votre dépôt cloné vers la base de données AlloyDB. Pour ce faire, vous devez créer un bucket Cloud Storage et accorder l'accès au compte de service AlloyDB. Vous pouvez également essayer de le faire à l'aide de la console Web, comme décrit dans la documentation.

Dans le terminal Google Cloud Shell, exécutez la commande suivante :

PROJECT_ID=$(gcloud config get-value project)
REGION=us-central1
gcloud storage buckets create gs://$PROJECT_ID-import --project=$PROJECT_ID --location=$REGION
gcloud storage buckets add-iam-policy-binding gs://$PROJECT_ID-import --member="serviceAccount:service-$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")@gcp-sa-alloydb.iam.gserviceaccount.com" --role=roles/storage.objectViewer

Charger des données

Le vidage SQL de la base de données se trouve dans le dossier du dépôt cloné. La commande suivante suppose que vous avez utilisé votre répertoire d'accueil comme point de départ lorsque vous avez cloné le dépôt à l'étape précédente lors de la création du cluster AlloyDB.

Copiez le fichier de dump de la base de données dans votre bucket Cloud Storage :

REPO_NAME="codelabs"
SOURCE_DIR="alloydb-ai-nla"
cd ~/$REPO_NAME/$SOURCE_DIR
gcloud storage cp ~/$REPO_NAME/$SOURCE_DIR/real_estate_data.sql  gs://$PROJECT_ID-import

Importez l'ensemble de données dans quickstart_db :

PROJECT_ID=$(gcloud config get-value project)
CLUSTER_NAME=alloydb-aip-01
REGION=us-central1
IAM_USER=$(gcloud config get-value account)
gcloud alloydb clusters import $CLUSTER_NAME  --region=us-central1 --database=quickstart_db --gcs-uri=gs://$PROJECT_ID-import/real_estate_data.sql --project=$PROJECT_ID --sql --user=$IAM_USER

La commande chargera l'ensemble de données exemple dans la base de données quickstart_db.

Vérifiez l'importation en comptant les enregistrements dans la table real_estate.agents à l'aide d'AlloyDB Studio :

SELECT COUNT(*) FROM real_estate.agents;

7. Préparer l'ontologie Schema

Configurez votre ensemble de contexte QueryData, fournissez des métadonnées de schéma et exécutez l'agent ADK.

Créer des métadonnées

Les métadonnées se composent de commentaires SQL appliqués aux tables et aux colonnes de la base de données. Ces commentaires guident le moteur QueryData lors de la sélection des objets de base de données.

Si vous avez importé votre schéma de données à l'aide du fichier de vidage SQL fourni, toutes les métadonnées sont déjà créées dans votre schéma.

Voici un exemple d'extrait de métadonnées appliquées au schéma real_estate :

COMMENT ON TABLE real_estate.agents IS 'Stores profile, contact, brokerage affiliation, and active status for individual real estate agents.';

COMMENT ON COLUMN real_estate.agents.agent_id IS 'Primary key uniquely identifying each real estate agent in the database.';

...

Si vous travaillez avec votre propre ensemble de données, vous pouvez suivre le guide pour ajouter les métadonnées de votre schéma.

Créer un contexte d'ontologie de schéma

Un ensemble de contexte contient des modèles de requêtes, des facettes et des recherches de valeurs qui aident le modèle d'IA à générer des requêtes. Sans contexte défini, un agent d'IA tentera de générer une requête en utilisant uniquement les informations disponibles à partir du schéma public, principalement en fonction des noms de tables, des colonnes et des métadonnées, en essayant de déduire les bons objets pour répondre à une requête en langage naturel.

L'ensemble de contexte QueryData est un fichier JSON contenant des modèles de requête, des facettes et des recherches de valeurs. Vous trouverez différents exemples dans la documentation.

Charger l'ensemble de contexte QueryData dans la console Web

Pour charger un ensemble de contexte, ouvrez AlloyDB Studio. En bas du panneau de gauche, vous verrez Context sets et trois points.

d3578ceee65cd0dd.jpeg

Cliquez sur ces trois points, puis sélectionnez Create context set.

Dans la boîte de dialogue qui s'ouvre, saisissez les valeurs suivantes :

  • Nom : sixrecipes
  • Description : Real Estate Query Data
  • Importer un fichier de contexte : le laisser vide

Lorsque vous appuyez sur le bouton Create, l'initialisation du stockage du contexte peut prendre un certain temps si vous le faites pour la première fois.

Vérifiez l'ID de l'ensemble de contexte QueryData en cliquant à nouveau sur les trois points à droite du nom de l'ensemble de contexte, puis sélectionnez Edit context set.

6739fb9f54d7f79d.jpeg

L'ID de l'ensemble de contexte s'affiche en haut de l'onglet ouvert à droite.

746b38c52a70bb3c.jpeg

Enregistrez cet ID pour les étapes ultérieures.

Démarrer l'agent IA

Pour interagir avec le backend QueryData afin de générer et d'exécuter des requêtes, vous pouvez utiliser l'interface AlloyDB Studio, comme décrit dans la documentation. Toutefois, dans cet atelier, vous utiliserez un agent d'IA comme principal outil de communication avec la base de données. Les agents d'IA constituent un scénario plus réaliste pour les futures charges de travail, et le framework ADK de Google fournit une excellente interface Web où vous pouvez voir toutes les étapes et interactions entre les modèles, les API et les applications.

Examinez la configuration de la source de l'agent dans Cloud Shell :

REPO_NAME="codelabs"
SOURCE_DIR="alloydb-ai-nla"
cd ~/$REPO_NAME/$SOURCE_DIR/data_agent
edit agent.py

Dans le fichier, vous pouvez voir où nous définissons la liste des tables dans notre schéma. Nous y reviendrons dans l'une des prochaines étapes.

# List of database tables included in QueryData API requests
TABLE_IDS = [
    "real_estate.municipalities",
    "real_estate.cities",
    "real_estate.agents",
    "real_estate.properties",
    "real_estate.schools",
    "real_estate.school_to_property",
    "real_estate.property_transactions",
    "real_estate.secure_offers",
]

Une procédure call_querydata_api permet de travailler avec l'API QueryData, ainsi qu'un ensemble d'outils pour travailler avec les compétences de l'API QueryData.

def call_querydata_api(
    prompt: str,
    target_project_id: str = project_id,
    target_location: str = location,
    target_cluster_id: str = cluster_name,
    target_instance_id: str = instance_name,
    target_database_id: str = database_name,
    target_context_set_id: str = context_set_id,
    target_table_ids: list[str] = TABLE_IDS,
) -> dict:
    """Calls the Gemini Data Analytics QueryData API to translate natural ...
<Redacted>
...

# ADK File-Based Skill

database_query_skill = load_skill_from_dir(
    pathlib.Path(__file__).parent / "skills" / "database-query-skill"
)

skill_toolset = SkillToolset(
    skills=[database_query_skill]
)

Voici l'agent racine lui-même, avec une invite et les outils disponibles.

# Agent configuration

root_agent = Agent(
    model=MODEL_ID,
    name='root_agent',
    description='A helpful assistant for analyst requests.',
    instruction=f"""
    Answer user questions to the best of your knowledge using provided tools.
    Do not try to generate non-existent data but use the grounded data from the database.
    When you answer questions about database contents, real estate properties, or sales activity:
    1. Refer to database-query-skill for instructions on querying data using QueryData API.
    2. Use `call_querydata_api` to submit the natural language prompt to the QueryData API.
       CRITICAL: Call `call_querydata_api` EXACTLY ONCE per user request. Do NOT make retries, follow-up calls, or rephrased calls to `call_querydata_api`, even if the result contains zero rows or empty data.
    3. Immediately parse the JSON response from that first `call_querydata_api` call.
    4. Format your final response to the user containing:
       - The generated SQL query (formatted in a Markdown SQL code block).
       - The query results formatted as a Markdown table (constructed from `queryResult.columns` and `queryResult.rows`).
       - The natural language answer (`naturalLanguageAnswer`).
    """,
    tools=[call_querydata_api, skill_toolset],
)

Consultez data_agent/SKILL.md pour comprendre les instructions système de l'agent :

REPO_NAME="codelabs"
SOURCE_DIR="alloydb-ai-nla"
cd ~/$REPO_NAME/$SOURCE_DIR/data_agent
edit SKILL.md

Avant de démarrer l'agent, vous devez préparer un fichier .env avec les paramètres des requêtes d'API Agent et QueryData.

Exportez l'ID de votre ensemble de contexte vers une variable d'environnement :

export QUERYDATA_CONTEXTSET=<context set id from the Create Context Set chapter>

Créez votre fichier de configuration de l'environnement d'exécution (.env) dans le répertoire data_agent :

PROJECT_ID=$(gcloud config get-value project)
REGION=us-central1
REPO_NAME="codelabs"
SOURCE_DIR="alloydb-ai-nla"
cd ~/$REPO_NAME/$SOURCE_DIR/data_agent
echo "GOOGLE_GENAI_USE_ENTERPRISE=true" > .env
echo "GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project -q)" >> .env
echo "GOOGLE_CLOUD_LOCATION=global" >> .env
echo "QUERYDATA_CONTEXTSET=$QUERYDATA_CONTEXTSET" >> .env
echo "ALLOYDB_CLUSTER=alloydb-aip-01" >> .env
echo "ALLOYDB_INSTANCE=alloydb-aip-01-pr" >> .env
echo "ALLOYDB_DATABASE=quickstart_db" >> .env
echo "ALLOYDB_LOCATION=$REGION" >> .env

Installez les dépendances et démarrez le service Web de l'agent :

cd ~/$REPO_NAME/$SOURCE_DIR/
uv sync

Démarrez l'agent :

uv run adk web --allow_origins 'regex:https://.*\.cloudshell\.dev'

Vous devriez obtenir un résultat semblable à ce qui suit avec le point de terminaison http://127.0.0.1:8000 .

INFO:     Started server process [83608]
INFO:     Waiting for application startup.

+-----------------------------------------------------------------------------+
| ADK Web Server started                                                      |
|                                                                             |
| For local testing, access at http://127.0.0.1:8000.                         |
+-----------------------------------------------------------------------------+

INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

4aa60270e31efe37.jpeg

Cliquez sur l'URL du serveur Web générée (par exemple, http://127.0.0.1:8000) dans Cloud Shell pour lancer l'interface de l'agent. Si cela ne fonctionne pas, cliquez sur Aperçu sur le Web > Prévisualiser sur le port 8000 dans Cloud Shell pour ouvrir l'interface de l'agent.

Vérifiez que data_agent est sélectionné dans le menu déroulant en haut à gauche.

ad695db289666da3.jpeg

Générer et exécuter des requêtes

Exécutons la requête Q1.

Exécutez cette requête en langage naturel dans l'interface de chat de l'agent pour évaluer les performances de base de QueryData sans modèles ni facettes personnalisés :

How many properties are currently for sale in San Diego?

Appuyez sur le bouton en forme de flèche.

77773a81de6360a2.jpeg

Il lancera le workflow de l'agent lorsqu'il aura sélectionné la compétence appropriée et l'utilisera, ainsi que la fonction, pour envoyer la requête au point de terminaison de l'API et récupérer la requête générée et les résultats.

de9b3e4854b9a506.jpeg

Si vous pointez sur les étapes, vous pouvez voir exactement ce qu'elles ont fait. Vous pouvez également voir cela et bien plus encore dans le panneau de gauche lorsque vous choisissez une étape ou une autre. Cela peut vous aider à comprendre comment les agents fonctionnent avec les compétences et les API.

605dcf936ba38cb8.jpeg

Envoyez une requête complémentaire (Q2) :

List agents who sold a property for over $1000000.

Vous remarquerez que l'agent accède directement à la compétence de l'API QueryData, car il a déjà écrit les compétences disponibles dans la mémoire et n'a pas besoin de repasser par la phase de découverte.

La requête SQL générée ressemble à cette instruction :

SELECT DISTINCT "a"."agent_id", "a"."firstname", "a"."lastname", "a"."email", "a"."brokerage" 
FROM "real_estate"."agents" AS "a" 
INNER JOIN "real_estate"."property_transactions" AS "t" ON "a"."agent_id" = "t"."seller_agent_id" 
WHERE "t"."sale_price" > 1000000 AND "a"."agent_id" IS NOT NULL 
ORDER BY "a"."lastname" ASC NULLS LAST;

Essayez une autre requête (Q3) :

What is the average price of a single-family home with a garage in San Diego?

QueryData génère des requêtes standards avec un ensemble de contexte vide, mais les tâches métier nécessitent souvent une logique métier spécifique et un comportement déterministe.

8. Obtenir une précision et une pertinence commerciale élevées en injectant de l'interprétation

En injectant de l'interprétation dans les requêtes, vous vous assurez que les requêtes générées correspondent à une logique métier spécifique.

Vous allez essayer un exemple avec un client qui demande des maisons de plain-pied à Del Mar. La question comporte deux métriques importantes et représente un modèle fréquent pour une sélection.

Essayez la requête en langage naturel suivante (T4) dans l'agent.

Single level Del Mar houses

Le moteur renvoie une requête générale, mais peut inclure des conditions supplémentaires telles que is_single_family = TRUE :

SELECT p."property_id", p."address_street", p."price", p."bedrooms", p."bathrooms", p."listing_status", p."is_single_family", p."num_stories", c."city_name" 
FROM "real_estate"."properties" AS p 
JOIN "real_estate"."cities" AS c ON p."city_id" = c."city_id" 
WHERE c."city_name" ILIKE 'Del Mar' AND p."num_stories" = 1 AND p."is_single_family" = TRUE 
ORDER BY p."property_id" ASC NULLS LAST;

Nous obtenons une ligne en résultat :

Natural Language Answer
The database contains one single-level, single-family house in Del Mar:

Address: 999 Main Ave (Postal Code: 90024)
Price: $3,800,000.00
Bedrooms: 4
Bathrooms: 3.5
Listing Status: Active
Features: Single-family home (1 story), has a garage, and no condo fees.

Testez un autre prompt (Q5) :

Del Mar houses close to good schools

Il est suffisamment intelligent pour utiliser les colonnes proximity_miles et school_ranking pour trier les propriétés, mais n'évalue pas le poids ni l'importance de chaque métrique.

SELECT "p"."property_id", "p"."address_street", "p"."price", "p"."bedrooms", "p"."bathrooms", "s"."school_name", "s"."school_ranking", "sp"."proximity_miles" 
FROM "real_estate"."properties" AS "p" 
INNER JOIN "real_estate"."cities" AS "c" ON "p"."city_id" = "c"."city_id" 
INNER JOIN "real_estate"."school_to_property" AS "sp" ON "p"."property_id" = "sp"."property_id" 
INNER JOIN "real_estate"."schools" AS "s" ON "sp"."school_id" = "s"."school_id" 
WHERE "c"."city_name" = 'Del Mar' AND "sp"."proximity_miles" IS NOT NULL AND "s"."school_ranking" IS NOT NULL 
ORDER BY "sp"."proximity_miles" ASC NULLS LAST, "s"."school_ranking" ASC NULLS LAST;

Généraliser un exemple de requête dans un modèle

La logique métier nécessite :

  • Proximité (dans un rayon de 3 km)
  • Classement de l'établissement scolaire égal ou inférieur à 5
  • Classement pondéré : 80% de classement de l'établissement + 20% de proximité (score = 0,8 * classement_de_l'établissement + 0,2 * proximité_en_km)

Voici la formule de classement pondéré :

‬‭‬‭‬‭‬‭‬Score = 0.8 * school_ranking + 0.2 * proximity_miles

Téléchargez le fichier de contexte sixrecipe_context1_1.json depuis Cloud Shell à l'aide de l'interface de ligne de commande dans Cloud Shell :

cloudshell download ~/codelabs/alloydb-ai-nla/sixrecipe_context1_1.json

Vous pouvez également utiliser l'élément de menu Download du menu Cloud Shell :

c0d781806d966dcb.jpeg

Importez le sixrecipe_context1_1.json dans votre ensemble de contexte sixrecipe à l'aide d'AlloyDB Studio pour définir cette structure standard. Cliquez sur les trois points à droite du contexte sixrecipes, puis sélectionnez "Modifier l'ensemble de contexte" pour importer le nouveau fichier. Il remplacera effectivement l'ancien contexte vide par le nouveau.

Le modèle définit des règles de modèle SQL explicites pour les règles de proximité des établissements scolaires :

{
  "facets": [],
  "templates": [
    {
      "nl_query": "Riverside houses close to good schools.",
      "sql": "SELECT \"p\".\"address_street\", \"p\".\"address_postal_code\", \"s\".\"school_name\", \"stp\".\"proximity_miles\" FROM \"real_estate\".\"properties\" AS \"p\" JOIN \"real_estate\".\"cities\" AS \"c\" ON \"p\".\"city_id\" = \"c\".\"city_id\" JOIN \"real_estate\".\"school_to_property\" AS \"stp\" ON \"p\".\"property_id\" = \"stp\".\"property_id\" JOIN \"real_estate\".\"schools\" AS \"s\" ON \"stp\".\"school_id\" = \"s\".\"school_id\" WHERE \"c\".\"city_name\" = 'Riverside' AND \"s\".\"school_ranking\" <= 5 AND \"stp\".\"proximity_miles\" <= 2 ORDER BY 0.8 * \"s\".\"school_ranking\" + 0.2 * \"stp\".\"proximity_miles\"",
      "intent": "Houses in Riverside close to good schools (within 2 miles of schools with a rank of at least 5).",
      "manifest": "A given city houses close to good schools.",
      "parameterized": {
        "parameterized_intent": "Houses in $1 close to good schools (within 2 miles of schools with a rank of at least 5).",
        "parameterized_sql": "SELECT \"p\".\"address_street\", \"p\".\"address_postal_code\", \"s\".\"school_name\", \"stp\".\"proximity_miles\" FROM \"real_estate\".\"properties\" AS \"p\" JOIN \"real_estate\".\"cities\" AS \"c\" ON \"p\".\"city_id\" = \"c\".\"city_id\" JOIN \"real_estate\".\"school_to_property\" AS \"stp\" ON \"p\".\"property_id\" = \"stp\".\"property_id\" JOIN \"real_estate\".\"schools\" AS \"s\" ON \"stp\".\"school_id\" = \"s\".\"school_id\" WHERE \"c\".\"city_name\" = $1 AND \"s\".\"school_ranking\" <= 5 AND \"stp\".\"proximity_miles\" <= 2 ORDER BY 0.8 * \"s\".\"school_ranking\" + 0.2 * \"stp\".\"proximity_miles\""
      }
    }
  ]
}

Réexécutez le prompt :

Del Mar houses close to good schools

La requête obtenue correspond désormais à la logique métier définie dans le modèle :

SELECT "p"."address_street", "p"."address_postal_code", "s"."school_name", "stp"."proximity_miles" 
FROM "real_estate"."properties" AS "p" 
JOIN "real_estate"."cities" AS "c" ON "p"."city_id" = "c"."city_id" 
JOIN "real_estate"."school_to_property" AS "stp" ON "p"."property_id" = "stp"."property_id" 
JOIN "real_estate"."schools" AS "s" ON "stp"."school_id" = "s"."school_id" 
WHERE "c"."city_name" = 'Del Mar' AND "s"."school_ranking" <= 5 AND "stp"."proximity_miles" <= 2 
ORDER BY 0.8 * "s"."school_ranking" + 0.2 * "stp"."proximity_miles"

En plus de l'instruction SQL générée, l'intention suivante est générée :

intentExplanation: "Houses in Del Mar close to good schools (within 2 miles of schools with a rank of at least 5)."

Vous pouvez voir l'intention dans le volet de gauche de l'interface Web ADK, comme indiqué ici :

bfb9d6bc718f57cf.jpeg

Les résultats affichent deux propriétés triées par notre score :

Query Results
address_street        address_postal_code        school_name        proximity_miles
200 Coast Blvd        92014        Del Mar Heights Elementary        0.50
100 Del Mar Heights Rd        92014        Del Mar Heights Elementary        1.00
Answer
Based on the data, here are the properties in Del Mar located within 2 miles of a top-rated school (ranking of 5 or better):

200 Coast Blvd (92014)

School: Del Mar Heights Elementary
Proximity: 0.50 miles
100 Del Mar Heights Rd (92014)

School: Del Mar Heights Elementary
Proximity: 1.00 miles

Dans le chapitre suivant, vous allez étendre le champ d'application du modèle.

Injecter de l'interprétation en capturant et en interprétant plusieurs questions

Vous pouvez étendre le champ d'application de notre modèle pour qu'il soit utilisé non seulement pour les "maisons à proximité de bonnes écoles", mais aussi en ajoutant d'autres conditions au fichier manifeste, par exemple pour les "maisons pour les familles avec enfants".

Téléchargez le fichier de contexte mis à jour :

cloudshell download ~/codelabs/alloydb-ai-nla/sixrecipe_context1_2.json

Importez le fichier sixrecipe_context1_2.json mis à jour dans votre ensemble de contexte.

Dans le nouveau fichier de contexte, nous remplaçons le fichier manifeste "manifest": "A given city houses close to good schools." par le fichier manifeste "manifest": "A given city houses close to good schools, or are great for families with kids.". Il étend les applications de ce modèle à plusieurs cas d'utilisation.

Envoyez cette requête (Q6) :

Houses in Waterloo great for families with kids.

L'agent utilise le fichier manifeste du modèle mis à jour pour appliquer votre logique de requête standardisée, ce qui renvoie la même requête que ci-dessus, avec l'intention suivante :

intentExplanation
:
"Houses in Waterloo close to good schools (within 2 miles of schools with a rank of at least 5)."

9. Utiliser des modèles à facettes pour atteindre une grande précision tout en conservant la flexibilité

Les facettes sont des conditions SQL modulaires combinées de manière dynamique avec des modèles pour gérer les qualificatifs (par exemple, "famille débutante", "vue sur l'océan") sans créer de modèles distincts pour chaque permutation.

Au lieu de créer un modèle pour chaque requête ou condition, nous pouvons préparer des fragments qui peuvent être appliqués aux modèles existants en ajoutant des dimensions à la requête.

Définissez un facette pour les propriétés avec les attributs suivants :

  • Maison individuelle
  • Un état de fiche actif
  • Trois à quatre chambres et au moins deux salles de bain
  • Localisation dans une municipalité dont le taux de criminalité est inférieur à 500 pour 100 000 habitants

Téléchargez le fichier de contexte à facettes :

cloudshell download ~/codelabs/alloydb-ai-nla/sixrecipe_context2_1.json

Importez sixrecipe_context2_1.json dans votre ensemble de contexte sixrecipes dans AlloyDB Studio pour configurer les facettes.

  "facets": [
    {
      "sql_snippet": "real_estate.properties.is_single_family = TRUE AND real_estate.properties.listing_status = 'Active' AND real_estate.properties.bedrooms BETWEEN 3 AND 4 AND real_estate.properties.bathrooms >= 2.0 AND 500 > (SELECT m.crime_rate_per_100k FROM real_estate.cities c JOIN real_estate.municipalities m ON c.municipality_id = m.municipality_id AND c.city_id = real_estate.properties.city_id)",
      "intent": "for a starter family (active single-family home listings with 3 to 4 bedrooms, 2+ bathrooms, and a crime rate under 500 per 100,000 residents)",
      "manifest": "for a starter family",
      "parameterized": {
        "parameterized_intent": "for a starter family (active single-family home listings with 3 to 4 bedrooms, 2+ bathrooms, and a crime rate under 500 per 100,000 residents)",
        "parameterized_sql_snippet": "real_estate.properties.is_single_family = TRUE AND real_estate.properties.listing_status = 'Active' AND real_estate.properties.bedrooms BETWEEN 3 AND 4 AND real_estate.properties.bathrooms >= 2.0 AND 500 > (SELECT m.crime_rate_per_100k FROM real_estate.cities c JOIN real_estate.municipalities m ON c.municipality_id = m.municipality_id AND c.city_id = real_estate.properties.city_id)"
      }
    }
  ]

Exécutez cette requête en langage naturel (Q7) :

Del Mar houses close to good schools for a starter family.

L'application du moteur ajoute vos conditions de facette directement à la requête de base :

SELECT "p"."address_street", "p"."address_postal_code", "s"."school_name", "stp"."proximity_miles" 
FROM "real_estate"."properties" AS "p" 
JOIN "real_estate"."cities" AS "c" ON "p"."city_id" = "c"."city_id" 
JOIN "real_estate"."school_to_property" AS "stp" ON "p"."property_id" = "stp"."property_id" 
JOIN "real_estate"."schools" AS "s" ON "stp"."school_id" = "s"."school_id" 
WHERE "c"."city_name" = 'Del Mar' 
  AND "s"."school_ranking" <= 5 
  AND "stp"."proximity_miles" <= 2 
  AND "p"."is_single_family" = TRUE 
  AND "p"."listing_status" = 'Active' 
  AND "p"."bedrooms" BETWEEN 3 AND 4 
  AND "p"."bathrooms" >= 2.0 
  AND 500 > (
    SELECT m.crime_rate_per_100k 
    FROM real_estate.cities c 
    JOIN real_estate.municipalities m ON c.municipality_id = m.municipality_id 
    AND c.city_id = p.city_id
  ) 
ORDER BY 0.8 * "s"."school_ranking" + 0.2 * "stp"."proximity_miles"

Voici les résultats :

Query Results
address_street        address_postal_code        school_name        proximity_miles
200 Coast Blvd        92014        Del Mar Heights Elementary        0.50
Natural Language Answer
Based on your criteria, there is an active listing for a single-family house in Del Mar that fits your needs:

Address: 200 Coast Blvd, Del Mar, 92014
Nearby School: Del Mar Heights Elementary (ranked within the top 5)
Proximity to School: 0.50 miles

Ajouter d'autres facettes

Et si nous voulions ajouter une dimension supplémentaire basée sur le nouveau modèle émergent, comme une vue de fenêtre pour la propriété ?

Ajoutez la correspondance sémantique de la vue sur l'océan dans une définition de facette.

    {
      "sql_snippet": "((real_estate.properties.description_embedding <=> google_ml.embedding('gemini-embedding-001', 'unobstructed, panoramic, ocean vista, vast Pacific, breathtaking, commanding view, deep blue horizon, massive, sweeping view')::vector) < 0.4)",
      "intent": "with ocean view",
      "manifest": "with ocean view",
      "parameterized": {
        "parameterized_intent": "with ocean view",
        "parameterized_sql_snippet": "((real_estate.properties.description_embedding <=> google_ml.embedding('gemini-embedding-001', 'unobstructed, panoramic, ocean vista, vast Pacific, breathtaking, commanding view, deep blue horizon, massive, sweeping view')::vector) < 0.4)"
      }

La valeur 0,4 dans la définition de facette ci-dessus est un seuil calibré de manière expérimentale. Téléchargez le fichier de contexte multifacette :

cloudshell download ~/codelabs/alloydb-ai-nla/sixrecipe_context2_2.json

Importez sixrecipe_context2_2.json dans votre ensemble de contexte et exécutez cette requête (Q8) :

Ocean view Del Mar homes near good schools.

Le résultat contient des conditions standards combinées à une correspondance de similarité vectorielle via google_ml.embedding :

SELECT "p"."address_street", "p"."address_postal_code", "s"."school_name", "stp"."proximity_miles" 
FROM "real_estate"."properties" AS "p" 
JOIN "real_estate"."cities" AS "c" ON "p"."city_id" = "c"."city_id" 
JOIN "real_estate"."school_to_property" AS "stp" ON "p"."property_id" = "stp"."property_id" 
JOIN "real_estate"."schools" AS "s" ON "stp"."school_id" = "s"."school_id" 
WHERE "c"."city_name" = 'Del Mar' AND "s"."school_ranking" <= 5 AND "stp"."proximity_miles" <= 2 
AND (((p.description_embedding <=> google_ml.embedding('gemini-embedding-001', 'unobstructed, panoramic, ocean vista, vast Pacific, breathtaking, commanding view, deep blue horizon, massive, sweeping view')::vector) <= 0.4)) 
ORDER BY 0.8 * "s"."school_ranking" + 0.2 * "stp"."proximity_miles";

La requête renvoie une propriété :

Query Results
address_street        address_postal_code        school_name        proximity_miles
100 Del Mar Heights Rd        92014        Del Mar Heights Elementary        1.00
Answer
The ocean-view home in Del Mar near a top-ranked school is located at 100 Del Mar Heights Rd (92014).

It is situated just 1.00 mile away from Del Mar Heights Elementary, which is one of the area's highly-ranked schools (ranking of 5 or better).

Vérifiez les résultats en consultant la description de la propriété dans AlloyDB Studio et en exécutant la requête suivante :

select description from real_estate.properties where address_postal_code='92014' and description is not null;

La description "magnifique spectacle marin" s'affiche.

Well-located 2-bedroom condo with excellent proximity to coastal shopping and dining. The balcony provides a magnificent marine spectacle against an azure backdrop. Under contract, high monthly HOA fee.

10. Désambiguïsation de vos données privées à l'aide de recherches de valeurs

Les requêtes ambiguës peuvent correspondre à plusieurs entités, par exemple en confondant un nom de lieu avec un nom d'entreprise. Les recherches de valeurs associent des termes à des concepts de base de données pour résoudre ces références.

La recherche de valeurs permet d'associer un nom de colonne et des valeurs dans la colonne à un type de concept (par exemple, une ville, un pays ou un nom). Comme il a indexé non seulement les métadonnées, mais aussi des échantillons représentatifs des données d'une colonne, il peut associer une valeur utilisée dans la requête en langage naturel à un type de concept potentiel, ainsi qu'à la table et à la colonne pouvant être utilisées dans le code SQL résultant.

Ambiguïté dans une requête

Dans la vie réelle, cela peut être encore plus difficile en raison des fautes d'orthographe dans les noms et les lieux. Exécutez cette requête (Q9) contenant une faute de frappe et un terme ambigu :

Westwod's sold properties in the last 1 month.

Il y a deux problèmes ici : le nom est mal orthographié et il n'est pas clair si nous parlons de propriétés vendues dans la localité portant un nom similaire ou d'une agence immobilière. Lorsque nous exécutons la requête, elle renvoie une requête semblable à celle-ci.

SELECT "p"."property_id", "p"."address_street", "p"."address_postal_code", "p"."price" AS "listing_price", "p"."bedrooms", "p"."bathrooms", "p"."listing_status", "pt"."sale_date", "pt"."sale_price", "c"."city_name" FROM "real_estate"."properties" AS "p" INNER JOIN "real_estate"."property_transactions" AS "pt" ON "p"."property_id" = "pt"."property_id" INNER JOIN "real_estate"."cities" AS "c" ON "p"."city_id" = "c"."city_id" WHERE ("c"."city_name" ILIKE '%Westwood%' OR "c"."city_name" ILIKE '%Westwod%') AND "pt"."sale_date" >= '2025-09-01'::date AND "pt"."sale_date" <= '2025-09-30'::date ORDER BY "pt"."sale_date" DESC NULLS LAST, "pt"."sale_price" DESC NULLS LAST

Il a corrigé le nom et supposé que la question portait sur l'emplacement des propriétés, en essayant de trouver des propriétés dans la ville de Westwood.

Activez l'extension pg_trgm dans AlloyDB Studio pour prendre en charge les comparaisons de chaînes approximatives :

CREATE EXTENSION IF NOT EXISTS pg_trgm;

Nous pouvons ensuite utiliser l'extension pour ajouter des recherches de valeurs à notre ensemble de contexte QueryData.

Voici la liste des recherches de valeurs que nous souhaitons utiliser :

"value_searches": [
    {
      "conceptType": "Municipality name",
      "query": "/* Requires extension: pg_trgm */ WITH TrigramMetrics AS (SELECT T.municipality_name AS original_value, (T.municipality_name % $value::text) AS normalized_dist FROM real_estate.municipalities T WHERE T.municipality_name % $value::text) SELECT original_value AS value, 'real_estate.municipalities.municipality_name' AS columns, 'Name of the municipality' AS concept_type, normalized_dist AS distance, ''::text AS context FROM TrigramMetrics",
      "description": "Trigram match for municipality name"
    },
    {
      "conceptType": "City name",
      "query": "/* Requires extension: pg_trgm */ WITH TrigramMetrics AS (SELECT T.city_name AS original_value, (T.city_name % $value::text) AS normalized_dist FROM real_estate.cities T WHERE T.city_name % $value::text) SELECT original_value AS value, 'real_estate.cities.city_name' AS columns, 'Name of the city' AS concept_type, normalized_dist AS distance, ''::text AS context FROM TrigramMetrics",
      "description": "Trigram match for city name"
    },
    {
      "conceptType": "Brokerage name",
      "query": "/* Requires extension: pg_trgm */ WITH TrigramMetrics AS (SELECT T.brokerage AS original_value, (T.brokerage % $value::text) AS normalized_dist FROM real_estate.agents T WHERE T.brokerage % $value::text) SELECT original_value AS value, 'real_estate.agents.brokerage' AS columns, 'Name of the brokerage' AS concept_type, normalized_dist AS distance, ''::text AS context FROM TrigramMetrics",
      "description": "Trigram match for brokerage name"
    }
  ]

Téléchargez le fichier de contexte de recherche de valeurs :

cloudshell download ~/codelabs/alloydb-ai-nla/sixrecipe_context3_1.json

Importez sixrecipe_context3_1.json dans l'ensemble de contextes QueryData sixrecipes et enregistrez-le.

Relancez le prompt (Q9) :

Westwod's sold properties in the last 1 month.

Il a renvoyé une réponse de clarification, car la recherche de valeurs a trouvé trois entités différentes ayant des valeurs similaires :

The database requires some clarification to provide the exact information you need:

The search term 'Westwod' (Westwood) is ambiguous because it can refer to:

A municipality name (e.g., the municipality of Westwood)
A city name (e.g., the city of Westwood)
A brokerage name (e.g., properties sold by agents affiliated with the Westwood brokerage)
Could you please clarify which of these you are referring to?

La trace nous donne une idée de la raison pour laquelle elle a choisi ce chemin particulier.

88c52932dcbef8f9.jpeg

Voici le texte complet :

"[NOT_ENOUGH_INFO] The search term 'Westwod' is ambiguous because it can refer to a municipality name, a city name, or a brokerage name. Could you please clarify if you are referring to properties sold within the municipality of Westwood, the city of Westwood, or properties sold by agents affiliated with the Westwood brokerage?"

Supprimer l'ambiguïté de la requête

Affinez la requête d'entrée (Q10) :

Westwood city location properties sold in September 2025.

Nous avons maintenant la requête et les résultats :

Generated SQL
SELECT "p"."property_id", "p"."address_street", "p"."address_postal_code", "p"."price", "pt"."sale_date", "pt"."sale_price" 
FROM "real_estate"."properties" AS "p" 
INNER JOIN "real_estate"."cities" AS "c" ON "p"."city_id" = "c"."city_id" 
INNER JOIN "real_estate"."property_transactions" AS "pt" ON "p"."property_id" = "pt"."property_id" 
WHERE "c"."city_name" = 'Westwood' 
  AND "pt"."sale_date" >= '2025-09-01'::date 
  AND "pt"."sale_date" <= '2025-09-30'::date 
ORDER BY "pt"."sale_date" ASC NULLS LAST

Query Results
property_id        address_street        address_postal_code        price        sale_date        sale_price
32        999 Main St        99999        3800000.00        2025-09-12        1000000.00

11. Recherche sémantique et fonctions d'IA

Les fonctions AlloyDB AI permettent le filtrage sémantique des données non structurées. Utilisez des fonctions telles que ai.if et google_ml.embedding directement dans les requêtes SQL, en plus des opérateurs de base de données standards.

Modèle avec des opérateurs ILIKE

Pour commencer, importez le nouveau fichier de contexte avec les opérateurs ILIKE.

Téléchargez le fichier de contexte du modèle ILIKE :

cloudshell download ~/codelabs/alloydb-ai-nla/sixrecipe_context4_1.json

Importez sixrecipe_context4_1.json dans votre ensemble de contexte pour évaluer la correspondance de modèle ILIKE standard pour les maisons "prêtes à emménager".

    {
      "nl_query": "Move-in ready houses",
      "sql": "SELECT \"p\".\"address_street\", \"p\".\"address_postal_code\" FROM \"real_estate\".\"properties\" AS \"p\" WHERE \"p\".listing_status = 'Active' AND \"p\".\"description\" IS NOT NULL AND (\"p\".description ILIKE '%turnkey%' OR \"p\".description ILIKE '%move-in%')",
      "intent": "Move-in ready houses",
      "manifest": "Move-in ready houses",
      "parameterized": {
        "parameterized_intent": "Move-in ready houses",
        "parameterized_sql": "SELECT \"p\".\"address_street\", \"p\".\"address_postal_code\" FROM \"real_estate\".\"properties\" AS \"p\" WHERE \"p\".listing_status = 'Active' AND \"p\".\"description\" IS NOT NULL AND (\"p\".description ILIKE '%turnkey%' OR \"p\".description ILIKE '%move-in%')"
      }
    }

Envoyez cette requête (Q11) :

List move-in ready houses.

Requête obtenue :

SELECT "p"."address_street", "p"."address_postal_code" 
FROM "real_estate"."properties" AS "p" 
WHERE "p".listing_status = 'Active' 
  AND "p"."description" IS NOT NULL 
  AND ("p".description ILIKE '%turnkey%' OR "p".description ILIKE '%move-in%')

Il renvoie un enregistrement correspondant.

Query Results
address_street        address_postal_code
40 Main St, Unit 8        N2H 2G8

Modèle avec la fonction AI.IF

Téléchargez le fichier de contexte du modèle AI.IF :

cloudshell download ~/codelabs/alloydb-ai-nla/sixrecipe_context4_2.json

Importez sixrecipe_context4_2.json dans votre ensemble de contexte pour évaluer la fonction de filtrage sémantique ai.if.

    {
      "nl_query": "List Move-in ready houses",
      "sql": "SELECT \"p\".\"address_street\", \"p\".\"address_postal_code\" FROM \"real_estate\".\"properties\" AS \"p\" WHERE \"p\".listing_status = 'Active' AND \"p\".\"description\" IS NOT NULL AND ai.if('Does this description represent a move-in ready house? Description: ' || \"p\".\"description\")",
      "intent": "List Move-in ready houses",
      "manifest": "List Move-in ready houses",
      "parameterized": {
        "parameterized_intent": "List Move-in ready houses",
        "parameterized_sql": "SELECT \"p\".\"address_street\", \"p\".\"address_postal_code\" FROM \"real_estate\".\"properties\" AS \"p\" WHERE \"p\".listing_status = 'Active' AND \"p\".\"description\" IS NOT NULL AND ai.if('Does this description represent a move-in ready house? Description: ' || \"p\".\"description\")"
      }
    }

Répétez la requête (Q12) :

List move-in ready houses.

Code SQL généré :

SELECT "p"."address_street", "p"."address_postal_code" 
FROM "real_estate"."properties" AS "p" 
WHERE "p".listing_status = 'Active' 
  AND "p"."description" IS NOT NULL 
  AND ai.if('Does this description represent a move-in ready house? Description: ' || "p"."description")

Cela nous donne la même propriété dans le résultat :

Query Results
Address Street        Address Postal Code
40 Main St, Unit 8        N2H 2G8
Natural Language Answer
The move-in ready home currently available is located at:

Address: 40 Main St, Unit 8
Postal Code: N2H 2G8

Téléchargez le fichier de contexte de recherche d'embedding :

cloudshell download ~/codelabs/alloydb-ai-nla/sixrecipe_context4_3.json

Importez sixrecipe_context4_3.json dans votre ensemble de contexte pour évaluer la recherche vectorielle sémantique à l'aide de google_ml.embedding et recherchez des maisons avec de nouveaux appareils de cuisine.

Mettez à jour l'ensemble de contexte à l'aide du fichier sixrecipe_context4_3.json avec une facette supplémentaire à l'aide de la recherche vectorielle AlloyDB.

    {
      "sql_snippet": "((real_estate.properties.description_embedding <=> google_ml.embedding('gemini-embedding-001', 'Upgraded kitchen appliances')::vector) < 0.4)",
      "intent": "with upgraded kitchen appliances",
      "manifest": "with upgraded kitchen appliances",
      "parameterized": {
        "parameterized_intent": "with upgraded kitchen appliances",
        "parameterized_sql_snippet": "((real_estate.properties.description_embedding <=> google_ml.embedding('gemini-embedding-001', 'Upgraded kitchen appliances')::vector) < 0.4)"
      }
    }

La valeur 0,4 dans la définition de facette ci-dessus est un seuil calibré de manière expérimentale.

Exécutez cette requête (Q13) :

List move-in ready houses with upgraded kitchen appliances.

Code SQL généré :

SELECT "p"."address_street", "p"."address_postal_code" FROM "real_estate"."properties" AS "p" WHERE "p"."listing_status" = 'Active' AND "p"."description" IS NOT NULL AND ai.if('Does this description represent a Move-in ready house? Description: ' || "p"."description") AND ((("p"."description_embedding" <=> google_ml.embedding('gemini-embedding-001', 'Upgraded kitchen appliances')::vector) < 0.4))

Voici les résultats de la requête et la recommandation en langage naturel :

Query Results
Address        Postal Code
40 Main St, Unit 8        N2H 2G8
Natural Language Answer
The move-in ready house with upgraded kitchen appliances is located at:

Address: 40 Main St, Unit 8
Postal Code: N2H 2G8

12. Sécurité et confidentialité

Chaque fois que l'API QueryData génère des requêtes, il existe un risque de confidentialité que les requêtes générées puissent exposer des données appartenant à d'autres utilisateurs. Les vues sécurisées paramétrées (VSP) appliquent des limites de sécurité au niveau des lignes lors de la génération de NL2SQL.

Créer une vue sécurisée paramétrée

Nous disposons d'une table real_estate.offers contenant des informations sur les offres pour toutes nos transactions immobilières. Nous souhaitons que nos agents ne voient que leurs propres offres, et non celles des autres agents. Nous créons un fichier PSV avec la possibilité de filtrer les lignes en fonction de l'agent_id.

Créez une vue sécurisée paramétrée pour la table des offres dans AlloyDB Studio :

CREATE VIEW real_estate.secure_offers
  WITH (security_barrier) AS
SELECT offer_id, property_id, buyer_agent_id,
    seller_agent_id, offer_amount, offer_date,
    offer_status, offer_expiration_date,
    contingencies
FROM real_estate.offers
WHERE buyer_agent_id = $@agent_id;

Vérifiez la définition de la vue de sécurité paramétrée :

SELECT definition
FROM parameterized_views.all_parameterized_views
WHERE schemaname = 'real_estate' AND viewname = 'secure_offers';

Qu'en est-il de la table real_estate.offers ?

Limiter l'accès aux données QueryData à l'aide de vues sécurisées paramétrées AlloyDB

Dans agent.py, la vue de sécurité paramétrée real_estate.secure_offers est enregistrée dans TABLE_IDS au lieu de real_estate.offers :

Code source de agent.py :

 List of database tables included in QueryData API requests
TABLE_IDS = [
    "real_estate.municipalities",
    "real_estate.cities",
    "real_estate.agents",
    "real_estate.properties",
    "real_estate.schools",
    "real_estate.school_to_property",
    "real_estate.property_transactions",
    "real_estate.secure_offers",
]

Et transmet le contexte d'exécution de la session pour un agent avec agent_id=1 :

            "parameterized_secure_view_parameters": {
                "parameters": {
                    "key": "agent_id",
                    "value": "1"
                }
            }
        },

Par conséquent, chaque requête API QueryData transmettra ce paramètre. Lorsque la vue sécurisée est incluse dans la requête, elle applique un filtrage qui ne renvoie que les résultats pour cet agent spécifique.

Exécutez cette requête (Q14) :

List the offer id and listing price in 2025 where the offer was within 5% of the final selling price.

Code SQL généré :

SELECT "o"."offer_id", "p"."price" AS "listing_price" 
FROM "real_estate"."secure_offers" AS "o" 
INNER JOIN "real_estate"."properties" AS "p" ON "o"."property_id" = "p"."property_id" 
INNER JOIN "real_estate"."property_transactions" AS "t" ON "o"."property_id" = "t"."property_id" 
WHERE "o"."offer_date" >= '2025-01-01' 
  AND "o"."offer_date" <= '2025-12-31' 
  AND "o"."offer_amount" IS NOT NULL 
  AND "t"."sale_price" IS NOT NULL 
  AND "p"."price" IS NOT NULL 
  AND ABS("o"."offer_amount" - "t"."sale_price") <= (0.05 * "t"."sale_price") 
ORDER BY "o"."offer_id" ASC NULLS LAST

L'interrogation de la vue filtre les résultats en fonction du paramètre de session d'agent actif, protégeant ainsi les données d'enregistrement non autorisées et ne renvoyant que deux enregistrements.

Query Results
Offer ID        Listing Price
7        $750,000.00
8        $510,000.00
Natural Language Answer
Here are the offers from 2025 where the offer amount was within 5% of the final selling price:

Offer ID 7: Listing Price of $750,000.00
Offer ID 8: Listing Price of $510,000.00

Si vous exécutez la requête suivante sur la table de base real_estate.offers dans AlloyDB Studio :

SELECT "o"."offer_id", "p"."price" AS "listing_price" 
FROM "real_estate"."offers" AS "o" 
INNER JOIN "real_estate"."properties" AS "p" ON "o"."property_id" = "p"."property_id" 
INNER JOIN "real_estate"."property_transactions" AS "t" ON "o"."property_id" = "t"."property_id" 
WHERE "o"."offer_date" >= '2025-01-01' 
  AND "o"."offer_date" <= '2025-12-31' 
  AND "o"."offer_amount" IS NOT NULL 
  AND "t"."sale_price" IS NOT NULL 
  AND "p"."price" IS NOT NULL 
  AND ABS("o"."offer_amount" - "t"."sale_price") <= (0.05 * "t"."sale_price") 
ORDER BY "o"."offer_id" ASC NULLS LAST

Elle renvoie plus de deux enregistrements, affichant les données de tous les agents.

Cet atelier est maintenant terminé. Vous avez appris à utiliser l'API QueryData, les fonctions d'IA et les vues sécurisées paramétrées AlloyDB.

13. Nettoyer l'environnement

Détruisez les instances et le cluster AlloyDB une fois l'atelier terminé.

Supprimer le cluster AlloyDB et toutes les instances

Le cluster est détruit avec l'option force, qui supprime également toutes les instances appartenant au cluster.

Si votre terminal s'est déconnecté, reconnectez-vous et définissez les variables de projet et d'environnement dans Cloud Shell :

gcloud config set project <YOUR_PROJECT_ID>
export REGION=us-central1
export ADBCLUSTER=alloydb-aip-01
export PROJECT_ID=$(gcloud config get-value project)

Supprimez le cluster :

gcloud alloydb clusters delete $ADBCLUSTER --region=$REGION --force

Résultat attendu sur la console :

student@cloudshell:~ (test-project-001-402417)$ gcloud alloydb clusters delete $ADBCLUSTER --region=$REGION --force
All of the cluster data will be lost when the cluster is deleted.

Do you want to continue (Y/n)?  Y

Operation ID: operation-1697820178429-6082890a0b570-4a72f7e4-4c5df36f
Deleting cluster...done.   

Supprimer les sauvegardes AlloyDB

Supprimez toutes les sauvegardes AlloyDB du cluster :

for i in $(gcloud alloydb backups list \
  --filter="CLUSTER_NAME: projects/$PROJECT_ID/locations/$REGION/clusters/$ADBCLUSTER" \
  --format="value(name)" \
  --sort-by=~createTime) ; do \
  gcloud alloydb backups delete $(basename $i) --region $REGION --quiet; done

Résultat attendu sur la console :

student@cloudshell:~ (test-project-001-402417)$ for i in $(gcloud alloydb backups list --filter="CLUSTER_NAME: projects/$PROJECT_ID/locations/$REGION/clusters/$ADBCLUSTER" --format="value(name)" --sort-by=~createTime) ; do gcloud alloydb backups delete $(basename $i) --region $REGION --quiet; done
Operation ID: operation-1697826266108-60829fb7b5258-7f99dc0b-99f3c35f
Deleting backup...done.                                                                                                                                                                                                                                                            

Supprimer le bucket Cloud Storage

Supprimez le bucket d'importation Cloud Storage pour éviter des frais de stockage continus :

gcloud storage rm -r gs://${PROJECT_ID}-import 

14. Félicitations

Félicitations, vous avez terminé cet atelier de programmation !

Points abordés

  • Déployer un cluster et une instance principale AlloyDB
  • Activer QueryData, les fonctions d'IA et les vues sécurisées paramétrées pour AlloyDB
  • Créer un ensemble de contextes QueryData
  • Utiliser différents modèles et contextes dans l'API QueryData
  • Rendre les modèles QueryData plus flexibles à l'aide de facettes
  • Résoudre l'ambiguïté des données avec la recherche de valeurs et l'indexation par trigrammes
  • Utiliser les fonctions d'IA pour la recherche sémantique avec des modèles
  • Utiliser des vues sécurisées paramétrées avec l'API QueryData

Enquête

Résultat :

Comment allez-vous utiliser ce tutoriel ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices