AlloyDB 中实现接近 100% 准确率的文本到 SQL 的六种方法 - Codelab

1. 简介

在此 Codelab 中,您将学习如何使用 QueryData API 获得最佳结果。您将了解如何通过自然语言请求数据库来生成准确且可重复的结果。为帮助您实现这一目标,本指南将在多个章节中介绍六个配方,探索自然语言到 SQL (NL2SQL) 的不同方面及其实际应用。

前提条件

  • 对 Google Cloud 和 Google Cloud 控制台有基本的了解
  • 具备命令行界面和 Cloud Shell 方面的基本技能

学习内容

  • 如何部署 AlloyDB 集群和主实例
  • 如何启用 QueryData、AI 函数和参数化安全视图
  • 如何创建 QueryData 上下文集
  • 如何在 QueryData API 中使用不同的模板和上下文
  • 如何使用分面让 QueryData 模板更灵活
  • 如何通过值搜索和三元索引解决数据歧义
  • 如何使用 AI 函数通过模板进行语义搜索
  • 如何将参数化安全视图与 QueryData API 搭配使用

所需条件

  • Google Cloud 账号和 Google Cloud 项目
  • 支持 Google Cloud 控制台和 Cloud Shell 的网络浏览器,例如 Chrome

2. 设置和要求

项目设置

  1. 登录 Google Cloud 控制台。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个。

请改用个人账号,而非工作账号或学校账号。

  1. 创建新项目或重复使用现有项目。如需在 Google Cloud 控制台中创建新项目,请在标题中点击“选择项目”按钮,系统随即会打开一个弹出式窗口。

295004821bab6a87.png

在“选择项目”窗口中,点击“新建项目”,系统随即会打开一个用于创建新项目的对话框。

37d264871000675d.png

在对话框中,输入项目名称并选择位置。

96d86d3d5655cdbe.png

  • 项目名称是此项目参与者的显示名称。Google API 不会使用项目名称,并且您可以随时更改项目名称。
  • 项目 ID 在所有 Google Cloud 项目中是唯一的,并且是不可变的(一经设置便无法更改)。Google Cloud 控制台会自动生成一个唯一 ID,但您可以自定义该 ID。如果您不喜欢生成的 ID,可以生成另一个随机 ID,也可以提供自己的 ID 来检查其可用性。在大多数 Codelab 中,您都需要引用项目 ID,该 ID 通常用占位符 PROJECT_ID 标识。
  • 此外,还有第三个值,即部分 API 使用的项目编号,供您参考。如需详细了解所有这三个值,请参阅文档。

启用结算功能

设置个人结算账号

如果您使用 Google Cloud 积分设置了结算,则可以跳过此步骤。

如需设置个人结算账号,请点击此处在 Cloud 控制台中启用结算功能。

注意事项:

  • 完成本实验的 Cloud 资源费用应低于 5 美元。
  • 您可以按照本实验末尾的步骤删除资源,以避免产生更多费用。
  • 新用户符合参与 $300 USD 免费试用计划的条件。

启动 Cloud Shell

虽然可以通过笔记本电脑对 Google Cloud 进行远程操作,但在此 Codelab 中,您将使用 Google Cloud Shell,这是一个在云端运行的命令行环境。

在 Google Cloud 控制台 中,点击右上角工具栏中的 Cloud Shell 图标:

激活 Cloud Shell

或者,您也可以按 G 键,然后按 S 键。如果您位于 Google Cloud 控制台中,或者使用此链接,此序列将激活 Cloud Shell。

预配和连接到环境应该只需要片刻时间。完成后,您应该会看到如下内容:

Google Cloud Shell 终端的屏幕截图,显示环境已连接

这个虚拟机已加载了您需要的所有开发工具。它提供了一个持久的 5 GB 主目录,并且在 Google Cloud 中运行,大大增强了网络性能和身份验证功能。您在此 Codelab 中的所有工作都可以在浏览器中完成。您无需安装任何程序。

3. 准备工作

启用 API

如需使用 AlloyDB、Compute Engine、网络服务和代理平台,请在 Google Cloud 项目中启用各自的 API。

在 Cloud Shell 终端中,确保项目 ID 已设置:

gcloud config get-value project

该命令应返回您的 Google Cloud 项目 ID。

设置 PROJECT_ID 环境变量:

PROJECT_ID=$(gcloud config get-value project)

启用必需的 API:

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

预期输出:

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.

您可以在文档中了解有关每个已启用 API 的信息。

4. 部署 AlloyDB

创建 AlloyDB 集群和主实例。您可以使用准备好的脚本部署它,该脚本将部署所有必需的资源;也可以按照 AlloyDB 文档自行逐步完成部署。

使用自动化脚本部署 AlloyDB

此方法使用自动化脚本来部署 AlloyDB 集群,并提供必要的配置详细信息,以便开始使用已部署的资源。

在 Cloud Shell 终端中,运行命令以从代码库克隆部署脚本:

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

运行部署脚本:

./deploy_alloydb.sh

该脚本需要 5 到 7 分钟才能完成。输出会显示集群部署详细信息。请注意,您的密码会有所不同 - 请记录该密码以供将来使用。

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

在 Google Cloud 控制台中的 AlloyDB 集群页面上查看新集群和实例。

4271eb55bcc9ec84.png

5. 准备数据库

创建数据库,启用 Gemini Enterprise Agent Platform 集成,构建数据库对象,并添加示例数据。

向 AlloyDB 授予必要权限

向 AlloyDB 服务代理授予所需的 IAM 角色:

点击“添加 (+)”以打开新的 Cloud Shell 标签页。

abc505ac4d41f24e.png

在新标签页中,运行以下命令:

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"

预期的控制台输出:

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
 

启用数据访问 API

在 AlloyDB 集群上启用 Data Access API。

在同一终端标签页中,运行以下命令:

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

更新数据库标志

在实例级启用标志之前,请等待数据访问 API 步骤完成。确保您的实例状态显示为绿色对勾标记,如下图所示。

6284731b8eff74c.jpeg

如需将 AI 扩展程序和 IAM 身份验证用于代理工具,您必须在实例上启用特定的数据库标志,并将自己添加为数据库用户。启用以下标志:

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

在同一终端标签页中,运行以下命令:

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

为您的账号创建 AlloyDB IAM 用户:

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

在终端中运行 exit 命令,关闭该标签页:

exit

连接到 AlloyDB Studio

在后续章节中,您可以在 AlloyDB Studio 中运行所有需要数据库连接的 SQL 命令。

如需打开 AlloyDB Studio,请执行以下步骤:

  1. 在 Google Cloud 控制台中,前往 AlloyDB for PostgreSQL 的集群页面。
  2. 选择主实例以打开其 Web 界面。

1d7298e7096e7313.png

  1. 然后,点击左侧的 AlloyDB Studio:

a33131c72ad29478.png

  1. 选择 postgres 数据库,选择“IAM 身份验证”,然后点击“身份验证”。

8ddfb250344ca749.jpeg

此操作会打开 AlloyDB Studio 界面。

  1. 选择“未命名的查询”标签页以打开 SQL 编辑器。

6696bc771fab9983.png

系统随即会打开一个编辑器,您可以在其中运行 SQL 命令,如下图所示:

ae34288e5bf237c7.png

创建数据库

创建快速入门数据库

在 AlloyDB Studio 编辑器中,运行以下命令以创建数据库:

CREATE DATABASE quickstart_db

预期输出:

Statement executed successfully

连接到 quickstart_db

在 AlloyDB Studio 中,点击“更改数据库/用户”。

6f5c98f5d0b3d550.png

从数据库列表中选择 quickstart_db,选择“IAM 身份验证”,然后进行身份验证。

a1940c26c36ff840.jpeg

系统将打开一个新连接,您可以在其中处理 quickstart_db 数据库中的对象。

启用扩展程序

检查所有必需的扩展程序,验证它们是否已在数据库中启用,并且是否具有正确的最低版本。

验证数据库中是否存在所有必需的扩展程序:

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

预期输出:

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

如果未返回上述所有行,则表示某些扩展服务未启用。

在启用扩展程序之前,我们需要确保实例上已启用所有数据库标志“google_ml_integration.enable_ai_query_engine”和“parameterized_views.enabled”。

在 AlloyDB Studio 中,连接到 quickstart_db 后执行以下操作:

show google_ml_integration.enable_ai_query_engine;
show parameterized_views.enabled;

如果其中任何一个显示“关闭”,请尝试重复“更新数据库标志”这一步骤,然后再次使用“show…”命令检查状态。

