1. 简介
此 Codelab 将引导您使用 Spanner 的 AI 和图表功能来增强现有零售数据库。您将学习在 Spanner 中利用机器学习来更好地为客户提供服务的实用技巧。具体来说,我们将实现 k 最近邻 (kNN) 和近似最近邻 (ANN),以发现符合个人客户需求的新产品。您还将集成 LLM,以针对为何做出特定产品推荐提供清晰的自然语言解释。
除了推荐之外,我们还将深入了解 Spanner 的图功能。您将使用图查询,根据客户交易记录和产品说明对产品之间的关系进行建模。这种方法可以发现深度相关的商品,从而显著提高“购买此商品的客户也购买了”或“相关商品”功能的关联性和有效性。完成此 Codelab 后,您将掌握构建完全由 Google Cloud Spanner 提供支持的智能、可扩缩且响应迅速的零售应用所需的技能。
场景
您在一家电子设备零售商工作。您的电子商务网站有一个标准的 Spanner 数据库,其中包含 Products、Orders 和 OrderItems。
一位客户访问您的网站,并提出了具体需求:“我想购买一款高性能键盘。我有时会在海滩上编写代码,所以键盘可能会弄湿。”
您的目标是使用 Spanner 的高级功能智能地回答此请求:
- 查找: 使用向量搜索,超越简单的关键字搜索,查找其 说明 在语义上与用户请求匹配的产品。
- 解释: 使用 LLM 分析最匹配的结果,并解释 为何 推荐的产品非常适合,从而建立客户信任。
- 关联: 使用图表查询查找客户经常与推荐产品一起购买的 其他 产品。
2. 准备工作
- 创建项目 :在 Google Cloud 控制台的“项目选择器”页面上,选择或创建一个 Google 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 语句。您需要展开侧边的菜单,找到放大镜。

- 读取 Products 表。创建一个新标签页,或使用已创建的“无标题查询”标签页。

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 步:生成和存储向量嵌入
我们的 Product 表包含文本说明,但 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-最近邻 (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表,它使用标签“Purchased”将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 分隔,我们将其分解为多个部分。
- 首先,我们使用向量搜索找到最佳匹配项。
- ML.PREDICT 使用 EmbeddingsModel 从用户的文本查询生成向量嵌入。
- 该查询计算此新嵌入与所有商品的存储 p.embedding_vector 之间的 COSINE_DISTANCE。
- 它会选择并返回距离最小(语义相似度最高)的单个 bestMatch 产品。
- 接下来,我们遍历图表以搜索关系。
NEXT MATCH (bestMatch)<-[:Purchased]-(user:Orders)-[:Purchased]->(purchasedWith:products_with_embeddings)
- 该查询从 bestMatch 回溯到常见的 Orders 节点 (user),然后向前到其他 purchasedWith 产品。
- 它会过滤掉原始产品,并使用 GROUP BY 和 COUNT(1) 聚合商品的共同购买频率。
- 它会返回共同购买频率最高的前 3 个共同购买产品 (purchasedWith),并按共同购买频率排序。
此外,我们还找到了用户订单关系。
NEXT MATCH (bestMatch)<-[:Purchased]-(user:Orders)-[purchased:Purchased]->(purchasedWith)
- 此中间步骤执行遍历模式以绑定关键实体:bestMatch、连接用户:Orders 节点和 purchasedWith 商品。
- 它专门将关系本身绑定为 purchased,以便在下一步中提取数据。
- 此模式可确保建立上下文,以提取订单特定和产品特定的详细信息。
- 最后,我们输出结果,以便在作为 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)以查找语义相关的产品,以及如何使用图表查询来发现产品关系。您构建的系统不仅可以 查找 产品,还可以 解释 推荐产品和 推荐 相关商品,所有这些都来自一个可扩缩的数据库。