Шесть рецептов для достижения почти 100% точности преобразования текста в SQL в AlloyDB - codelab

1. Введение

В этом практическом занятии вы узнаете, как добиться наилучших результатов, используя API QueryData. Вы откроете для себя, как генерировать точные и воспроизводимые результаты при выполнении запросов к базе данных на естественном языке. Чтобы помочь вам в этом, руководство проведет вас через шесть примеров в нескольких главах, исследуя различные аспекты преобразования естественного языка в SQL (NL2SQL) и его практическое применение.

Предварительные требования

  • Базовое понимание Google Cloud и консоли Google Cloud.
  • Базовые навыки работы с интерфейсом командной строки и Cloud Shell.

Что вы узнаете

  • Как развернуть кластер AlloyDB и основной экземпляр
  • Как включить QueryData, функции искусственного интеллекта и параметризованные защищенные представления.
  • Как создать набор контекстов QueryData
  • Как использовать различные шаблоны и контексты в API QueryData
  • Как сделать шаблоны QueryData более гибкими с помощью фасетов.
  • Как разрешить неоднозначность данных с помощью поиска по значению и триграммного индексирования
  • Как использовать функции ИИ для семантического поиска с помощью шаблонов
  • Как использовать параметризованные защищенные представления с API QueryData

Что вам понадобится

  • Аккаунт Google Cloud и проект Google Cloud
  • Веб-браузер, такой как Chrome, поддерживающий консоль Google Cloud и Cloud Shell.

2. Настройка и требования

Настройка проекта

  1. Войдите в консоль Google Cloud . Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .

Используйте личный аккаунт вместо рабочего или учебного.

  1. Создайте новый проект или используйте существующий. Чтобы создать новый проект в консоли Google Cloud, в заголовке нажмите кнопку «Выбрать проект», после чего откроется всплывающее окно.

295004821bab6a87.png

В окне «Выберите проект» нажмите кнопку «Новый проект», после чего откроется диалоговое окно для создания нового проекта.

37d264871000675d.png

В диалоговом окне введите название проекта и выберите местоположение.

