Seis recetas para alcanzar una precisión cercana al 100% en la conversión de texto a SQL en AlloyDB: Codelab

1. Introducción

En este codelab, aprenderás a obtener los mejores resultados con la API de QueryData. Descubrirás cómo generar resultados precisos y repetibles cuando realices solicitudes en lenguaje natural a tu base de datos. Para ayudarte a lograrlo, la guía te explica seis recetas en varios capítulos, en los que se exploran diferentes aspectos del lenguaje natural a SQL (NL2SQL) y sus aplicaciones prácticas.

Requisitos previos

  • Conocimientos básicos de Google Cloud y la consola de Google Cloud
  • Habilidades básicas con la interfaz de línea de comandos y Cloud Shell

Qué aprenderás

  • Cómo implementar un clúster y una instancia principal de AlloyDB
  • Cómo habilitar QueryData, las funciones potenciadas por IA y las vistas seguras parametrizadas
  • Cómo crear un conjunto de contexto de QueryData
  • Cómo usar diferentes plantillas y contextos en la API de QueryData
  • Cómo hacer que las plantillas de QueryData sean más flexibles con facetas
  • Cómo resolver la ambigüedad de los datos con la búsqueda de valores y la indexación de trigramas
  • Cómo usar las funciones de IA para la búsqueda semántica con plantillas
  • Cómo usar vistas seguras con parámetros con la API de QueryData

Requisitos

  • Una cuenta y un proyecto de Google Cloud
  • Un navegador web, como Chrome, que admita la consola de Google Cloud y Cloud Shell

2. Configuración y requisitos

Configuración del proyecto

  1. Accede a la consola de Google Cloud. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.

Usa una cuenta personal en lugar de una cuenta laboral o educativa.

  1. Crea un proyecto nuevo o reutiliza uno existente. Para crear un proyecto nuevo en la consola de Google Cloud, haz clic en el botón Seleccionar un proyecto del encabezado, que abrirá una ventana emergente.

295004821bab6a87.png

En la ventana Selecciona un proyecto, haz clic en Nuevo proyecto, que abrirá un cuadro de diálogo para el proyecto nuevo.

37d264871000675d.png

En el cuadro de diálogo, ingresa un nombre para el proyecto y elige la ubicación.

96d86d3d5655cdbe.png

  • El Nombre del proyecto es el nombre visible de los participantes de este proyecto. El nombre del proyecto no se usa en las APIs de Google y se puede cambiar en cualquier momento.
  • El ID del proyecto es único en todos los proyectos de Google Cloud y es inmutable (no se puede cambiar después de configurarlo). La consola de Google Cloud genera automáticamente un ID único, pero puedes personalizarlo. Si no te gusta el ID generado, puedes generar otro aleatorio o proporcionar el tuyo para verificar su disponibilidad. En la mayoría de los codelabs, deberás hacer referencia al ID del proyecto, que suele identificarse con el marcador de posición PROJECT_ID.
  • Recuerda que hay un tercer valor, un número de proyecto, que usan algunas APIs. Obtén más información sobre estos tres valores en la documentación.

Habilitar facturación

Configura una cuenta de facturación personal

Si configuraste la facturación con créditos de Google Cloud, puedes omitir este paso.

Para configurar una cuenta de facturación personal, ve aquí para habilitar la facturación en la consola de Cloud.

Algunas notas:

  • Completar este lab debería costar menos de USD 5 en recursos de Cloud.
  • Puedes seguir los pasos al final de este lab para borrar recursos y evitar cargos adicionales.
  • Los usuarios nuevos pueden acceder a la prueba gratuita de USD 300.

Inicie Cloud Shell

Si bien Google Cloud y Spanner se pueden operar de manera remota desde tu laptop, en este codelab usarás Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

En Google Cloud Console, haz clic en el ícono de Cloud Shell en la barra de herramientas en la parte superior derecha:

Activar Cloud Shell

