AlloyDB で 100% に近い精度の Text-to-SQL を実現するための 6 つのレシピ - Codelab

1. はじめに

この Codelab では、QueryData API を使用して最適な結果を取得する方法について説明します。データベースに自然言語リクエストを行う際に、正確で再現可能な結果を生成する方法について説明します。このガイドでは、自然言語から SQL(NL2SQL)への変換とその実用的なアプリケーションのさまざまな側面を説明する 6 つのレシピを複数の章にわたって紹介します。

前提条件

  • 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 で識別されます。
  • なお、3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。

課金を有効にする

個人用の請求先アカウントを設定する

Google Cloud クレジットを使用して課金を設定した場合は、この手順をスキップできます。

個人用の請求先アカウントを設定するには、Cloud コンソールでこちらに移動して課金を有効にします。

注意事項:

  • このラボを完了するのにかかる Cloud リソースの費用は 5 米ドル未満です。
  • このラボの最後の手順に沿ってリソースを削除すると、それ以上の料金は発生しません。
  • 新規ユーザーは、300 米ドル分の無料トライアルをご利用いただけます。

Cloud Shell の起動

Google Cloud はノートパソコンからリモートで操作できますが、この Codelab では、Google Cloud Shell(Cloud 上で動作するコマンドライン環境)を使用します。

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、ネットワーキング サービス、Agent Platform を使用するには、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
 

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

データベース フラグを更新する

インスタンス レベルでフラグを有効にする前に、Data Access 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. プライマリ インスタンスを選択して、ウェブ インターフェースを開きます。

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 がすべて有効になっていることを確認する必要があります。

quickstart_db に接続している AlloyDB Studio で、次のコマンドを実行します。

show google_ml_integration.enable_ai_query_engine;
show parameterized_views.enabled;

いずれかが「off」になっている場合は、「データベース フラグを更新する」の手順を繰り返し、「show...」コマンドを使用してステータスを再度確認します。

必要な拡張機能を有効にします。

quickstart_db に接続している AlloyDB Studio で、次のコマンドを実行します。

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

parameterized_views と google_ml の拡張機能のバージョンを確認します。

quickstart_db に接続している AlloyDB Studio で、次のコマンドを実行します。

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;

バージョンを再度確認します。

quickstart_db に接続している AlloyDB Studio で、次のコマンドを実行します。

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 サービス アカウントにアクセス権を付与する必要があります。または、ドキュメントに記載されているように、ウェブ コンソールを使用して実行することもできます。

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 と 3 つのドットをクリックします。

d3578ceee65cd0dd.jpeg

その他アイコンをクリックして Create context set を選択します。

開いたダイアログで、次の値を入力します。

  • 名前: sixrecipes
  • 説明: Real Estate Query Data
  • コンテキスト ファイルをアップロードする: 空のままにする

Create ボタンを初めて押すと、コンテキスト ストレージの初期化に時間がかかることがあります。

コンテキスト セット名の右側にある 3 つのドットをもう一度クリックして Edit context set を選択し、QueryData コンテキスト セット ID を確認します。

6739fb9f54d7f79d.jpeg

コンテキスト セット ID は、右側の開いたタブの上部に表示されます。

746b38c52a70bb3c.jpeg

後の手順で使用するため、この ID を保存します。

AI エージェントを開始する

QueryData バックエンドとやり取りしてクエリを生成して実行するには、ドキュメントで説明されているように、AlloyDB Studio インターフェースを使用します。ただし、このラボでは、データベースとの主な通信ツールとして AI エージェントを使用します。AI エージェントは、将来のワークロードのより現実的なシナリオです。Google ADK フレームワークは、モデル、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

エージェントを開始する前に、エージェントと QueryData API リクエストのパラメータを含む .env ファイルを準備する必要があります。

コンテキスト セット 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

