Seis receitas para alcançar uma conversão de texto em SQL quase 100% precisa no AlloyDB: codelab

1. Introdução

Neste codelab, você vai aprender a ter os melhores resultados usando a API QueryData. Você vai descobrir como gerar resultados precisos e repetíveis ao fazer solicitações em linguagem natural para seu banco de dados. Para ajudar você a fazer isso, o guia mostra seis receitas em vários capítulos, explorando diferentes aspectos da linguagem natural para SQL (NL2SQL) e suas aplicações práticas.

Pré-requisitos

  • Conhecimentos básicos sobre o Google Cloud e o console do Google Cloud
  • Habilidades básicas com a interface de linha de comando e o Cloud Shell

O que você vai aprender

  • Como implantar um cluster do AlloyDB e uma instância principal
  • Como ativar o QueryData, as funções de IA e as visualizações seguras parametrizadas
  • Como criar um conjunto de contextos do QueryData
  • Como usar diferentes modelos e contextos na API QueryData
  • Como tornar os modelos QueryData mais flexíveis usando atributos
  • Como resolver a ambiguidade de dados com a pesquisa de valores e a indexação de trigramas
  • Como usar funções de IA para pesquisa semântica com modelos
  • Como usar visualizações seguras parametrizadas com a API QueryData

O que é necessário

  • Uma conta do Google Cloud e um projeto na nuvem do Google Cloud
  • Um navegador da Web, como o Chrome, que seja compatível com o console do Google Cloud e o Cloud Shell

2. Configuração e requisitos

Configuração do projeto

  1. Faça login no Console do Google Cloud. Crie uma conta do Gmail ou do Google Workspace, se ainda não tiver uma.

Use uma conta pessoal em vez de uma conta escolar ou de trabalho.

  1. Crie um projeto ou reutilize um existente. Para criar um projeto no console do Google Cloud, clique no botão "Selecionar um projeto" no cabeçalho, que vai abrir uma janela pop-up.

295004821bab6a87.png

Na janela "Selecionar um projeto", clique em "Novo projeto", que vai abrir uma caixa de diálogo para o novo projeto.

37d264871000675d.png

Na caixa de diálogo, insira um nome de projeto e escolha o local.

96d86d3d5655cdbe.png

  • O Nome do projeto é o nome de exibição para os participantes do projeto. O nome do projeto não é usado pelas APIs do Google e pode ser alterado a qualquer momento.
  • O ID do projeto é exclusivo em todos os projetos do Google Cloud e não pode ser mudado após a definição. O console do Google Cloud gera automaticamente um ID exclusivo, mas você pode personalizá-lo. Se você não gostar do ID gerado, crie outro aleatório ou forneça o seu para verificar a disponibilidade. Na maioria dos codelabs, é necessário fazer referência ao ID do projeto, que normalmente é identificado com o marcador de posição PROJECT_ID.
  • Para sua informação, há um terceiro valor, um Número do projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.

Ativar faturamento

Configurar uma conta de faturamento pessoal

Se você configurou o faturamento usando créditos do Google Cloud, pule esta etapa.

Para configurar uma conta de faturamento pessoal, acesse este link para ativar o faturamento no console do Cloud.

Algumas observações:

  • A conclusão deste laboratório custa menos de US $5 em recursos do Cloud.
  • Siga as etapas no final deste laboratório para excluir recursos e evitar mais cobranças.
  • Novos usuários podem aproveitar o teste sem custos financeiros de US$300.

Iniciar o Cloud Shell

Embora o Google Cloud e o Spanner possam ser operados remotamente do seu laptop, neste codelab usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.

No Console do Google Cloud, clique no ícone do Cloud Shell na barra de ferramentas superior à direita:

Ativar o Cloud Shell

Ou pressione G e S. Essa sequência vai ativar o Cloud Shell se você estiver no console do Google Cloud ou usar este link.

O provisionamento e a conexão com o ambiente levarão apenas alguns instantes para serem concluídos: Quando o processamento for concluído, você verá algo como:

Captura de tela do terminal do Google Cloud Shell mostrando que o ambiente foi conectado

Essa máquina virtual contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Neste codelab, todo o trabalho pode ser feito com um navegador. Você não precisa instalar nada.