También puedes presionar G y, luego, S. Esta secuencia activará Cloud Shell si estás en la consola de Google Cloud o usas este vínculo.

El aprovisionamiento y la conexión al entorno deberían tomar solo unos minutos. Cuando termine el proceso, debería ver algo como lo siguiente:

Captura de pantalla de la terminal de Google Cloud Shell que muestra que el entorno se conectó

Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Todo tu trabajo en este codelab se puede hacer en un navegador. No es necesario que instales nada.

3. Antes de comenzar

Habilitar la API

Para usar AlloyDB, Compute Engine, los servicios de redes y la Agent Platform, habilita sus respectivas APIs en tu proyecto de Google Cloud.

En la terminal de Cloud Shell, asegúrate de que tu ID del proyecto esté configurado:

gcloud config get-value project

El comando debería devolver el ID de tu proyecto de Google Cloud.

Configura la variable de entorno PROJECT_ID:

PROJECT_ID=$(gcloud config get-value project)

Habilita las APIs necesarias:

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

Resultado esperado:

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.

Puedes leer sobre cada API habilitada en la documentación.

4. Implementa AlloyDB

Crea un clúster y una instancia principal de AlloyDB. Puedes implementarlo con una secuencia de comandos preparada que implementará todos los recursos necesarios o puedes hacerlo paso a paso por tu cuenta siguiendo la documentación de AlloyDB.

Implementa AlloyDB con una secuencia de comandos automatizada

Este enfoque utiliza una secuencia de comandos automatizada para implementar el clúster de AlloyDB y proporciona los detalles de configuración necesarios para comenzar a trabajar con los recursos implementados.

En la terminal de Cloud Shell, ejecuta el comando para clonar la secuencia de comandos de implementación desde el repositorio:

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

Ejecuta la secuencia de comandos de implementación:

./deploy_alloydb.sh

La secuencia de comandos tarda entre cinco y siete minutos en completarse. En el resultado, se muestran los detalles de la implementación del clúster. Ten en cuenta que tu contraseña será diferente. Regístrala para usarla más adelante.

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

Visualiza tu nuevo clúster y tu nueva instancia en la página Clústeres de AlloyDB en la consola de Google Cloud.

4271eb55bcc9ec84.png

5. Prepara la base de datos

Crea una base de datos, habilita la integración de Gemini Enterprise Agent Platform, crea objetos de base de datos y agrega datos de muestra.

Otorga los permisos necesarios a AlloyDB

Otorga los roles de IAM necesarios al agente de servicio de AlloyDB:

Haz clic en Agregar (+) para abrir una pestaña nueva de Cloud Shell.

abc505ac4d41f24e.png

En la pestaña nueva, ejecuta lo siguiente:

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"

Resultado esperado en la consola:

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
 

Habilita la API de Data Access

Habilita la API de Data Access en tu clúster de AlloyDB.

En la misma pestaña de la terminal, ejecuta el siguiente comando:

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",
 }'

Actualiza las marcas de la base de datos

Antes de habilitar marcas a nivel de la instancia, espera a que finalice el paso de la API de Data Access. Asegúrate de que el estado de tu instancia muestre una marca de verificación verde, como se ilustra en la siguiente imagen.

6284731b8eff74c.jpeg

Para usar las extensiones de IA y la autenticación de IAM para las herramientas basadas en agentes, debes habilitar marcas de bases de datos específicas en la instancia y agregarte como usuario de la base de datos. Habilita los siguientes parámetros:

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

En la misma pestaña de la terminal, ejecuta los siguientes comandos:

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

Crea un usuario de IAM de AlloyDB para tu cuenta:

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

Cierra la pestaña ejecutando el comando de salida en la terminal:

exit

Conéctate a AlloyDB Studio

En los siguientes capítulos, puedes ejecutar todos los comandos SQL que requieren una conexión de base de datos en AlloyDB Studio.

Para abrir AlloyDB Studio, sigue estos pasos:

  1. En la consola de Google Cloud, ve a la página Clústeres de AlloyDB para PostgreSQL.
  2. Selecciona tu instancia principal para abrir su interfaz web.