96d86d3d5655cdbe.png

  • Название проекта — это отображаемое имя участников данного проекта. Название проекта не используется API Google и может быть изменено в любое время.
  • The Project ID is unique across all Google Cloud projects and is immutable (it can't be changed after it has been set). The Google Cloud console automatically generates a unique ID, but you can customize it. If you don't like the generated ID, you can generate another random one or provide your own to check its availability. In most codelabs, you'll need to reference your project ID, which is typically identified with the placeholder PROJECT_ID.
  • К вашему сведению, существует третье значение — номер проекта , которое используется некоторыми API. Подробнее обо всех трех значениях можно узнать в документации .

Включить выставление счетов

Создайте личный платежный аккаунт.

Если вы настроили оплату с использованием кредитов Google Cloud, этот шаг можно пропустить.

Чтобы настроить личный платежный аккаунт, перейдите сюда, чтобы включить оплату в облачной консоли.

Несколько замечаний:

  • Выполнение этой лабораторной работы должно обойтись менее чем в 5 долларов США в виде облачных ресурсов.
  • В конце этой лабораторной работы вы можете выполнить действия по удалению ресурсов, чтобы избежать дальнейших списаний средств.
  • Новые пользователи могут воспользоваться бесплатной пробной версией стоимостью 300 долларов США .

Запустить Cloud Shell

Хотя Google Cloud можно управлять удаленно с ноутбука, в этом практическом занятии вы будете использовать Google Cloud Shell — среду командной строки, работающую в облаке.

В консоли Google Cloud нажмите на значок Cloud Shell на панели инструментов в правом верхнем углу:

Активировать Cloud Shell

В качестве альтернативы вы можете нажать G, а затем S. Эта последовательность активирует Cloud Shell, если вы находитесь в консоли Google Cloud, или воспользуйтесь этой ссылкой .

Подготовка и подключение к среде займут всего несколько минут. После завершения вы должны увидеть что-то подобное:

Скриншот терминала Google Cloud Shell, показывающий, что среда подключена.

Эта виртуальная машина содержит все необходимые инструменты разработки. Она предоставляет постоянный домашний каталог объемом 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Вся работа в этом практическом задании может выполняться в браузере. Вам не нужно ничего устанавливать.

3. Прежде чем начать

Включить API

Для использования AlloyDB, Compute Engine, сетевых сервисов и Agent Platform необходимо включить соответствующие API в вашем проекте Google Cloud.

В терминале Cloud Shell убедитесь, что идентификатор вашего проекта указан правильно:

gcloud config get-value project

Эта команда должна вернуть идентификатор вашего проекта Google Cloud.

Установите переменную среды 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

Выполнение скрипта занимает от пяти до семи минут. В результате отображаются сведения о развертывании вашего кластера. Обратите внимание, что ваш пароль будет другим — запишите его для дальнейшего использования .

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

Просмотрите свой новый кластер и экземпляр на странице «Кластеры AlloyDB» в консоли Google Cloud.

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 доступа к данным

Включите API доступа к данным в вашем кластере AlloyDB.

В той же вкладке терминала выполните следующую команду:

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

Для использования расширений ИИ и аутентификации IAM для инструментов агентской сети необходимо включить определенные флаги базы данных на экземпляре и добавить себя в качестве пользователя базы данных. Включите следующие флаги:

  • alloydb.iam_authentication
  • password.enforce_complexity
  • google_ml_integration.enable_ai_query_engine
  • параметризованные_представления

В той же вкладке терминала выполните следующие команды:

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

В следующих главах вы сможете выполнить все команды SQL, требующие подключения к базе данных, в AlloyDB Studio.

Чтобы открыть AlloyDB Studio, выполните следующие действия:

  1. В консоли Google Cloud перейдите на страницу «Кластеры» для AlloyDB for PostgreSQL.
  2. Выберите основной экземпляр, чтобы открыть его веб-интерфейс.

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 Authentication и выполните аутентификацию.

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;

Если какой-либо из этих параметров отображается как «выключен», попробуйте повторить шаг «Обновить флаги базы данных» и снова проверьте состояние с помощью команд «показать...».

Включите необходимые расширения.

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

Для параметризованных представлений попробуйте:

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 в клонированном репозитории.

Создать корзину хранения

Для импорта данных из клонированного репозитория в базу данных AlloyDB используйте Google SDK (gcloud). Для этого необходимо создать сегмент Cloud Storage и предоставить доступ учетной записи службы AlloyDB. В качестве альтернативы вы всегда можете попробовать сделать это через веб-консоль, как описано в документации.

В терминале 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.

Проверьте импорт, подсчитав количество записей в таблице real_estate.agents с помощью AlloyDB Studio:

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.';

...

Если вы работаете со своим собственным набором данных, то можете следовать описанному руководству по добавлению метаданных для вашей схемы.

Создать контекст онтологии схемы

Контекстный набор содержит шаблоны запросов, фасеты и поиск значений, которые помогают модели ИИ генерировать запросы. Без контекстного набора агент ИИ будет пытаться сгенерировать запрос, используя только доступную информацию из public схемы, основываясь главным образом на именах таблиц, столбцах и метаданных, пытаясь определить правильные объекты для ответа на естественный запрос.

Контекстный набор QueryData представляет собой JSON-файл с шаблонами запросов, фасетами и поиском по значениям. Примеры можно посмотреть в документации .

Загрузка контекста QueryData в веб-консоль

Чтобы загрузить набор контекстов, откройте AlloyDB Studio, и в левой панели внизу вы увидите Context sets и три точки.

d3578ceee65cd0dd.jpeg

Нажмите на эти три точки и выберите Create context set .

В открывшемся диалоговом окне введите следующие значения:

  • Имя: sixrecipes
  • Описание: Real Estate Query Data
  • Загрузите файл контекста: оставьте его пустым.

При первом нажатии кнопки Create » может потребоваться некоторое время для инициализации хранилища контекста.

Проверьте идентификатор набора контекста QueryData, снова щелкнув три точки справа от имени набора контекста, и выберите Edit context set .

6739fb9f54d7f79d.jpeg

Идентификатор набора контекста отображается в верхней части открытой вкладки справа.

746b38c52a70bb3c.jpeg

Сохраните этот ID для последующих шагов.

Запуск ИИ-агента

To interact with the QueryData backend to generate and execute query you can use AlloyDB Studio interface as it is described in the documentation . But in this lab you will be using an AI agent as the main tool of communication with the database. The AI agents are a more realistic scenario for future workloads and Google ADK framework provides a great web interface where you can see all the steps and interactions between models, APIs and applications.

Проверьте конфигурацию источника агента в 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",
]

Существует процедура call_querydata_api для работы с API QueryData , а также набор инструментов для работы с навыками работы с 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]
)

Вот сам корневой агент с подсказкой и доступными инструментами.

# 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 с параметрами для запросов к API Agent и QueryData.

Сохраните идентификатор набора контекста в переменную среды:

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

Создайте файл конфигурации среды выполнения (.env) в каталоге 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

Установите зависимости и запустите веб-службу агента:

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

Чтобы запустить интерфейс агента, щелкните сгенерированный URL-адрес веб-сервера (например, http://127.0.0.1:8000) в Cloud Shell. Если это не сработает, в Cloud Shell щелкните Web Preview -> Preview на порту 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

Отправьте дополнительный вопрос (вопрос 2):

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. Достижение высокой точности и актуальности для бизнеса путем внедрения интерпретации.

Внедрение интерпретации в запросы гарантирует, что сгенерированные запросы будут соответствовать конкретной бизнес-логике.

Вы попробуете рассмотреть подобный пример с клиентом, который спрашивает о возможности приобретения одноэтажных домов в Дель-Маре. Этот вопрос имеет два важных показателя и отражает часто встречающийся шаблон для выбора.

Попробуйте ввести в агента следующий вопрос на естественном языке (вопрос 4).

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 * рейтинг школы + 0,2 * расстояние в милях)

Вот формула взвешенного ранжирования:

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

Загрузите файл контекста sixrecipe_context1_1.json из Cloud Shell, используя интерфейс командной строки в Cloud Shell:

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