3. Antes de começar

Ativar API

Para usar o AlloyDB, o Compute Engine, os serviços de rede e a Agent Platform, ative as respectivas APIs no seu projeto na nuvem do Google Cloud.

No terminal do Cloud Shell, verifique se o ID do projeto está configurado:

gcloud config get-value project

O comando vai retornar o ID do projeto do Google Cloud.

Defina a variável de ambiente PROJECT_ID:

PROJECT_ID=$(gcloud config get-value project)

Ative as APIs necessárias:

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

Saída esperada:

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.

Leia sobre cada API ativada na documentação.

4. Implantar o AlloyDB

Crie um cluster e uma instância principal do AlloyDB. É possível fazer a implantação usando um script preparado que vai implantar todos os recursos necessários ou seguir as etapas da documentação do AlloyDB.

Implantar o AlloyDB usando um script automatizado

Essa abordagem usa um script automatizado para implantar o cluster do AlloyDB e fornece os detalhes de configuração necessários para começar a trabalhar com os recursos implantados.

No terminal do Cloud Shell, execute o comando para clonar o script de implantação do repositório:

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

Execute o script de implantação:

./deploy_alloydb.sh

O script leva de cinco a sete minutos para ser concluído. A saída mostra os detalhes da implantação do cluster. Sua senha será diferente. Anote-a para uso futuro.

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

Confira o novo cluster e a instância na página de clusters do AlloyDB no console do Google Cloud.

4271eb55bcc9ec84.png

5. Preparar banco de dados

Crie um banco de dados, ative a integração da Gemini Enterprise Agent Platform, crie objetos de banco de dados e adicione dados de amostra.

Conceder as permissões necessárias ao AlloyDB

Conceda os papéis do IAM necessários ao agente de serviço do AlloyDB:

Abra uma nova guia do Cloud Shell clicando em Adicionar (+).

abc505ac4d41f24e.png

Na nova guia, execute:

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"

Saída esperada do 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
 

Ativar a API Data Access

Ative a API Data Access no cluster do AlloyDB.

Na mesma guia do terminal, execute o seguinte 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",
 }'

Atualizar flags do banco de dados

Antes de ativar flags no nível da instância, aguarde a conclusão da etapa da API Data Access. Confira se o status da instância mostra uma marca de seleção verde, conforme ilustrado na imagem a seguir.

6284731b8eff74c.jpeg

Para usar extensões de IA e autenticação do IAM em ferramentas de agente, é necessário ativar flags de banco de dados específicas na instância e se adicionar como um usuário do banco de dados. Ative as seguintes flags:

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

Na mesma guia do terminal, execute os seguintes 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

Crie um usuário do IAM no AlloyDB para sua conta:

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

Feche a guia executando o comando de saída no terminal:

exit

Conectar-se ao AlloyDB Studio

Nos capítulos a seguir, você pode executar todos os comandos SQL que exigem uma conexão de banco de dados no AlloyDB Studio.

Para abrir o AlloyDB Studio, siga estas etapas:

  1. No console do Google Cloud, acesse a página "Clusters" do AlloyDB para PostgreSQL.
  2. Selecione a instância principal para abrir a interface da Web.

1d7298e7096e7313.png

  1. Em seguida, clique em AlloyDB Studio à esquerda:

a33131c72ad29478.png

  1. Selecione o banco de dados postgres, escolha "Autenticação do IAM" e clique em "Autenticar".

8ddfb250344ca749.jpeg

Essa ação abre a interface do AlloyDB Studio.

  1. Selecione a guia "Consulta sem título" para abrir o editor de SQL.

6696bc771fab9983.png

Isso abre um editor em que é possível executar comandos SQL, conforme mostrado na imagem a seguir:

ae34288e5bf237c7.png

Criar banco de dados

Criar o banco de dados de início rápido

No editor do AlloyDB Studio, execute o seguinte comando para criar o banco de dados:

CREATE DATABASE quickstart_db

Saída esperada:

Statement executed successfully

Conectar-se a quickstart_db

Clique em "Mudar banco de dados/usuário" no AlloyDB Studio.

6f5c98f5d0b3d550.png

Selecione quickstart_db na lista de bancos de dados, escolha "Autenticação do IAM" e faça a autenticação.

