1. 简介
此 Codelab 将引导您使用 Spanner 的 AI 和图表功能来增强现有的零售数据库。您将学习在 Spanner 中利用机器学习来更好地服务客户的实用技巧。具体来说,我们将实现 k 最近邻 (kNN) 和近似最近邻 (ANN),以发现符合个别客户需求的新产品。您还将集成 LLM,以便用清晰的自然语言解释为什么会提供特定的商品推荐。
除了推荐之外,我们还将深入探讨 Spanner 的图表功能。您将使用图查询来根据客户购买历史记录和商品说明对商品之间的关系进行建模。这种方法有助于发现深度相关商品,从而显著提高“买家还买了”或“相关商品”功能的关联性和效果。完成此 Codelab 后,您将掌握相关技能,能够构建完全由 Google Cloud Spanner 提供支持的智能、可扩缩且响应迅速的零售应用。
场景
您在一家电子设备零售商工作。您的电子商务网站有一个包含 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 的产品页面。
- 选择正确的实例。

- 在屏幕上,选择“探索数据集”。然后在弹出式窗口中选择“零售”选项。


- 前往 Spanner Studio。Spanner Studio 包含一个与查询编辑器和 SQL 查询结果表集成的“探索器”窗格。您可以在这一个界面中运行 DDL、DML 和 SQL 语句。您需要展开侧边栏中的菜单,然后找到放大镜图标。

- 解读“商品”表格。创建新标签页或使用已创建的“未命名的查询”标签页。

SELECT *
FROM Products;
5. 第 2 步:创建 AI 模型。
现在,我们来创建包含 Spanner 对象的远程模型。这些 SQL 语句会创建链接到 Vertex AI 端点的 Spanner 对象。
- 在 Spanner Studio 中打开一个新标签页,然后创建两个模型。第一个是 EmbeddingsModel,可用于生成嵌入。第二个是 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 步:生成和存储向量嵌入
我们的“商品”表包含文本说明,但 AI 模型理解的是向量(数字数组)。我们需要添加一个新列来存储这些向量,然后通过运行 EmbeddingsModel 处理所有商品说明来填充该列。
- 创建新表以支持嵌入。首先,创建一个可以支持嵌入的表。我们使用的嵌入模型与商品表格示例嵌入不同。您需要确保嵌入是使用同一模型生成的,这样向量搜索才能正常运行。
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指定为距离指标,该指标非常适合语义文本搜索。请注意,WHERE 子句实际上是必需的,因为 Spanner 会要求查询包含该子句。
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 索引的原因)。
此查询会执行以下两项操作:
- 子查询使用 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 向量搜索和图查询来查找相关商品。
此查询分为三个部分,以 NEXT statement 分隔,我们将其分解为多个部分。
- 首先,我们使用 Vector Search 找到最佳匹配项。
- ML.PREDICT 使用 EmbeddingsModel 根据用户的文本查询生成向量嵌入。
- 该查询会计算此新嵌入与所有产品的存储 p.embedding_vector 之间的 COSINE_DISTANCE。
- 它会选择并返回距离最小(语义相似度最高)的单个 bestMatch 产品。
- 接下来,我们遍历图以寻找关系。
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)以查找在语义上相关的产品,以及如何使用图查询来发现产品关系。您构建了一个系统,该系统不仅可以查找产品,还可以说明推荐内容和建议相关商品,而这一切都只需一个可扩缩的数据库即可实现。