启用所需扩展程序。

在 AlloyDB Studio 中,连接到 quickstart_db 后执行以下操作:

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

检查 parameterized_views 和 google_ml 的扩展版本。

在 AlloyDB Studio 中,连接到 quickstart_db 后执行以下操作:

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

预期输出:

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

如果版本低于所需版本,请尝试更新 parameterized_views 扩展程序。

对于 parameterized_views,请尝试:

ALTER EXTENSION parameterized_views UPDATE;

再次验证版本。

在 AlloyDB Studio 中,连接到 quickstart_db 后执行以下操作:

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

预期输出:

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

6. 示例数据

现在,您需要在数据库中创建对象并加载数据。您将使用一个虚构的房地产公司数据集。其中包含有关房产销售和代理的虚构数据。数据已在克隆的代码库中的 SQL 脚本 real_estate_data.sql 中提供。

创建存储桶

使用 Google SDK (gcloud) 将数据从克隆的代码库导入到 AlloyDB 数据库。您需要为此创建一个 Cloud Storage 存储分区,并向 AlloyDB 服务账号授予访问权限。或者,您也可以始终尝试使用 Web 控制台来完成此操作,如文档中所述。

在 Google Cloud Shell 终端中,执行以下命令:

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

加载数据

数据库 SQL 转储位于克隆的代码库文件夹中。以下命令假定您在创建 AlloyDB 集群时,已使用主目录作为上一步中克隆代码库的起点。

将数据库转储文件复制到您的 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

将数据集导入到 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

该命令会将示例数据集加载到 quickstart_db 数据库中。

使用 AlloyDB Studio 统计 real_estate.agents 表中的记录数,以验证导入是否成功:

SELECT COUNT(*) FROM real_estate.agents;

7. 准备架构本体

设置 QueryData 上下文集,提供架构元数据,并运行 ADK 代理。

创建元数据

元数据由应用于数据库表和列的 SQL 注释组成。这些注释可在选择数据库对象时为 QueryData 引擎提供指导。

如果您已使用提供的 SQL 转储导入数据架构,则所有元数据都已在您的架构中创建。

以下是应用于 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.';

...

如果您使用的是自己的数据集,可以按照相关文档中的指南了解如何为架构添加元数据。

创建架构本体上下文

上下文集包含查询模板、多面搜索和值搜索,可帮助 AI 模型生成查询。如果没有设置上下文,AI 智能体将尝试仅使用 public 架构中的可用信息(主要基于表名称、列和元数据)来生成查询,尝试推断出正确的对象来回答自然语言查询请求。

QueryData 上下文集是一个 JSON 文件,其中包含查询模板、多面搜索和值搜索。您可以查看文档中的不同示例。

在 Web 控制台中加载 QueryData 上下文集

如需加载上下文集,请打开 AlloyDB Studio,然后在左侧面板底部找到 Context sets 和三个点。

d3578ceee65cd0dd.jpeg

点击这三个点,然后选择 Create context set。

在打开的对话框中,输入以下值:

  • 名称:sixrecipes
  • 说明:Real Estate Query Data
  • 上传上下文的描述文件:保持为空

如果您是首次按下 Create 按钮,系统可能需要一些时间来初始化上下文存储空间。

再次点击上下文集名称右侧的三个点,然后选择 Edit context set,以检查 QueryData 上下文集 ID。

6739fb9f54d7f79d.jpeg

上下文集 ID 会显示在右侧打开的标签页顶部

746b38c52a70bb3c.jpeg

保存此 ID 以供后续步骤使用。

启动 AI 智能体

如需与 QueryData 后端互动以生成和执行查询,您可以使用文档中所述的 AlloyDB Studio 界面。不过,在本实验中,您将使用 AI 智能体作为与数据库通信的主要工具。AI 智能体是未来工作负载的更真实场景,而 Google ADK 框架提供了一个出色的 Web 界面,您可以在其中查看模型、API 和应用之间的所有步骤和互动。

在 Cloud Shell 中查看代理源配置:

REPO_NAME="codelabs"
SOURCE_DIR="alloydb-ai-nla"
cd ~/$REPO_NAME/$SOURCE_DIR/data_agent
edit 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",
]

此外,还有一种使用 QueryData API 的程序 call_querydata_api,以及一套用于处理 QueryData API 技能的工具。

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