1d7298e7096e7313.png

  1. Luego, haz clic en AlloyDB Studio a la izquierda:

a33131c72ad29478.png

  1. Selecciona la base de datos postgres, elige Autenticación de IAM y haz clic en Autenticar.

8ddfb250344ca749.jpeg

Esta acción abrirá la interfaz de AlloyDB Studio.

  1. Selecciona la pestaña Consulta sin título para abrir el editor de SQL.

6696bc771fab9983.png

Se abrirá un editor en el que podrás ejecutar comandos de SQL, como se muestra en la siguiente imagen:

ae34288e5bf237c7.png

Crear base de datos

Crea la base de datos de inicio rápido

En el editor de AlloyDB Studio, ejecuta el siguiente comando para crear la base de datos:

CREATE DATABASE quickstart_db

Resultado esperado:

Statement executed successfully

Conéctate a quickstart_db

Haz clic en Cambiar base de datos o usuario en AlloyDB Studio.

6f5c98f5d0b3d550.png

Selecciona quickstart_db en la lista de bases de datos, selecciona Autenticación de IAM y autentícate.

a1940c26c36ff840.jpeg

Se abrirá una nueva conexión en la que podrás trabajar con objetos de la base de datos quickstart_db.

Habilita las extensiones

Verifica todas las extensiones requeridas para asegurarte de que estén habilitadas en tu base de datos y tengan las versiones mínimas correctas.

Verifica que todas las extensiones requeridas existan en tu base de datos:

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

Resultado esperado:

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

Si no devuelve todas las filas anteriores, significa que algunas de las extensiones no están habilitadas.

Antes de habilitar las extensiones, debemos asegurarnos de que todas las marcas de la base de datos ,"google_ml_integration.enable_ai_query_engine" y "parameterized_views.enabled" estén habilitadas en la instancia.

En AlloyDB Studio, mientras estás conectado a quickstart_db, ejecuta lo siguiente:

show google_ml_integration.enable_ai_query_engine;
show parameterized_views.enabled;

Si alguna de ellas muestra el estado "off", intenta repetir el paso "Actualizar marcas de la base de datos" y vuelve a verificar el estado con los comandos "show…".

Habilita las extensiones necesarias.

En AlloyDB Studio, mientras estás conectado a quickstart_db, ejecuta lo siguiente:

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

Verifica las versiones de las extensiones para parameterized_views y google_ml.

En AlloyDB Studio, mientras estás conectado a quickstart_db, ejecuta lo siguiente:

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

Resultado esperado:

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

Si la versión es inferior a la requerida, intenta actualizar la extensión parameterized_views.

Para parameterized_views, prueba lo siguiente:

ALTER EXTENSION parameterized_views UPDATE;

Vuelve a verificar la versión.

En AlloyDB Studio, mientras estás conectado a quickstart_db, ejecuta lo siguiente:

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

Resultado esperado:

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

6. Datos de muestra

Ahora debes crear objetos en la base de datos y cargar datos. Usarás un conjunto de datos ficticio de una empresa de bienes raíces. Contiene datos ficticios sobre ventas de propiedades y agentes. Los datos se proporcionaron en el lenguaje SQL real_estate_data.sql del repositorio clonado.

Cree un bucket de almacenamiento

Usa el SDK de Google (gcloud) para importar datos desde tu repositorio clonado a la base de datos de AlloyDB. Para ello, debes crear un bucket de Cloud Storage y otorgar acceso a la cuenta de servicio de AlloyDB. Como alternativa, siempre puedes intentar hacerlo con la consola web, como se describe en la documentación.

En la terminal de Google Cloud Shell, ejecuta lo siguiente:

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

Cargar datos

El volcado de SQL de la base de datos se encuentra en la carpeta del repositorio clonado. En el siguiente comando, se supone que usaste tu directorio principal como punto de partida cuando clonaste el repositorio en el paso anterior mientras creabas el clúster de AlloyDB.

