1. はじめに
この Codelab では、Spanner の AI 機能とグラフ機能を使用して既存の小売データベースを強化する方法について説明します。このコースでは、Spanner 内で ML を活用して顧客に提供するサービスを改善するための実践的な手法を学びます。具体的には、k 近傍法(kNN)と近似最近傍探索(ANN)を実装して、個々の顧客のニーズに合った新商品を見つけます。また、LLM を統合して、特定の商品のおすすめが表示された理由をわかりやすい自然言語で説明します。
推奨事項に加えて、Spanner のグラフ機能についても詳しく説明します。グラフクエリを使用して、顧客の購入履歴と商品説明に基づいて商品間の関係をモデル化します。このアプローチにより、関連性の高い商品を検出できるため、「お客様が一緒に購入した商品」や「関連商品」機能の関連性と有効性が大幅に向上します。この Codelab を終えると、Google Cloud Spanner で完全に動作するインテリジェントでスケーラブルなレスポンシブな小売アプリケーションを構築するスキルを習得できます。
シナリオ
あなたは電子機器小売店に勤めています。e コマース サイトには、Products、Orders、OrderItems を含む標準の Spanner データベースがあります。
お客様が「高性能なキーボードを購入したい」という具体的なニーズを持ってサイトにアクセスします。ビーチでコーディングすることもあるので、濡れる可能性があります。」
このリクエストにインテリジェントに応答するために、Spanner の高度な機能を使用することが目標です。
- 検索: ベクトル検索を使用して、単純なキーワード検索を超えて、ユーザーのリクエストと意味的に一致する商品説明の商品を検索します。
- 説明: LLM を使用して上位一致を分析し、推奨事項が適切な理由を説明して、お客様の信頼を築きます。
- 関連付け: グラフクエリを使用して、お客様がそのおすすめ商品と一緒に頻繁に購入した他の商品を見つけます。
2. 始める前に
- プロジェクトを作成する: Google Cloud コンソールのプロジェクト セレクタ ページで、Google Cloud プロジェクトを選択または作成します。
- 課金を有効にする: Cloud プロジェクトで課金が有効になっていることを確認します。詳しくは、プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
- Cloud Shell をアクティブにする: コンソールで [Cloud Shell をアクティブにする] ボタンをクリックして、Cloud Shell をアクティブにします。Cloud Shell ターミナルとエディタを切り替えることができます。

- プロジェクトの承認と設定 Cloud Shell に接続したら、認証が完了しており、プロジェクトがプロジェクト ID に設定されていることを確認します。
gcloud auth list
gcloud config list project
- プロジェクトが設定されていない場合は、次のコマンドを使用して設定します。
<PROJECT_ID>は実際のプロジェクト ID に置き換えてください。
export PROJECT_ID=<PROJECT_ID>
gcloud config set project $PROJECT_ID
- 必要な API を有効にする: Spanner、Vertex AI、Compute Engine の各 API を有効にします。これには数分かかることがあります。
gcloud services enable \
spanner.googleapis.com \
aiplatform.googleapis.com \
compute.googleapis.com
- 再利用する環境変数をいくつか設定します。
export INSTANCE_ID=my-first-spanner
export INSTANCE_CONFIG=regional-us-central1
- Spanner インスタンスがまだない場合は、無料トライアルの Spanner インスタンスを作成します。データベースをホストするには、Spanner インスタンスが必要です。構成として
regional-us-central1を使用します。必要に応じて更新できます。
gcloud spanner instances create $INSTANCE_ID \
--instance-type=free-instance --config=$INSTANCE_CONFIG \
--description="Trial Instance"
3. アーキテクチャの概要
Spanner は、Vertex AI でホストされているモデルを除く、必要な機能をすべてカプセル化します。
4. ステップ 1: データベースを設定し、最初のクエリを送信します。
まず、データベースを作成し、小売業のサンプルデータを読み込み、Spanner に Vertex AI との通信方法を指示する必要があります。
このセクションでは、次の SQL スクリプトを使用します。
- Spanner のプロダクト ページに移動します。
- 正しいインスタンスを選択します。

- 画面で [データセットを探索] を選択します。ポップアップで [Retail] オプションを選択します。


- Spanner Studio に移動します。Spanner Studio には、クエリエディタと SQL クエリ結果テーブルが統合された [エクスプローラ] ペインがあります。この 1 つのインターフェースから DDL、DML、SQL ステートメントを実行できます。サイドメニューを開き、虫眼鏡のアイコンを探します。

- Products テーブルを読み取ります。新しいタブを作成するか、すでに作成されている [無題のクエリ] タブを使用します。