a1940c26c36ff840.jpeg

Isso vai abrir uma nova conexão em que você pode trabalhar com objetos do banco de dados quickstart_db.

Ativar extensões

Verifique se todas as extensões necessárias estão ativadas no banco de dados e têm as versões mínimas corretas.

Verifique se todas as extensões necessárias estão no seu banco de dados:

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

Saída esperada:

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

Se não retornar todas as linhas acima, significa que algumas das extensões não estão ativadas.

Antes de ativar as extensões, precisamos garantir que todas as flags do banco de dados "google_ml_integration.enable_ai_query_engine" e "parameterized_views.enabled" estejam ativadas na instância.

No AlloyDB Studio, enquanto conectado ao quickstart_db, execute:

show google_ml_integration.enable_ai_query_engine;
show parameterized_views.enabled;

Se algum deles estiver mostrando "desativado", repita a etapa "Atualizar flags de banco de dados" e verifique o status usando os comandos "show..." novamente.

Ative as extensões necessárias.

No AlloyDB Studio, enquanto conectado ao quickstart_db, execute:

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

Verifique as versões das extensões parameterized_views e google_ml.

No AlloyDB Studio, enquanto conectado ao quickstart_db, execute:

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

Saída esperada:

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

Se a versão for anterior à necessária, tente atualizar a extensão parameterized_views.

Para parameterized_views, tente:

ALTER EXTENSION parameterized_views UPDATE;

Verifique a versão novamente.

No AlloyDB Studio, enquanto conectado ao quickstart_db, execute:

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

Saída esperada:

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

6. Dados de amostra

Agora você precisa criar objetos no banco de dados e carregar dados. Você vai usar um conjunto de dados fictício de uma imobiliária. Ele tem dados fictícios sobre vendas de imóveis e agentes. Os dados foram fornecidos no script SQL real_estate_data.sql no repositório clonado.

Crie um bucket do Cloud Storage

Use o SDK do Google (gcloud) para importar dados do repositório clonado para o banco de dados do AlloyDB. Para isso, crie um bucket do Cloud Storage e conceda acesso à conta de serviço do AlloyDB. Como alternativa, você pode tentar fazer isso usando o console da Web, conforme descrito na documentação.

No terminal do Google Cloud Shell, execute:

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

Carregar dados

O dump SQL do banco de dados está localizado na pasta do repositório clonado. O comando a seguir pressupõe que você usou seu diretório principal como ponto de partida ao clonar o repositório na etapa anterior durante a criação do cluster do AlloyDB.

Copie o arquivo dump do banco de dados para o bucket do 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

Importe o conjunto de dados para 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

O comando vai carregar o conjunto de dados de amostra no banco de dados quickstart_db.

Verifique a importação contando os registros na tabela "real_estate.agents" usando o AlloyDB Studio:

SELECT COUNT(*) FROM real_estate.agents;

7. Preparar a ontologia de esquema

Configure o conjunto de contexto QueryData, forneça metadados de esquema e execute o agente do ADK.

Criar metadados

Os metadados consistem em comentários SQL aplicados a tabelas e colunas de banco de dados. Esses comentários orientam o mecanismo QueryData ao selecionar objetos de banco de dados.

Se você importou seu esquema de dados usando o dump SQL fornecido, todos os metadados já foram criados no seu esquema.

Confira um exemplo de snippet de metadados aplicado ao 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.';

...

Se você estiver trabalhando com seu próprio conjunto de dados, siga o guia documentado sobre como adicionar os metadados do seu esquema.

Criar contexto de ontologia de esquema

Um conjunto de contexto contém modelos de consulta, atributos e pesquisas de valor que ajudam o modelo de IA a gerar consultas. Sem o contexto definido, um agente de IA vai tentar gerar uma consulta usando apenas as informações disponíveis do esquema public, principalmente com base nos nomes de tabelas, colunas e metadados, tentando deduzir os objetos certos para responder a uma solicitação de consulta natural.

O conjunto de contexto QueryData é um arquivo JSON com modelos de consulta, facetas e pesquisas de valor. Confira diferentes exemplos na documentação.

Carregar o conjunto de contexto QueryData no console da Web