Copia el archivo de volcado de la base de datos en tu bucket de 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

Importa el conjunto de datos a 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

El comando cargará el conjunto de datos de muestra en la base de datos quickstart_db.

Verifica la importación contando los registros en la tabla real_estate.agents con AlloyDB Studio:

SELECT COUNT(*) FROM real_estate.agents;

7. Prepara la ontología del esquema

Configura tu conjunto de contexto de QueryData, proporciona metadatos de esquema y ejecuta el agente del ADK.

Crear metadatos

Los metadatos consisten en comentarios SQL aplicados a las tablas y columnas de la base de datos. Estos comentarios guían el motor de QueryData cuando selecciona objetos de la base de datos.

Si importaste tu esquema de datos con el volcado de SQL proporcionado, todos los metadatos ya se crearon en tu esquema.

A continuación, se muestra un ejemplo de un fragmento de metadatos aplicado al esquema 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 trabajas con tu propio conjunto de datos, puedes seguir la guía documentada para agregar los metadatos de tu esquema.

Crea un contexto de ontología de esquema

Un conjunto de contexto contiene plantillas de búsqueda, facetas y búsquedas de valores que ayudan al modelo de IA a generar búsquedas. Sin el contexto establecido, un agente de IA intentará generar una consulta usando solo la información disponible del esquema de public, principalmente en función de los nombres de las tablas, las columnas y los metadatos, para deducir los objetos correctos que permitan responder una solicitud de consulta natural.

El conjunto de contexto QueryData es un archivo JSON con plantillas de búsqueda, facetas y búsquedas de valores. Puedes consultar diferentes ejemplos en la documentación.

Carga el conjunto de contexto de QueryData en la consola web

Para cargar un conjunto de contexto, abre AlloyDB Studio y, en el panel izquierdo, en la parte inferior, verás Context sets y tres puntos.

d3578ceee65cd0dd.jpeg

Haz clic en esos tres puntos y elige Create context set.

En el diálogo que se abrió, ingresa los siguientes valores:

  • Nombre: sixrecipes
  • Descripción: Real Estate Query Data
  • Sube el archivo de contexto: Déjalo vacío

Cuando presiones el botón Create, es posible que la inicialización del almacenamiento del contexto tarde un tiempo si lo haces por primera vez.

Vuelve a hacer clic en los tres puntos que se encuentran a la derecha del nombre del conjunto de contexto y elige Edit context set para verificar el ID del conjunto de contexto de QueryData.

6739fb9f54d7f79d.jpeg

El ID del conjunto de contexto aparece en la parte superior de la pestaña abierta a la derecha.

746b38c52a70bb3c.jpeg

Guarda este ID para los pasos posteriores.

Iniciar el agente de IA

Para interactuar con el backend de QueryData y generar y ejecutar consultas, puedes usar la interfaz de AlloyDB Studio, como se describe en la documentación. Sin embargo, en este lab, usarás un agente de IA como la principal herramienta de comunicación con la base de datos. Los agentes de IA son un escenario más realista para las cargas de trabajo futuras, y el framework de ADK de Google proporciona una excelente interfaz web en la que puedes ver todos los pasos y las interacciones entre los modelos, las APIs y las aplicaciones.

Revisa la configuración de la fuente del agente en Cloud Shell:

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

En el archivo, puedes ver dónde definimos la lista de tablas en nuestro esquema. Volveremos a tratar este tema en uno de nuestros próximos pasos.

# 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",
]

Además, hay un procedimiento call_querydata_api para trabajar con la API de QueryData junto con un conjunto de herramientas para trabajar con las habilidades de la API de 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]
)

Aquí se muestra el agente raíz con una instrucción y las herramientas 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],
)

Revisa data_agent/SKILL.md para comprender las instrucciones del sistema del agente:

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

Antes de iniciar el agente, debes preparar un archivo .env con parámetros para las solicitudes de la API de Agent y QueryData.