以下是根代理本身,其中包含提示和可用工具。

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

查看 data_agent/SKILL.md 以了解智能体的系统指令:

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

在启动代理之前,您需要准备一个 .env 文件,其中包含 Agent 和 QueryData API 请求的参数。

将上下文集 ID 导出为环境变量:

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

在 data_agent 目录中创建运行时环境配置文件 (.env):

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

安装依赖项并启动代理 Web 服务:

cd ~/$REPO_NAME/$SOURCE_DIR/
uv sync

启动代理:

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

您应该会看到如下所示的输出,其中包含端点 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

点击 Cloud Shell 中生成的 Web 服务器网址(例如 http://127.0.0.1:8000),以启动代理界面。如果此方法不起作用,请在 Cloud Shell 中依次点击“网页预览” ->“在端口 8000 上预览”,以打开代理界面。

验证左上角的下拉菜单中是否选择了 data_agent。

ad695db289666da3.jpeg

生成并运行查询

我们来运行查询 Q1

在代理聊天界面中运行以下自然语言提示,以评估不使用自定义模板或分面的基本 QueryData 性能:

How many properties are currently for sale in San Diego?

然后按箭头按钮。

77773a81de6360a2.jpeg

当智能体选择合适的技能时,它会启动工作流,并使用该技能和函数向 API 端点发送请求,然后获取生成的查询和结果。

de9b3e4854b9a506.jpeg

如果您将鼠标光标悬停在相应步骤上,就可以确切地了解这些步骤在做什么。此外,当您选择某个步骤时,还可以在左侧面板中看到这些信息以及更多内容。这有助于了解智能体如何与技能和 API 协同工作。

605dcf936ba38cb8.jpeg

提交后续查询 (Q2):

List agents who sold a property for over $1000000.

您会注意到,智能体直接进入 QueryData API 技能,因为它已将可用技能写入内存,无需再次经历发现阶段。

生成的 SQL 类似于以下语句:

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;

尝试其他查询 (Q3):

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

QueryData 会生成具有空上下文集的标准查询,但业务任务通常需要特定的业务逻辑和确定性行为。

8. 通过注入解读方式,实现高准确率和业务相关性

在提示中注入解释可确保生成的查询与特定业务逻辑相匹配。

您将尝试这样一个示例:客户要求查找德尔马地区的单层房源。该问题包含两个重要指标,代表了一种常见的选择模式。

在代理中尝试以下自然语言提示(Q4)。

Single level Del Mar houses

引擎会返回一个常规查询,但可能包含一些额外的条件,例如 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;

我们得到一个返回的行作为结果:

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.

测试其他提示(问题 5):

Del Mar houses close to good schools

它足够智能,可以使用 proximity_miles 和 school_ranking 列对房源进行排序,但不会评估每个指标的权重和重要性。

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;

将示例查询泛化为模板

业务逻辑要求:

  • 2 英里内的邻近度
  • 学校排名为 5 或更低
  • 加权排序:80% 学校排名 + 20% 邻近度(得分 = 0.8 * school_ranking + 0.2 * proximity_miles)

以下是加权排名公式:

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

使用 Cloud Shell 中的命令行界面从 Cloud Shell 下载 sixrecipe_context1_1.json 上下文的描述文件:

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

或者,通过 Cloud Shell 菜单中的 Download 菜单项:

c0d781806d966dcb.jpeg

使用 AlloyDB Studio 将 sixrecipe_context1_1.json 上传到 sixrecipe 上下文集中,以定义此标准结构。点击 sixrecipes 上下文右侧的三点状图标,然后使用“修改上下文集”上传新文件。它会有效地将旧的空上下文替换为新的上下文。

该模板为良好的学校邻近度规则定义了明确的 SQL 模板规则:

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

重新运行提示:

Del Mar houses close to good schools

生成的查询现在与模板中定义的业务逻辑相匹配:

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"

除了生成的 SQL 语句之外,还会生成以下 intent:

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

您可以在 ADK 网页界面的左侧窗格中看到相应 intent,如图所示:

bfb9d6bc718f57cf.jpeg

结果显示了按得分排序的两个房源:

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

在下一章中,您将扩大模板的范围。

通过捕获和解读多个问题来注入解读方式

您可以扩大模板的使用范围,使其不仅适用于“靠近好学校的房源”,还可以向清单中添加其他条件,例如“适合有孩子的家庭的房源”。

下载更新后的上下文的描述文件:

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

将更新后的 sixrecipe_context1_2.json 文件上传到您的上下文集。

在新上下文设置文件中,我们将清单从 "manifest": "A given city houses close to good schools." 替换为清单 "manifest": "A given city houses close to good schools, or are great for families with kids."。它扩展了此模板的应用范围,使其不仅可用于一种情况。

提交以下查询 (Q6):

Houses in Waterloo great for families with kids.

代理使用更新后的模板清单来应用标准化查询逻辑,返回与上述查询相同的查询,目的是:

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

9. 使用分面模板,在保持灵活性的同时实现高准确率

分面是与模板动态组合的模块化 SQL 条件,用于处理限定符(例如“入门家庭”“海景”),而无需为每种排列组合创建单独的模板。

我们可以准备一些片段,将其应用于现有模板,从而为查询添加更多维度,而不必为每个查询或条件都创建一个模板。

为具有以下属性的媒体资源定义一个方面:

  • 独栋住宅
  • 有效商品详情状态
  • 三到四间卧室和至少两间浴室
  • 位于犯罪率低于每 10 万居民 500 起的城市

下载分面上下文的描述文件:

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

将 sixrecipe_context2_1.json 上传到 AlloyDB Studio 中的 sixrecipes 上下文集,以配置方面。

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

运行此 NL 查询 (Q7):

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

引擎会将您的分面条件直接附加到基本查询中:

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"

结果如下:

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

添加更多商品详情

如果我们想根据新出现的模式(例如房源的窗口视图)再添加一个维度,该怎么办?

在分面定义中添加了海景语义匹配。

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

上面分面定义中的值 0.4 是经过实验校准的阈值。下载多方面的上下文的描述文件:

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

将 sixrecipe_context2_2.json 上传到上下文集,然后运行以下提示 (Q8):

Ocean view Del Mar homes near good schools.

输出包含标准条件,并通过 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";

查询会返回一个属性:

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

通过在 AlloyDB Studio 中查看相应属性的说明并运行以下查询来验证结果:

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

其中将显示“壮丽的海洋奇观”说明。

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. 使用值搜索消除专用数据的歧义

模糊不清的请求可能会匹配多个实体,例如将营业地点名称与商家名称混淆。值搜索会将术语映射到数据库概念,以帮助解析这些引用。

值搜索功能可将列名称以及列中的值与概念类型(例如城市、国家/地区或名称)相关联。由于它不仅对元数据进行了索引,还对列中的代表性数据样本进行了索引,因此可以将自然语言请求中使用的值与潜在的概念类型以及结果 SQL 中可以使用的表和列相关联。

查询中的歧义

在现实生活中,由于不同名称和位置的拼写错误,这种情况可能会更加复杂。运行包含拼写错误和模糊不清的字词的此提示 (Q9):

Westwod's sold properties in the last 1 month.

这里存在两个问题:名称拼写错误,并且不清楚我们是在谈论名称相似的房源,还是在谈论房地产中介机构。运行查询时,系统会返回如下查询。

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

它已更正名称,并假设问题是关于房产位置的,试图匹配 Westwood 城市中的房产。

在 AlloyDB Studio 中启用 pg_trgm 扩展程序,以支持模糊字符串比较:

CREATE EXTENSION IF NOT EXISTS pg_trgm;

之后,我们可以使用扩展程序将值搜索添加到 QueryData 上下文集中。

以下是我们想要使用的值搜索列表:

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

下载价值搜索上下文的描述文件:

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

将 sixrecipe_context3_1.json 上传到 QueryData sixrecipes 上下文集并保存。

重新运行提示 (Q9):

Westwod's sold properties in the last 1 month.

它返回了澄清响应,因为在值搜索中,它找到了 3 个具有相似值的不同实体:

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?

轨迹可为我们提供有关其为何选择特定路径的线索

88c52932dcbef8f9.jpeg

以下是完整文本:

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

消除查询中的歧义

优化输入提示(问题 10):

Westwood city location properties sold in September 2025.

现在,我们有了查询和结果:

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. 语义搜索和 AI 函数

AlloyDB AI 函数支持对非结构化数据进行语义过滤。在 SQL 查询中直接使用 ai.if 和 google_ml.embedding 等函数以及标准数据库运算符。

包含 ILIKE 运算符的模板

第一步,上传包含 ILIKE 运算符的新上下文的描述文件。

下载 ILIKE 模板上下文的描述文件:

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

将 sixrecipe_context4_1.json 上传到您的上下文集中,以评估“可入住”房屋的标准 ILIKE 模式匹配。

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

提交以下查询 (Q11):

List move-in ready houses.

生成的查询:

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

它会返回一条匹配的记录。

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

包含 AI.IF 函数的模板

下载 AI.IF 模板上下文的描述文件:

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

将 sixrecipe_context4_2.json 上传到您的上下文集,以评估 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\")"
      }
    }