Para carregar um conjunto de contexto, abra o AlloyDB Studio e, no painel esquerdo na parte de baixo, você vai encontrar Context sets e três pontos.

d3578ceee65cd0dd.jpeg

Clique nos três pontos e escolha Create context set.

Na caixa de diálogo aberta, insira os seguintes valores:

  • Nome: sixrecipes
  • Descrição: Real Estate Query Data
  • Fazer upload do arquivo de contexto: deixe-o vazio

Ao pressionar o botão Create, pode levar algum tempo para inicializar o armazenamento de contexto se você fizer isso pela primeira vez.

Verifique o ID do conjunto de contexto QueryData clicando novamente nos três pontos à direita do nome do conjunto de contexto e escolha Edit context set.

6739fb9f54d7f79d.jpeg

O ID do conjunto de contexto aparece na parte de cima da guia aberta à direita.

746b38c52a70bb3c.jpeg

Salve esse ID para as próximas etapas.

Iniciar o agente de IA

Para interagir com o back-end QueryData e gerar e executar consultas, use a interface do AlloyDB Studio, conforme descrito na documentação. Mas, neste laboratório, você vai usar um agente de IA como a principal ferramenta de comunicação com o banco de dados. Os agentes de IA são um cenário mais realista para cargas de trabalho futuras, e a estrutura ADK do Google oferece uma ótima interface da Web em que é possível conferir todas as etapas e interações entre modelos, APIs e aplicativos.

Revise a configuração da origem do agente no Cloud Shell:

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

No arquivo, você pode ver onde definimos a lista de tabelas no nosso esquema. Vamos voltar a isso em uma das próximas etapas.

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

Além disso, há um procedimento call_querydata_api para trabalhar com a API QueryData e um conjunto de ferramentas para usar as habilidades da 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]
)

Este é o agente raiz com um comando e as ferramentas disponíveis.

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

Analise data_agent/SKILL.md para entender as instruções do sistema do agente:

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

Antes de iniciar o agente, prepare um arquivo .env com parâmetros para solicitações de API Agent e QueryData.

Exporte o ID do conjunto de contexto para uma variável de ambiente:

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

Crie o arquivo de configuração do ambiente de execução (.env) no diretório 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

Instale as dependências e inicie o serviço da Web do agente:

cd ~/$REPO_NAME/$SOURCE_DIR/
uv sync

Inicie o agente:

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

Você vai ver uma saída como esta, com o endpoint 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