Exporta el ID del conjunto de contexto a una variable de entorno:

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

Crea tu archivo de configuración del entorno de ejecución (.env) en el directorio 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

Instala las dependencias e inicia el servicio web del agente:

cd ~/$REPO_NAME/$SOURCE_DIR/
uv sync

Inicia el agente:

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

Deberías ver un resultado como el siguiente con el extremo 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

Haz clic en la URL del servidor web generada (p.ej., http://127.0.0.1:8000) en Cloud Shell para iniciar la interfaz del agente. Si no funciona, en Cloud Shell, haz clic en Vista previa en la Web -> Vista previa en el puerto 8000 para abrir la interfaz del agente.

Verifica que data_agent esté seleccionado en el menú desplegable de la parte superior izquierda.

ad695db289666da3.jpeg

Generar y ejecutar consultas

Ejecutemos la consulta Q1

Ejecuta esta instrucción en lenguaje natural en la interfaz de chat del agente para evaluar el rendimiento de QueryData base sin plantillas ni facetas personalizadas:

How many properties are currently for sale in San Diego?

Y presiona el botón de flecha.

77773a81de6360a2.jpeg

Iniciará el flujo de trabajo del agente cuando seleccione la habilidad adecuada y use esa habilidad y la función para enviar la solicitud al endpoint de API y recuperar la búsqueda generada y los resultados.

de9b3e4854b9a506.jpeg

Si colocas el cursor del mouse sobre los pasos, puedes ver exactamente qué hacían. También puedes ver eso y mucho más en el panel izquierdo cuando eliges un paso u otro. Puede ayudarte a comprender cómo funcionan los agentes con las habilidades y las APIs.

605dcf936ba38cb8.jpeg

Envía una consulta de seguimiento (P2):

List agents who sold a property for over $1000000.

Notarás que el agente va directamente a la habilidad de la API de QueryData, ya que ya escribió en la memoria las habilidades disponibles y no necesita volver a pasar por la fase de descubrimiento.

El código SQL generado se parece a esta instrucción:

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;

Prueba con otra búsqueda (P3):

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

QueryData genera consultas estándar con un conjunto de contexto vacío, pero las tareas comerciales a menudo requieren una lógica empresarial específica y un comportamiento determinístico.

8. Logra una alta precisión y relevancia comercial incorporando la interpretación

La inyección de interpretación en las instrucciones garantiza que las búsquedas generadas coincidan con una lógica empresarial específica.

Probarás un ejemplo de este tipo con un cliente que solicita casas de un solo nivel en Del Mar. La pregunta tiene dos métricas importantes y representa un patrón frecuente para una selección.

Prueba la siguiente instrucción en lenguaje natural (P4) en el agente.

Single level Del Mar houses

El motor devuelve una búsqueda general, pero puede incluir algunas condiciones adicionales, como 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;

Obtenemos una fila devuelta como resultado:

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.

Prueba otra instrucción (P5):

Del Mar houses close to good schools

Es lo suficientemente inteligente como para usar las columnas proximity_miles y school_ranking para ordenar las propiedades, pero no evalúa el peso ni la importancia de cada métrica.

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;

Generaliza la consulta de ejemplo en una plantilla

La lógica empresarial requiere lo siguiente:

  • Proximidad en un radio de 3.2 kilómetros
  • Clasificación de la escuela de 5 o menos
  • Ordenamiento ponderado: 80% de ranking de la institución educativa y 20% de proximidad (puntuación = 0.8 * ranking_de_la_institución_educativa + 0.2 * proximidad_en_millas)

Esta es la fórmula de clasificación ponderada:

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

Descarga el archivo de contexto sixrecipe_context1_1.json desde Cloud Shell con la interfaz de línea de comandos en Cloud Shell:

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

O bien, usa el elemento de menú Download del menú de Cloud Shell:

c0d781806d966dcb.jpeg

Sube el sixrecipe_context1_1.json a tu conjunto de contexto sixrecipe con AlloyDB Studio para definir esta estructura estándar. Haz clic en los tres puntos a la derecha del contexto sixrecipes y usa "Editar conjunto de contexto" para subir el archivo nuevo. Reemplazará de manera efectiva el contexto vacío anterior por el nuevo.

La plantilla define reglas explícitas de plantillas de SQL para reglas de buena proximidad a la escuela:

{
  "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\""
      }
    }
  ]
}