SELECT *
FROM Products;
5. ステップ 2: AI モデルを作成します。
次に、Spanner オブジェクトを使用してリモートモデルを作成します。これらの SQL ステートメントは、Vertex AI エンドポイントにリンクする Spanner オブジェクトを作成します。
- Spanner Studio で新しいタブを開き、2 つのモデルを作成します。1 つ目は、エンベディングの生成を可能にする EmbeddingsModel です。2 つ目は LLMModel で、LLM(この例では gemini-2.5-flash)を操作できます。<PROJECT_ID> を実際のプロジェクト ID に置き換えてください。
### Create the Embedding Model object in Spanner
CREATE MODEL EmbeddingsModel INPUT(
content STRING(MAX),
) OUTPUT(
embeddings STRUCT<statistics STRUCT<truncated BOOL, token_count FLOAT32>, values ARRAY<FLOAT32>>,
) REMOTE OPTIONS (
endpoint = '//aiplatform.googleapis.com/projects/<PROJECT_ID>/locations/us-central1/publishers/google/models/text-embedding-005'
);
### Create the LLM Model object in Spanner
CREATE MODEL LLMModel INPUT(
prompt STRING(MAX),
) OUTPUT(
content STRING(MAX),
) REMOTE OPTIONS (
endpoint = '//aiplatform.googleapis.com/projects/<PROJECT_ID>/locations/us-central1/publishers/google/models/gemini-2.5-flash',
default_batch_size = 1
);
- 注:
PROJECT_IDは実際の$PROJECT_IDに置き換えてください。

この手順をテストする: SQL エディタで次のコマンドを実行して、モデルが作成されたことを確認できます。
SELECT *
FROM information_schema.models;

6. ステップ 3: ベクトル エンベディングを生成して保存する
Product テーブルにはテキストの説明がありますが、AI モデルはベクトル(数値の配列)を理解します。これらのベクトルを保存するための新しい列を追加し、すべての商品説明を EmbeddingsModel で実行して、その列にデータを入力する必要があります。
- エンベディングをサポートする新しいテーブルを作成します。まず、エンベディングをサポートできるテーブルを作成します。Google は、商品テーブルのサンプル エンベディングとは異なるエンベディング モデルを使用しています。ベクトル検索が適切に機能するには、エンベディングが同じモデルから生成されていることを確認する必要があります。
CREATE TABLE products_with_embeddings (
ProductID INT64,
embedding_vector ARRAY<FLOAT32>(vector_length=>768),
embedding_text STRING(MAX)
)
PRIMARY KEY (ProductID);
- モデルから生成されたエンベディングを使用して新しいテーブルにデータを入力します。ここでは、わかりやすくするために insert into ステートメントを使用します。これにより、クエリ結果が作成したテーブルにプッシュされます。
SQL ステートメントは、まずエンベディングを生成する関連するテキスト列をすべて取得して連結します。その後、使用したテキストなどの関連情報を返します。通常は必要ありませんが、結果を視覚化できるように含めています。
INSERT INTO products_with_embeddings (productId, embedding_text, embedding_vector)
SELECT
ProductID,
content as embedding_text,
embeddings.values as embedding_vector
FROM ML.PREDICT(
MODEL EmbeddingsModel,
(
SELECT
ProductID,
embedding_text AS content
FROM (
SELECT
ProductID,
CONCAT(
Category,
" ",
Description,
" ",
Name
) AS embedding_text
FROM products)));
- 新しい埋め込みを確認します。生成されたエンベディングが表示されます。
SELECT *
FROM products_with_embeddings
LIMIT 1;

7. ステップ 4: ANN 検索用のベクトル インデックスを作成する
数百万のベクトルを瞬時に検索するには、インデックスが必要です。このインデックスにより、高速で水平方向にスケーリング可能な近似最近傍(ANN)検索が可能になります。
- 次の DDL クエリを実行してインデックスを作成します。距離指標として
COSINEを指定します。これは、セマンティック テキスト検索に最適です。Spanner では、WHERE 句がクエリの要件となるため、WHERE 句は実際に必要です。
CREATE VECTOR INDEX DescriptionEmbeddingIndex
ON products_with_embeddings(embedding_vector)
WHERE embedding_vector IS NOT NULL
OPTIONS (
distance_type = 'COSINE'
);
- [オペレーション] タブでインデックス作成のステータスを確認します。