Clique no URL do servidor da Web gerado (por exemplo, http://127.0.0.1:8000) no Cloud Shell para iniciar a interface do agente. Se não funcionar, no Cloud Shell, clique em "Visualização na Web" -> "Visualizar na porta 8000" para abrir a interface do agente.

Verifique se data_agent está selecionado no menu suspenso no canto superior esquerdo.

ad695db289666da3.jpeg

Gerar e executar consultas

Vamos executar a consulta Q1

Execute este comando de linguagem natural na interface de chat do agente para avaliar a performance básica do QueryData sem modelos ou facetas personalizados:

How many properties are currently for sale in San Diego?

e pressione o botão de seta.

77773a81de6360a2.jpeg

Ele vai iniciar o fluxo de trabalho do agente quando escolher a habilidade adequada e usá-la, além da função, para enviar a solicitação ao endpoint de API e receber de volta a consulta gerada e os resultados.

de9b3e4854b9a506.jpeg

E se você passar o cursor do mouse sobre as etapas, poderá ver exatamente o que elas estavam fazendo. Você também pode conferir isso e muito mais no painel à esquerda ao escolher uma ou outra etapa. Ele pode ajudar a entender como os agentes trabalham com habilidades e APIs.

605dcf936ba38cb8.jpeg

Envie uma consulta complementar (Q2):

List agents who sold a property for over $1000000.

Você vai notar que o agente vai direto para a habilidade da API QueryData, já que ele já gravou na memória as habilidades disponíveis e não precisa passar pela fase de descoberta novamente.

O SQL gerado é semelhante a esta instrução:

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;

Tente outra consulta (Q3):

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

O QueryData gera consultas padrão com um conjunto de contexto vazio, mas as tarefas comerciais geralmente exigem uma lógica de negócios específica e um comportamento determinista.

8. Alcance alta precisão e relevância comercial injetando interpretação

Ao injetar interpretação nos comandos, você garante que as consultas geradas correspondam a uma lógica de negócios específica.

Você vai testar um exemplo em que um cliente pede casas de um único nível em Del Mar. A pergunta tem duas métricas importantes e representa um padrão frequente para uma seleção.

Teste o seguinte comando de linguagem natural (Q4) no agente.

Single level Del Mar houses

O mecanismo retorna uma consulta geral, mas pode incluir algumas condições adicionais, 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;

Recebemos uma linha retornada 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.

Teste outro comando (Q5):

Del Mar houses close to good schools

Ele é inteligente o suficiente para usar as colunas proximity_miles e school_ranking para classificar as propriedades, mas não avalia o peso e a importância 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;

Generalizar o exemplo de consulta em um modelo

A lógica de negócios exige:

  • Proximidade em um raio de 3 km
  • Classificação da escola de 5 ou menos
  • Ordenação ponderada: 80% de classificação da escola + 20% de proximidade (pontuação = 0,8 * classificação_da_escola + 0,2 * milhas_de_proximidade)

Esta é a fórmula do ranking ponderado:

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

Faça o download do arquivo de contexto sixrecipe_context1_1.json do Cloud Shell usando a interface de linha de comando no Cloud Shell:

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

Ou usando o item de menu Download no menu do Cloud Shell:

c0d781806d966dcb.jpeg

Faça upload do sixrecipe_context1_1.json no conjunto de contexto sixrecipe usando o AlloyDB Studio para definir essa estrutura padrão. Clique nos três pontos à direita do contexto sixrecipes e use "Editar conjunto de contexto" para fazer upload do novo arquivo. Isso vai substituir o contexto vazio antigo pelo novo.

O modelo define regras explícitas de modelo SQL para boas regras de proximidade escolar:

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

Execute o comando novamente:

Del Mar houses close to good schools

A consulta resultante agora corresponde à lógica de negócios definida no modelo:

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"

Além da instrução SQL gerada, a seguinte intenção é produzida:

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

Você pode ver a intent no painel esquerdo da interface da web do ADK, como mostrado aqui:

bfb9d6bc718f57cf.jpeg

Os resultados mostram duas propriedades classificadas pela nossa pontuação:

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

No próximo capítulo, você vai ampliar o escopo do modelo.

Injetar interpretação capturando e interpretando várias perguntas

Você pode ampliar o escopo do nosso modelo para que ele seja usado não apenas para "perto de boas escolas", mas também adicionando outras condições ao manifesto para ser usado, por exemplo, para "casas para famílias com crianças".

Baixe o arquivo de contexto atualizado:

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

Faça upload do arquivo sixrecipe_context1_2.json atualizado para seu conjunto de contexto.

No novo arquivo de conjunto de contexto, estamos substituindo o manifesto de "manifest": "A given city houses close to good schools." para o manifesto "manifest": "A given city houses close to good schools, or are great for families with kids.". Ele expande as aplicações desse modelo para ser usado em mais de um caso.

Envie esta consulta (Q6):

Houses in Waterloo great for families with kids.

O agente usa o manifesto de modelo atualizado para aplicar sua lógica de consulta padronizada, retornando a mesma consulta acima, com o objetivo de:

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

9. Use modelos facetados para alcançar alta precisão e manter a flexibilidade

Os refinamentos são condições SQL modulares combinadas dinamicamente com modelos para processar qualificadores (por exemplo, "família iniciante", "vista para o mar") sem criar modelos separados para cada permutação.

Em vez de criar um modelo para cada consulta ou condição, podemos preparar fragmentos que podem ser aplicados a modelos atuais, adicionando mais dimensões à consulta.

Defina uma faceta para propriedades com os seguintes atributos:

  • Uma casa unifamiliar
  • Um status de informações do produto ativo
  • Três a quatro quartos e pelo menos dois banheiros
  • Localização em um município com uma taxa de criminalidade inferior a 500 por 100 mil habitantes

Baixe o arquivo de contexto facetado:

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

Faça upload de sixrecipe_context2_1.json para o conjunto de contexto sixrecipes no AlloyDB Studio para configurar as 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)"
      }
    }
  ]

Execute esta consulta em linguagem natural (Q7):

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