Или воспользуйтесь пунктом меню Download в меню Cloud Shell:

c0d781806d966dcb.jpeg

Загрузите файл sixrecipe_context1_1.json в свой набор контекстов sixrecipe с помощью AlloyDB Studio, чтобы определить эту стандартную структуру. Щелкните три точки справа от контекста 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-запросом формируется следующее намерение:

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

В левой панели веб-интерфейса ADK вы можете увидеть соответствующий запрос, как показано здесь:

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." . Это расширяет возможности применения этого шаблона, позволяя использовать его не только в одном случае.

Отправьте этот запрос (вопрос 6):

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-условия, динамически комбинируемые с шаблонами для обработки квалификаторов (например, "стартовая семья", "вид на океан") без создания отдельных шаблонов для каждой комбинации.

Вместо создания шаблона для каждого запроса или условия мы можем подготовить фрагменты, которые можно применять к существующим шаблонам, добавляя новые измерения к запросу.

Определите фасет для свойств со следующими атрибутами:

  • Односемейный дом
  • Статус активного объявления
  • Три-четыре спальни и как минимум две ванные комнаты.
  • Расположение в муниципалитете с уровнем преступности ниже 500 на 100 000 жителей.

Загрузите файл контекста с фасетной разбивкой:

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

Загрузите файл sixrecipe_context2_1.json в свой набор контекстов sixrecipes в AlloyDB Studio, чтобы настроить фасеты.

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

Выполните этот запрос на английском языке (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 в свой набор контекстов и выполните следующую команду (вопрос 8):

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. Уточнение ваших личных данных с помощью поиска по значению.

Неоднозначные запросы могут соответствовать нескольким сущностям, например, название местоположения может быть перепутано с названием компании. Поиск по значениям сопоставляет термины с концепциями базы данных, чтобы помочь разрешить такие ситуации.

The value search provides associations between column name plus values in the column and a concept type which can be for example a city, country or name. And since it has indexed not only metadata but also representative samples of the data in a column it can associate a value used in the natural language request with a potential concept type and what table and column can be used in the resulting 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

Было исправлено название, и было сделано предположение, что вопрос касался местоположения объектов недвижимости, с целью сопоставления объектов в городе Вествуд.

Включите расширение pg_trgm в AlloyDB Studio для поддержки нечеткого сравнения строк:

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 и сохраните его.

Повторно запустите приглашение (вопрос 9):

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

Устранение неоднозначности в запросе

Уточните подсказку для ввода (Q10):

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. Семантический поиск и функции искусственного интеллекта

Функции искусственного интеллекта AlloyDB позволяют выполнять семантическую фильтрацию неструктурированных данных. Используйте такие функции, как ai.if и google_ml.embedding, непосредственно в SQL-запросах наряду со стандартными операторами базы данных.

Шаблон с операторами 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. Безопасность и конфиденциальность

При генерации запросов через API QueryData всегда существует риск нарушения конфиденциальности, поскольку сгенерированные запросы могут раскрыть данные, принадлежащие другим пользователям. Параметризованные защищенные представления (PSV) обеспечивают соблюдение границ безопасности на уровне строк во время генерации NL2SQL.

Создать параметризованное защищенное представление

У нас есть таблица real_estate.offers с информацией о предложениях по всем нашим сделкам с недвижимостью. Мы хотим, чтобы наши агенты видели только свои собственные сделки, а не сделки других агентов. Мы создаем PSV с возможностью фильтрации строк по agent_id.

Создайте параметризованное защищенное представление для таблицы предложений в 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;

Проверьте определение параметризованного представления безопасности:

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

А что насчёт таблицы real_estate.offers?

Ограничьте доступ к данным QueryData с помощью параметризованных защищенных представлений AlloyDB.

В файле 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"
                }
            }
        },

В результате каждый запрос к API QueryData будет передавать этот параметр, и если в запрос будет включено защищенное представление, будет применена фильтрация, возвращающая только результаты для конкретного агента.

Выполните этот запрос (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

Если вы выполните следующий запрос к базовой таблице real_estate.offers в 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

Функция возвращает более двух записей, отображая данные по всем агентам.

На этом наша лабораторная работа завершена! Вы научились использовать API QueryData, функции искусственного интеллекта и параметризованные безопасные представления 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, чтобы избежать постоянных расходов на хранение:

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

14. Поздравляем!

Поздравляем с завершением практического занятия!

Что мы рассмотрели

  • Как развернуть кластер AlloyDB и основной экземпляр
  • Как включить QueryData, функции искусственного интеллекта и параметризованные безопасные представления для AlloyDB
  • Как создать набор контекстов QueryData
  • Как использовать различные шаблоны и контексты в API QueryData
  • Как сделать шаблоны QueryData более гибкими с помощью фасетов.
  • Как разрешить неоднозначность данных с помощью поиска по значению и триграммного индексирования
  • Как использовать функции ИИ для семантического поиска с помощью шаблонов
  • Как использовать параметризованные защищенные представления с API QueryData

Опрос

Выход:

Как вы будете использовать этот учебный материал?

Прочитайте только до конца. Прочитайте текст и выполните упражнения.