重复提示 (Q12):

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

这样一来,结果中就会包含相同的属性:

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

下载嵌入搜索上下文的描述文件:

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

将 sixrecipe_context4_3.json 上传到您的上下文集中,以使用 google_ml.embedding 评估语义向量搜索,并搜索配备新厨房电器设备的住宅。

使用 sixrecipe_context4_3.json 文件更新上下文集,并使用 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)"
      }
    }

上面分面定义中的值 0.4 是经过实验校准的阈值。

运行此查询 (Q13):

List move-in ready houses with upgraded kitchen appliances.

生成的 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") AND ((("p"."description_embedding" <=> google_ml.embedding('gemini-embedding-001', 'Upgraded kitchen appliances')::vector) < 0.4))

以下是查询结果和自然语言建议:

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. 安全与隐私

每当 QueryData API 生成查询时,都存在一种隐私风险:生成的查询可能会泄露属于其他用户的数据。参数化安全视图 (PSV) 在 NL2SQL 生成期间强制执行行级安全边界。

创建参数化安全视图

我们有一个表 real_estate.offers,其中包含有关所有房地产交易的优惠信息。我们希望代理只能看到自己的交易,而不能看到其他代理的交易。我们创建了一个 PSV,该 PSV 能够根据 agent_id 过滤行。