Vuelve a ejecutar la instrucción:

Del Mar houses close to good schools

La consulta resultante ahora coincide con la lógica empresarial definida en la plantilla:

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"

Junto con la sentencia de SQL generada, se produce la siguiente intención:

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

Puedes ver la intención en el panel izquierdo de la interfaz web del ADK, como se muestra aquí:

bfb9d6bc718f57cf.jpeg

Los resultados muestran dos propiedades ordenadas según nuestra puntuación:

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

En el siguiente capítulo, ampliarás el alcance de la plantilla.

Cómo insertar la interpretación capturando e interpretando varias preguntas

Puedes ampliar el alcance de nuestra plantilla para que se use no solo para "cerca de buenas escuelas", sino también para agregar otras condiciones al manifiesto, por ejemplo, para "casas para familias con niños".

Descarga el archivo de contexto actualizado:

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

Sube el archivo sixrecipe_context1_2.json actualizado a tu conjunto de contexto.

En el nuevo archivo de configuración del contexto, reemplazamos nuestro manifiesto de "manifest": "A given city houses close to good schools." al manifiesto "manifest": "A given city houses close to good schools, or are great for families with kids.". Expande las aplicaciones de esta plantilla para que se pueda usar en más de un caso.

Envía esta consulta (P6):

Houses in Waterloo great for families with kids.

El agente usa el manifiesto de plantilla actualizado para aplicar tu lógica de búsqueda estandarizada y devolver la misma búsqueda que antes, con la siguiente intención:

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

9. Usa plantillas facetadas para lograr una alta precisión y mantener la flexibilidad

Las facetas son condiciones SQL modulares que se combinan de forma dinámica con plantillas para controlar los calificadores (p.ej., "familiar para principiantes", "vista al océano") sin crear plantillas separadas para cada permutación.

En lugar de crear una plantilla para cada condición o consulta, podemos preparar fragmentos que se pueden aplicar a las plantillas existentes y agregar más dimensiones a la consulta.

Define una faceta para las propiedades con los siguientes atributos:

  • Una casa unifamiliar
  • Un estado de ficha activa
  • De tres a cuatro habitaciones y al menos dos baños
  • Ubicación en un municipio con una tasa de delincuencia inferior a 500 por cada 100,000 residentes

Descarga el archivo de contexto facetado:

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

Sube sixrecipe_context2_1.json a tu conjunto de contexto de sixrecipes en AlloyDB Studio para configurar las facetas.

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

Ejecuta esta búsqueda con lenguaje natural (P7):

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

La app del motor agrega tus condiciones de faceta directamente a la consulta 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"

Estos son los resultados:

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

Cómo agregar más facetas

¿Qué sucede si queremos agregar una dimensión más basada en el nuevo patrón emergente, como una vista de ventana para la propiedad?

Agrega la coincidencia semántica de la vista al mar dentro de una definición de faceta.

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

El valor 0.4 en la definición de faceta anterior es un umbral calibrado de forma experimental. Descarga el archivo de contexto multifacético:

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

Sube sixrecipe_context2_2.json a tu conjunto de contexto y ejecuta esta instrucción (P8):

Ocean view Del Mar homes near good schools.

El resultado contiene condiciones estándar combinadas con la coincidencia de similitud de vectores a través de 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";

Y la consulta devuelve una propiedad:

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

Para verificar los resultados, consulta la descripción de la propiedad en AlloyDB Studio y ejecuta la siguiente consulta:

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

Se mostrará la descripción con la frase "magnífico espectáculo marino".

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. Desambiguación de tus datos privados con las búsquedas de valores