依存関係をインストールして、エージェント ウェブサービスを開始します。

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 で生成されたウェブサーバーの URL(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. 解釈を挿入して高い精度とビジネス関連性を実現する

解釈をプロンプトに挿入することで、生成されたクエリが特定のビジネス ロジックと一致するようになります。

ここでは、デルマーの平屋建ての家を探している顧客の例を試します。この質問には 2 つの重要な指標があり、選択の頻繁なパターンを表しています。

エージェントで次の自然言語プロンプト(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;

結果として 1 行が返されます。

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.

別のプロンプトをテストします(Q5):

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 ステートメントとともに、次のインテントが生成されます。

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

インテントは、ADK ウェブ インターフェースの左側のペインに表示されます。

bfb9d6bc718f57cf.jpeg

結果には、スコアで並べ替えられた 2 つのプロパティが表示されます。

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 条件です。これにより、すべての順列に対して個別のテンプレートを作成することなく、修飾子(「スターター ファミリー」、「オーシャン ビュー」など)を処理できます。

クエリや条件ごとにテンプレートを作成する代わりに、既存のテンプレートに適用してクエリにディメンションを追加できるフラグメントを準備できます。

次の属性を持つプロパティのファセットを定義します。

  • 一戸建て住宅
  • 有効なリスティングのステータス
  • ベッドルーム 3 ~ 4 室、バスルーム 2 室以上
  • 人口 10 万人あたりの犯罪率が 500 未満の市町村にある

ファセット コンテキスト ファイルをダウンロードします。

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

AlloyDB Studio の sixrecipes コンテキスト セットに sixrecipe_context2_1.json をアップロードして、ファセットを構成します。

  "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

ファセットを追加する

新しいパターン(プロパティのウィンドウ ビューなど)に基づいて、もう 1 つディメンションを追加したい場合はどうすればよいでしょうか?

ファセット定義内にオーシャン ビューのセマンティック マッチングを追加します。

    {
      "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";

クエリは 1 つのプロパティを返します。

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.

ここでは、名前のスペルが間違っていることと、似たような名前の場所で販売されている物件について話しているのか、不動産会社について話しているのかが不明確であるという 2 つの問題があります。このクエリを実行すると、次のようなクエリが返されます。

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

クエリの曖昧さを解消する

入力プロンプトを改良する(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. セマンティック検索と AI 関数

AlloyDB AI 関数を使用すると、非構造化データに対するセマンティック フィルタリングが可能になります。ai.if や google_ml.embedding などの関数を、標準のデータベース演算子とともに SQL クエリで直接使用します。

ILIKE 演算子を含むテンプレート

まず、ILIKE 演算子を含む新しいコンテキスト ファイルをアップロードします。

ILIKE テンプレート コンテキスト ファイルをダウンロードします。

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

context セットに 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%')

一致するレコードが 1 つ返されます。

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

ai.if セマンティック フィルタリング関数を評価するために、sixrecipe_context4_2.json をコンテキスト セットにアップロードします。

    {
      "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 を使用したセマンティック ベクトル検索を評価し、新しいキッチン家電を備えた家を検索します。

AlloyDB ベクトル検索を使用して、追加のファセットを含む sixrecipe_context4_3.json ファイルを使用してコンテキスト セットを更新します。

    {
      "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 というテーブルがあります。エージェントには、他のエージェントの商談ではなく、自分の商談のみを表示したいと考えています。agent_id に基づいて行をフィルタリングできる PSV を作成します。

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 が real_estate.offers ではなく TABLE_IDS に登録されています。

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

ビューをクエリすると、アクティブなエージェント セッション パラメータで結果がフィルタされ、不正なレコードデータが保護され、2 つのレコードのみが返されます。

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

2 つ以上のレコードが返され、すべてのエージェントのデータが表示されます。

これでラボは終了です。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 でパラメータ化されたセキュアビューを使用する方法

アンケート

出力:

このチュートリアルをどのように使用されますか?

全体を通して読むだけ 内容を読んで演習をやり遂げる