O mecanismo anexa suas condições de faceta diretamente à consulta básica:

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"

Confira os 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

Adicionar mais atributos

E se quisermos adicionar mais uma dimensão com base no novo padrão emergente, como uma visualização de janela para a propriedade?

Adicione a correspondência semântica de vista para o mar em uma definição 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)"
      }

O valor 0,4 na definição de faceta acima é um limite calibrado experimentalmente. Baixe o arquivo de contexto multifacetado:

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

Faça upload de sixrecipe_context2_2.json para seu conjunto de contexto e execute este comando (Q8):

Ocean view Del Mar homes near good schools.

A saída contém condições padrão combinadas com correspondência de similaridade de vetor 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";

E a consulta retorna uma propriedade:

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

Verifique os resultados conferindo a descrição da propriedade no AlloyDB Studio e executando a seguinte consulta:

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

A descrição vai aparecer com "magnificent marine spectacle".

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. Desambiguação dos seus dados particulares usando pesquisas de valor

Solicitações ambíguas podem corresponder a várias entidades, como confundir o nome de um local com o de uma empresa. As pesquisas de valor mapeiam termos para conceitos de banco de dados e ajudam a resolver essas referências.

A pesquisa de valores fornece associações entre o nome da coluna mais os valores na coluna e um tipo de conceito, que pode ser, por exemplo, uma cidade, um país ou um nome. Como ele indexou não apenas metadados, mas também amostras representativas dos dados em uma coluna, é possível associar um valor usado na solicitação em linguagem natural a um tipo de conceito potencial e a qual tabela e coluna podem ser usadas no SQL resultante.

Ambiguidade em uma consulta

Na vida real, isso pode ser ainda mais difícil devido a erros de ortografia de diferentes nomes e locais. Execute este comando (Q9) com um erro de digitação e um termo ambíguo:

Westwod's sold properties in the last 1 month.

Há dois problemas aqui: o nome está escrito incorretamente e não está claro se estamos falando de propriedades vendidas no local com nomes semelhantes ou de uma imobiliária. Quando executamos a consulta, ela retorna uma consulta como esta:

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

Ele corrigiu o nome e assumiu que a pergunta era sobre a localização das propriedades, tentando corresponder a propriedades na cidade de Westwood.

Ative a extensão pg_trgm no AlloyDB Studio para oferecer suporte a comparações de strings aproximadas:

CREATE EXTENSION IF NOT EXISTS pg_trgm;

Depois disso, podemos usar a extensão para adicionar pesquisas de valor ao nosso conjunto de contexto QueryData.

Confira a lista de pesquisas de valor 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"
    }
  ]

Baixe o arquivo de contexto de pesquisa de valor:

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

Faça upload de sixrecipe_context3_1.json para o conjunto de contextos sixrecipes do QueryData e salve.

Execute o comando novamente (Q9):

Westwod's sold properties in the last 1 month.

Ela retornou uma resposta de esclarecimento porque, na pesquisa de valores, encontrou três entidades diferentes com valores semelhantes:

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?

O rastreamento nos dá uma pista de por que ele escolheu esse caminho específico.

88c52932dcbef8f9.jpeg

Confira o 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?"

Remover a ambiguidade da consulta

Refine o comando de entrada (Q10):

Westwood city location properties sold in September 2025.

Agora temos a consulta e os 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. Pesquisa semântica e funções de IA

As funções de IA do AlloyDB permitem a filtragem semântica em dados não estruturados. Use funções como ai.if e google_ml.embedding diretamente em consultas SQL com operadores de banco de dados padrão.

Modelo com operadores ILIKE

Como primeira etapa, faça upload do novo arquivo de contexto com operadores ILIKE.

Faça o download do arquivo de contexto do modelo ILIKE:

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

Faça upload de sixrecipe_context4_1.json no seu conjunto de contexto para avaliar a correspondência de padrões ILIKE padrão para casas "prontas para morar".

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

Envie esta consulta (Q11):

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

Ele retorna um registro correspondente.

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

Modelo com a função AI.IF

Baixe o arquivo de contexto do modelo AI.IF:

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

Faça upload de sixrecipe_context4_2.json para seu conjunto de contexto e avalie a função de filtragem semântica 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\")"
      }
    }