Las solicitudes ambiguas pueden coincidir con varias entidades, como confundir el nombre de una ubicación con el nombre de una empresa. Las búsquedas de valores asignan términos a conceptos de la base de datos para ayudar a resolver estas referencias.

La búsqueda de valores proporciona asociaciones entre el nombre de la columna y los valores de la columna, y un tipo de concepto que puede ser, por ejemplo, una ciudad, un país o un nombre. Además, como indexó no solo los metadatos, sino también muestras representativas de los datos en una columna, puede asociar un valor utilizado en la solicitud en lenguaje natural con un tipo de concepto potencial y con la tabla y la columna que se pueden usar en el SQL resultante.

Ambigüedad en una búsqueda

En la vida real, puede ser aún más difícil debido a los errores ortográficos de diferentes nombres y ubicaciones. Ejecuta esta instrucción (P9) que contiene un error tipográfico y un término ambiguo:

Westwod's sold properties in the last 1 month.

Aquí hay dos problemas: el nombre está mal escrito y no queda claro si hablamos de propiedades vendidas en la ubicación con nombres similares o de una agencia de bienes raíces. Cuando ejecutamos la consulta, se muestra una consulta como la siguiente.

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

Corrigió el nombre y supuso que la pregunta era sobre la ubicación de las propiedades, tratando de encontrar propiedades en la ciudad de Westwood.

Habilita la extensión pg_trgm en AlloyDB Studio para admitir comparaciones de cadenas aproximadas:

CREATE EXTENSION IF NOT EXISTS pg_trgm;

Después de eso, podemos usar la extensión para agregar búsquedas de valores a nuestro conjunto de contexto de QueryData.

Esta es la lista de búsquedas de valores que queremos usar:

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

Descarga el archivo de contexto de búsqueda de valores:

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

Sube sixrecipe_context3_1.json al conjunto de información contextual de QueryData sixrecipes y guárdalo.

Vuelve a ejecutar la instrucción (P9):

Westwod's sold properties in the last 1 month.

Devolvió una respuesta de aclaración porque, en la búsqueda de valores, encontró 3 entidades diferentes con valores similares:

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?

El registro nos da una pista de por qué eligió esa ruta en particular.

88c52932dcbef8f9.jpeg

Este es el texto completo:

"[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?"

Cómo eliminar la ambigüedad de la búsqueda

Define mejor la instrucción de entrada (P10):

Westwood city location properties sold in September 2025.

Ahora tenemos la búsqueda y los resultados:

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. Búsqueda semántica y funciones potenciadas por IA

Las funciones IA de AlloyDB permiten el filtrado semántico en datos no estructurados. Usa funciones como ai.if y google_ml.embedding directamente en las consultas de SQL junto con los operadores de bases de datos estándar.

Plantilla con operadores ILIKE

Como primer paso, sube el nuevo archivo de contexto con operadores ILIKE.

Descarga el archivo de contexto de la plantilla de ILIKE:

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

Sube sixrecipe_context4_1.json a tu conjunto de contexto para evaluar la coincidencia de patrones ILIKE estándar para las casas "listas para mudarse".

    {
      "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%')"
      }
    }

Envía esta consulta (P11):

List move-in ready houses.

Consulta resultante:

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

Devuelve un registro coincidente.

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

Plantilla con la función AI.IF

Descarga el archivo de contexto de la plantilla AI.IF:

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

Sube sixrecipe_context4_2.json a tu conjunto de contexto para evaluar la función de filtrado semántico de 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\")"
      }
    }

Repite la instrucción (P12):

List move-in ready houses.

SQL generado:

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

Esto nos da la misma propiedad en el resultado:

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

Descarga el archivo de contexto de búsqueda de la incorporación:

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

Sube sixrecipe_context4_3.json a tu conjunto de contexto para evaluar la búsqueda de vectores semánticos con google_ml.embedding y buscar casas con electrodomésticos de cocina nuevos.