在 AlloyDB Studio 中为 offers 表创建参数化安全视图:

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;

验证参数化安全视图定义:

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

那么 real_estate.offers 表呢?

使用 AlloyDB 参数化安全视图限制对 QueryData 数据的访问权限

在 agent.py 中,参数化安全视图 real_estate.secure_offers 已在 TABLE_IDS 中注册,而不是 real_estate.offers:

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

并传递了 agent_id=1 的代理的会话运行时上下文:

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

因此,每个 QueryData API 请求都会传递该参数,并且当安全视图包含在请求中时,它会应用过滤条件,仅返回该特定代理的结果。

运行此查询 (Q14):

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

生成的 SQL:

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

查询视图会按有效代理会话参数过滤结果,从而保护未经授权的记录数据,并仅返回两条记录。

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

如果您在 AlloyDB Studio 中针对基本 real_estate.offers 表运行以下查询:

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

它会返回两条以上的记录,显示所有客服人员的数据。

本实验到此结束!您已了解如何使用 QueryData API、AI 函数和 AlloyDB 参数化安全视图。

13. 清理环境

完成实验后销毁 AlloyDB 实例和集群。

删除 AlloyDB 集群和所有实例

系统会通过 force 选项销毁集群,该选项还会删除属于该集群的所有实例。

如果终端断开连接,请重新连接并在 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)

删除集群:

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

预期的控制台输出:

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.   

删除 AlloyDB 备份

删除集群的所有 AlloyDB 备份:

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

预期的控制台输出:

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.                                                                                                                                                                                                                                                            

删除 Cloud Storage 存储分区

删除 Cloud Storage 导入存储桶,以避免持续产生存储费用:

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

14. 恭喜

恭喜您完成此 Codelab!

所学内容

  • 如何部署 AlloyDB 集群和主实例
  • 如何为 AlloyDB 启用 QueryData、AI 函数和参数化安全视图
  • 如何创建 QueryData 上下文集
  • 如何在 QueryData API 中使用不同的模板和上下文
  • 如何使用分面让 QueryData 模板更灵活
  • 如何通过值搜索和三元索引解决数据歧义
  • 如何使用 AI 函数通过模板进行语义搜索
  • 如何将参数化安全视图与 QueryData API 搭配使用

调查问卷

输出:

您打算如何使用本教程?

仅通读 阅读并完成练习