Repita o comando (Q12):

List move-in ready houses.

SQL gerado:

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

Isso nos dá a mesma propriedade no 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

Baixe o arquivo de contexto da pesquisa de incorporação:

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

Faça upload de sixrecipe_context4_3.json para seu conjunto de contexto e avalie a pesquisa vetorial semântica usando google_ml.embedding e pesquise casas com eletrodomésticos de cozinha novos.

Atualize o conjunto de contexto usando o arquivo sixrecipe_context4_3.json com mais uma faceta usando a pesquisa vetorial do 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)"
      }
    }

O valor 0,4 na definição de faceta acima é um limite calibrado experimentalmente.

Execute esta consulta (Q13):

List move-in ready houses with upgraded kitchen appliances.

SQL gerado:

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

Aqui estão os resultados da consulta e a recomendação em linguagem 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. Segurança e privacidade

Sempre que a API QueryData gera consultas, há um risco de privacidade de que as consultas geradas exponham dados de outros usuários. As visualizações seguras parametrizadas (PSVs, na sigla em inglês) aplicam limites de segurança no nível da linha durante a geração de NL2SQL.

Criar visualização segura parametrizada

Temos uma tabela real_estate.offers com informações sobre ofertas de todos os nossos negócios imobiliários. Queremos que nossos agentes vejam apenas os próprios negócios, e não os de outros agentes. Criamos uma PSV com a capacidade de filtrar linhas com base no agent_id.

Crie uma visualização segura parametrizada para a tabela de ofertas no 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;

Verifique a definição da visualização de segurança parametrizada:

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

E a tabela "real_estate.offers"?

Limitar o acesso aos dados do QueryData usando visualizações seguras parametrizadas do AlloyDB

No agent.py, a visualização de segurança parametrizada real_estate.secure_offers é registrada em TABLE_IDS em vez de real_estate.offers:

O código-fonte 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",
]

E transmite o contexto de tempo de execução da sessão para um agente com agent_id=1:

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

Como resultado, todas as solicitações de API QueryData vão transmitir esse parâmetro. Quando a visualização segura for incluída na solicitação, ela vai aplicar a filtragem, retornando apenas resultados para esse agente específico.

Execute esta consulta (Q14):

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

SQL gerado:

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

A consulta da visualização filtra os resultados pelo parâmetro de sessão do agente ativo, protegendo dados de registros não autorizados e retornando apenas dois 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

Se você executar a seguinte consulta na tabela base real_estate.offers no 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

Ele retorna mais de dois registros, mostrando dados de todos os agentes.

Chegamos ao fim do laboratório. Você aprendeu a usar a API QueryData, as funções de IA e as visualizações seguras parametrizadas do AlloyDB.

13. Limpar o ambiente

Destrua as instâncias e o cluster do AlloyDB quando terminar o laboratório.

Excluir o cluster do AlloyDB e todas as instâncias

O cluster é destruído com a opção force, que também exclui todas as instâncias pertencentes a ele.

Se o terminal foi desconectado, conecte-se novamente e defina as variáveis de projeto e ambiente no 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)

Exclua o cluster:

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

Saída esperada do 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.   

Excluir backups do AlloyDB

Exclua todos os backups do AlloyDB do 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

Saída esperada do 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.                                                                                                                                                                                                                                                            

Excluir o bucket do Cloud Storage

Exclua o bucket de importação do Cloud Storage para evitar cobranças contínuas de armazenamento:

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

14. Parabéns

Parabéns por concluir o codelab!

O que aprendemos

  • Como implantar um cluster do AlloyDB e uma instância principal
  • Como ativar QueryData, funções de IA e visualizações seguras parametrizadas para o AlloyDB
  • Como criar um conjunto de contextos do QueryData
  • Como usar diferentes modelos e contextos na API QueryData
  • Como tornar os modelos QueryData mais flexíveis usando atributos
  • Como resolver a ambiguidade de dados com a pesquisa de valores e a indexação de trigramas
  • Como usar funções de IA para pesquisa semântica com modelos
  • Como usar visualizações seguras parametrizadas com a API QueryData

Pesquisa

Saída:

Como você usará este tutorial?

Apenas leitura Leitura e exercícios