Actualiza el conjunto de contexto con el archivo sixrecipe_context4_3.json con una faceta adicional usando la búsqueda de vectores de 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)"
      }
    }

El valor 0.4 en la definición de faceta anterior es un umbral calibrado de forma experimental.

Ejecuta esta consulta (P13):

List move-in ready houses with upgraded kitchen appliances.

SQL generado:

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

Estos son los resultados de la búsqueda y la recomendación en lenguaje natural:

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. Seguridad y privacidad

Cada vez que la API de QueryData genera consultas, existe el riesgo de que estas expongan datos que pertenecen a otros usuarios. Las vistas seguras parametrizadas (PSV) aplican límites de seguridad a nivel de la fila durante la generación de NL2SQL.

Crea una vista segura parametrizada

Tenemos una tabla real_estate.offers con información sobre las ofertas de todos nuestros acuerdos inmobiliarios. Queremos que nuestros agentes solo vean sus propios acuerdos, pero no los de otros agentes. Creamos un PSV con la capacidad de filtrar filas según el agent_id.

Crea una vista segura parametrizada para la tabla de ofertas en 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;

Verifica la definición de la vista de seguridad parametrizada:

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

¿Qué sucede con la tabla real_estate.offers?

Limita el acceso a los datos de QueryData con vistas seguras parametrizadas de AlloyDB

En agent.py, la vista de seguridad parametrizada real_estate.secure_offers se registra en TABLE_IDS en lugar de real_estate.offers:

El código fuente 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",
]

Y pasa el contexto de tiempo de ejecución de la sesión para un agente con agent_id=1:

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

Como resultado, cada solicitud a la API de QueryData pasará ese parámetro y, cuando se incluya la vista segura en la solicitud, se aplicará el filtrado y solo se devolverán los resultados para ese agente en particular.

Ejecuta esta consulta (P14):

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

SQL generado:

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

Cuando se consulta la vista, los resultados se filtran según el parámetro de sesión del agente activo, lo que protege los datos de registros no autorizados y muestra solo dos registros.

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 ejecutas la siguiente consulta en la tabla base real_estate.offers en 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

Devuelve más de dos registros, mostrando datos de todos los agentes.

Con esto, finaliza el lab. Aprendiste a usar la API de QueryData, las funciones basadas en IA y las vistas seguras parametrizadas de AlloyDB.

13. Limpia el entorno

Cuando termines el lab, destruye las instancias y el clúster de AlloyDB.

Borra el clúster de AlloyDB y todas las instancias

El clúster se destruye con la opción force, que también borra todas las instancias que pertenecen al clúster.

Si se desconectó tu terminal, vuelve a conectarte y define las variables de entorno y del proyecto en 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)

Borra el clúster:

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

Resultado esperado en la consola:

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.   

Borra las copias de seguridad de AlloyDB

Borra todas las copias de seguridad de AlloyDB del clúster:

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

Resultado esperado en la consola:

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.                                                                                                                                                                                                                                                            

Borra el bucket de Cloud Storage

Borra el bucket de importación de Cloud Storage para evitar cargos de almacenamiento continuos:

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

14. Felicitaciones

¡Felicitaciones por completar el codelab!

Temas abordados

  • Cómo implementar un clúster y una instancia principal de AlloyDB
  • Cómo habilitar QueryData, las funciones basadas en IA y las vistas seguras parametrizadas para AlloyDB
  • Cómo crear un conjunto de contexto de QueryData
  • Cómo usar diferentes plantillas y contextos en la API de QueryData
  • Cómo hacer que las plantillas de QueryData sean más flexibles con facetas
  • Cómo resolver la ambigüedad de los datos con la búsqueda de valores y la indexación de trigramas
  • Cómo usar las funciones de IA para la búsqueda semántica con plantillas
  • Cómo usar vistas seguras con parámetros con la API de QueryData

Encuesta

Resultado:

¿Cómo usarás este instructivo?

Solo lo leeré Lo leeré y completaré los ejercicios