8. ステップ 5: K 最近傍(KNN)検索で推奨事項を見つける
ここからが面白いところです。お客様のクエリに一致する商品を見つけましょう。「高性能なキーボードを購入したいのですが、ビーチでコーディングすることもあるので、濡れる可能性があります。」。
まず、K-Nearest Neighbor(KNN)検索から始めます。これは、クエリベクトルをすべての商品ベクトルと比較する完全一致検索です。精度は高いですが、非常に大規模なデータセットでは処理が遅くなる可能性があります(そのため、ステップ 5 で ANN インデックスを作成しました)。
このクエリは次の 2 つの処理を行います。
- サブクエリは ML.PREDICT を使用して、お客様のクエリのエンベディング ベクトルを取得します。
- 外側のクエリは COSINE_DISTANCE を使用して、クエリベクトルと各商品の embedding_vector の間の「距離」を計算します。距離が短いほど、一致度が高くなります。
SELECT
productid,
embedding_text,
COSINE_DISTANCE(
embedding_vector,
(
SELECT embeddings.values
FROM ML.PREDICT(
MODEL EmbeddingsModel,
(SELECT "I'd like to buy a high performance keyboard. I sometimes code while I'm at the beach so it may get wet." AS content)
)
)
) AS distance
FROM products_with_embeddings
WHERE embedding_vector IS NOT NULL
ORDER BY distance
LIMIT 5;
商品のリストが表示され、一番上に防水キーボードが表示されます。
9. ステップ 6: 近似(ANN)検索で推奨事項を見つける
KNN は優れていますが、数百万の商品と毎秒数千件のクエリを処理する本番環境システムでは、ANN インデックスの速度が必要です。
インデックスを使用するには、APPROX_COSINE_DISTANCE 関数を指定する必要があります。
- 上記と同様に、テキストのベクトル エンベディングを取得します。その結果を products_with_embeddings テーブルのレコードとクロス結合して、APPROX_COSINE_DISTANCE 関数で使用できるようにします。
WITH vector_query as
(
SELECT embeddings.values as vector
FROM ML.PREDICT(
MODEL EmbeddingsModel,
(SELECT "I'd like to buy a high performance keyboard. I sometimes code while I'm at the beach so it may get wet." as content)
)
)
SELECT
ProductID,
embedding_text,
APPROX_COSINE_DISTANCE(embedding_vector, vector, options => JSON '{\"num_leaves_to_search\": 10}') distance
FROM products_with_embeddings @{force_index=DescriptionEmbeddingIndex},
vector_query
WHERE embedding_vector IS NOT NULL
ORDER BY distance
LIMIT 5;
期待される出力: 結果は KNN クエリと同一または非常に類似している必要がありますが、インデックスを使用することで効率的に実行されます。この例では、このことに気づかないかもしれません。
10. ステップ 7: LLM を使用して推奨事項を説明する
商品のリストを表示するだけでも十分ですが、その商品が適しているかどうかの理由を説明するとさらに良いでしょう。これには LLMModel(Gemini)を使用できます。
このクエリは、ステップ 4 の KNN クエリを ML.PREDICT 呼び出し内にネストします。CONCAT を使用して LLM のプロンプトを作成し、次の情報を指定します。
- 明確な指示(「『はい』または『いいえ』で答え、その理由を説明してください」など)。
- お客様の元のクエリ。
- 上位一致商品の名前と説明。
LLM は、クエリに対して各商品を評価し、自然言語の回答を提供します。
SELECT
ProductID,
embedding_text,
content AS LLMResponse
FROM ML.PREDICT(
MODEL LLMModel,
(
SELECT
ProductID,
embedding_text,
CONCAT(
"Answer with ‘Yes' or ‘No' and explain why: Is this a good fit for me?",
"I'd like to buy a high performance keyboard. I sometimes code while I'm at the beach so it may get wet. \n",
"Product Description:", embedding_text
) AS prompt,
FROM products_with_embeddings
WHERE embedding_vector IS NOT NULL
ORDER BY COSINE_DISTANCE(
embedding_vector,
(
SELECT embeddings.values
FROM ML.PREDICT(
MODEL EmbeddingsModel,
(SELECT "I'd like to buy a high performance keyboard. I sometimes code while I'm at the beach so it may get wet." AS content)
)
)
)
LIMIT 5
),
STRUCT(1056 AS maxOutputTokens)
);
期待される出力: 新しい LLMResponse 列を含むテーブルが返されます。回答は次のようになります。「いいえ。理由は次のとおりです。* 「耐水」は「防水」ではありません。「防水」キーボードは、水しぶき、小雨、こぼれた液体に対応できます。
11. ステップ 8: プロパティ グラフを作成する
別のタイプのおすすめ「この商品を購入したお客様は、こちらも購入しています」について説明します。
これは関係ベースのクエリです。この目的に最適なツールは、プロパティ グラフです。Spanner を使用すると、データを複製せずに既存のテーブルの上にグラフを作成できます。
この DDL ステートメントは、グラフを定義します。
- ノード:
ProductテーブルとUserテーブル。ノードは、関係を導き出したいエンティティです。たとえば、自社の商品を購入した顧客が「XYZ」という商品も購入したことを知りたいとします。 - エッジ:
Ordersテーブル。User(ソース)をProduct(宛先)に「購入済み」というラベルで接続します。エッジは、ユーザーと購入したアイテムの関係を示します。
CREATE PROPERTY GRAPH RetailGraph
NODE TABLES (
products_with_embeddings,
Orders
)
EDGE TABLES (
OrderItems
SOURCE KEY (OrderID) REFERENCES Orders
DESTINATION KEY (ProductID) REFERENCES products_with_embeddings
LABEL Purchased
);
12. ステップ 9: ベクトル検索とグラフクエリを組み合わせる
これは最も強力なステップです。AI ベクトル検索とグラフクエリを 1 つのステートメントに組み合わせて、関連商品を見つけます。
このクエリは NEXT statement で区切られた 3 つの部分で読み取られます。セクションごとに分解してみましょう。
- まず、ベクトル検索を使用して最適な一致を見つけます。
- ML.PREDICT は、EmbeddingsModel を使用して、ユーザーのテキスト クエリからベクトル エンベディングを生成します。
- このクエリは、この新しいエンベディングとすべての商品の保存済み p.embedding_vector の間の COSINE_DISTANCE を計算します。
- 距離が最小(意味的類似性が最大)の bestMatch 商品を 1 つ選択して返します。
- 次に、グラフをトラバースして関係を検索します。
NEXT MATCH (bestMatch)<-[:Purchased]-(user:Orders)-[:Purchased]->(purchasedWith:products_with_embeddings)
- このクエリは、bestMatch から共通の Orders ノード(ユーザー)まで遡り、そこから他の purchasedWith プロダクトまで進みます。
- 元の商品をフィルタで除外し、GROUP BY と COUNT(1) を使用して、アイテムが同時に購入される頻度を集計します。
- 共起頻度で並べ替えられた、上位 3 つの同時購入商品(purchasedWith)を返します。
また、ユーザーと注文の関係も特定します。
NEXT MATCH (bestMatch)<-[:Purchased]-(user:Orders)-[purchased:Purchased]->(purchasedWith)
- この中間ステップでは、走査パターンを実行して、キーエンティティ(bestMatch、接続ユーザーの Orders ノード、purchasedWith アイテム)をバインドします。
- 具体的には、次のステップでデータを抽出するために、関係自体を購入済みとしてバインドします。
- このパターンにより、注文固有の詳細と商品固有の詳細を取得するためのコンテキストが確立されます。
- 最後に、グラフノードとして返される結果を出力します。グラフノードは、SQL 結果として返される前にフォーマットする必要があります。
GRAPH RetailGraph
MATCH (p:products_with_embeddings)
WHERE p.embedding_vector IS NOT NULL
RETURN p AS bestMatch
ORDER BY COSINE_DISTANCE(
p.embedding_vector,
(
SELECT embeddings.values
FROM ML.PREDICT(
MODEL EmbeddingsModel,
(SELECT "I'd like to buy a high performance keyboard. I sometimes code while I'm at the beach so it may get wet." AS content)
)
)
)
LIMIT 1
NEXT
MATCH (bestMatch)<-[:Purchased]-(user:Orders)-[:Purchased]->(purchasedWith:products_with_embeddings)
FILTER bestMatch.productId <> purchasedWith.productId
RETURN bestMatch, purchasedWith
GROUP BY bestMatch, purchasedWith
ORDER BY COUNT(1) DESC
LIMIT 3
NEXT
MATCH (bestMatch)<-[:Purchased]-(user:Orders)-[purchased:Purchased]->(purchasedWith)
RETURN
TO_JSON(Purchased) AS purchased,
TO_JSON(user.OrderID) AS user,
TO_JSON(purchasedWith.productId) AS purchasedWith;
出力の予想: クロスセルにおすすめの商品として、一緒に購入された上位 3 件の商品を表す JSON オブジェクトが表示されます。
13. クリーンアップ
課金されないようにするには、作成したリソースを削除します。
- Spanner インスタンスを削除する: インスタンスを削除すると、データベースも削除されます。
gcloud spanner instances delete my-first-spanner --quiet
- Google Cloud プロジェクトを削除する: このプロジェクトを Codelab 専用に作成した場合は、削除するのが最も簡単なクリーンアップ方法です。
- Google Cloud コンソールの [リソースの管理] ページに移動します。
- プロジェクトを選択し、[削除] をクリックします。
🎉 お疲れさまでした。
Spanner AI と Graph を使用して、高度なリアルタイム レコメンデーション システムを構築できました。
このチュートリアルでは、Spanner を Vertex AI と統合してエンベディングと LLM を生成する方法、高速ベクトル検索(KNN と ANN)を実行して意味的に関連性の高い商品を見つける方法、グラフクエリを使用して商品の関係を検出する方法について説明しました。単一のスケーラブルなデータベースから、商品を検索するだけでなく、おすすめを説明したり、関連商品を提案したりできるシステムを構築しました。