使用 AlloyDB 和 Vertex AI Agent Builder 构建智能购物助理 - 第 1 部分

1. 概览

在当今快节奏的零售环境中,提供卓越的客户服务并打造个性化的购物体验至关重要。我们将带您踏上技术之旅,创建一个知识驱动的聊天应用,该应用旨在回答客户问题、引导客户发现产品,并量身定制搜索结果。这款创新解决方案结合了 AlloyDB 强大的数据存储功能、用于上下文理解的内部分析引擎、用于相关性验证的 Gemini(大型语言模型),以及 Google 的 Agent Builder,可快速启动智能对话式助理。

挑战:现代零售客户希望获得即时解答,并获得符合其独特偏好的商品推荐。传统的搜索方法通常无法提供这种程度的个性化体验。

解决方案:我们的知识驱动型聊天应用可直接应对这一挑战。它利用从您的零售数据中提取的丰富知识库,了解客户意图、做出智能响应,并提供高度相关的结果。

构建内容

在本实验(第 1 部分)中,您将执行以下操作:

  1. 创建 AlloyDB 实例并加载电子商务数据集
  2. 在 AlloyDB 中启用 pgvector 和生成式 AI 模型扩展
  3. 根据商品说明生成嵌入
  4. 对用户搜索文本执行实时余弦相似度搜索
  5. 在无服务器 Cloud Run Functions 中部署解决方案

实验的第二部分将介绍 Agent Builder 步骤。

要求

  • 一个浏览器,例如 ChromeFirefox
  • 启用了结算功能的 Google Cloud 项目。

2. 架构

数据流:我们来详细了解一下数据在系统中的流动方式:

提取

第一步是将零售数据(目录、商品说明、客户互动)提取到 AlloyDB。

分析引擎

我们将使用 AlloyDB 作为分析引擎来执行以下操作:

  1. 情境提取:该引擎会分析 AlloyDB 中存储的数据,以了解产品、类别、客户行为等之间的关系(如适用)。
  2. 嵌入创建:为用户的查询和 AlloyDB 中存储的信息生成嵌入(文本的数学表示法)。
  3. 向量搜索:该引擎会执行相似性搜索,将查询嵌入与产品说明、评价和其他相关数据的嵌入进行比较。这会确定 25 个最相关的“最近邻”。

Gemini 验证

这些可能的回答会发送给 Gemini 进行评估。Gemini 会确定这些信息是否真正相关且安全,是否可以与用户分享。

回答生成

经过验证的响应会被构建为 JSON 数组,整个引擎会打包到从 Agent Builder 调用的无服务器 Cloud Run 函数中。

对话式互动

Agent Builder 会以自然语言格式向用户显示回答,从而促进双方进行互动对话。我们将在后续实验中介绍这部分内容。

3. 准备工作

创建项目

  1. Google Cloud Console 的项目选择器页面上,选择或创建一个 Google Cloud 项目
  2. 确保您的 Cloud 项目已启用结算功能。了解如何检查项目是否已启用结算功能
  3. 您将使用 Cloud Shell,它是一个在 Google Cloud 中运行的命令行环境,它预加载了 bq。点击 Google Cloud 控制台顶部的“激活 Cloud Shell”。

“激活 Cloud Shell”按钮图片

  1. 连接到 Cloud Shell 后,您可以使用以下命令检查自己是否已通过身份验证,以及项目是否已设置为您的项目 ID:
gcloud auth list
  1. 在 Cloud Shell 中运行以下命令,以确认 gcloud 命令了解您的项目。
gcloud config list project
  1. 如果项目未设置,请使用以下命令进行设置:
gcloud config set project <YOUR_PROJECT_ID>
  1. 启用所需的 API。
gcloud services enable alloydb.googleapis.com \
                       compute.googleapis.com \
                       cloudresourcemanager.googleapis.com \
                       servicenetworking.googleapis.com \
                       run.googleapis.com \
                       cloudbuild.googleapis.com \
                       cloudfunctions.googleapis.com \
                       aiplatform.googleapis.com

您可以通过控制台搜索各个产品或使用此链接,以替代 gcloud 命令。

如果缺少任何 API,您随时可以在实现过程中启用它。

如需了解 gcloud 命令和用法,请参阅文档

4. 数据库设置

在本实验中,我们将使用 AlloyDB 作为存储零售数据的数据库。它使用集群来存储所有资源,例如数据库和日志。每个集群都有一个主实例,用于提供对数据的访问点。表将存储实际数据。

我们来创建一个 AlloyDB 集群、实例和表,以便加载电子商务数据集。

创建集群和实例

  1. 在 Cloud 控制台中,前往 AlloyDB 页面。在 Cloud Console 中查找大多数页面时,最简单的方法是使用控制台的搜索栏进行搜索。
  2. 从该页面中选择“创建集群”:

f76ff480c8c889aa.png

  1. 您将看到如下所示的界面。使用以下值创建集群和实例
  • 集群 ID:“shopping-cluster
  • 密码:“alloydb
  • 与 PostgreSQL 15 兼容
  • 地区:“us-central1
  • 网络:“default

538dba58908162fb.png

  1. 选择默认网络后,您会看到如下所示的界面。选择“设置连接”。
    7939bbb6802a91bf.png
  2. 然后,选择“使用自动分配的 IP 范围”并点击“继续”。查看相关信息后,选择“创建关联”。768ff5210e79676f.png
  3. 设置好网络后,您可以继续创建集群。点击“创建集群”以完成集群设置,如下所示:

e06623e55195e16e.png

请务必将实例 ID 更改为“shopping-instance"”。

请注意,创建集群大约需要 10 分钟。成功后,您应该会看到类似以下内容的界面:

24eec29fa5cfdb3e.png

5. 数据提取

现在,我们需要添加一个包含商店数据的表格。前往 AlloyDB,选择主集群,然后选择 AlloyDB Studio:

847e35f1bf8a8bd8.png

您可能需要等待实例创建完成。完成后,使用您在创建集群时创建的凭据登录 AlloyDB。使用以下数据对 PostgreSQL 进行身份验证:

  • 用户名:“postgres
  • 数据库:“postgres
  • 密码:“alloydb

成功通过身份验证登录 AlloyDB Studio 后,您可以在编辑器中输入 SQL 命令。您可以使用最后一个窗口右侧的加号添加多个编辑器窗口。

91a86d9469d499c4.png

您将在编辑器窗口中输入 AlloyDB 命令,并根据需要使用“Run”“Format”和“Clear”选项。

启用扩展程序

如需构建此应用,我们将使用扩展程序 pgvectorgoogle_ml_integration。借助 pgvector 扩展程序,您可以存储和搜索向量嵌入。google_ml_integration 扩展程序提供了一些函数,可用于访问 Vertex AI 预测端点,以便在 SQL 中获取预测结果。运行以下 DDL 以启用这些扩展程序:

CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;
CREATE EXTENSION IF NOT EXISTS vector;

如果您想检查数据库上已启用的扩展程序,请运行以下 SQL 命令:

select extname, extversion from pg_extension;

创建表

使用以下 DDL 语句创建表:

CREATE TABLE
 apparels ( id BIGINT,
   category VARCHAR(100),
   sub_category VARCHAR(50),
   uri VARCHAR(200),
   image VARCHAR(100),
   content VARCHAR(2000),
   pdt_desc VARCHAR(5000),
   embedding vector(768) );

成功执行上述命令后,您应该能够在数据库中查看该表。示例屏幕截图如下所示:

908e33bbff58a6d.png

注入数据

在本实验中,我们在此 SQL 文件中提供了约 200 条记录的测试数据。其中包含 id, category, sub_category, uri, imagecontent。我们将在稍后的实验中填写其他字段。

从中复制 20 行/insert 语句,然后将这些行粘贴到空白编辑器标签页中,并选择“运行”。

如需查看表内容,请展开“Explorer”部分,直到看到名为 apparels 的表。选择三点状图标 (⋮) 即可看到用于查询表格的选项。系统会在新标签页的“编辑器”中打开一个 SELECT 语句。

b31ece70e670ab89.png

授予权限

运行以下语句可向用户 postgres 授予对 embedding 函数的执行权限:

GRANT EXECUTE ON FUNCTION embedding TO postgres;

向 AlloyDB 服务账号授予 Vertex AI User 角色

前往 Cloud Shell 终端,然后输入以下命令:

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"

6. 环境

返回“AlloyDB 实例”页面。

如需创建嵌入,我们需要有一个 context,即我们要包含在单个字段中的所有信息。为此,我们将创建一个商品说明(我们将其称为 pdt_desc)。在本例中,我们将使用每件商品的所有信息,但当您使用自己的数据执行此操作时,可以随意按照对您的业务有意义的方式对数据进行工程化处理。

在您新创建的实例的 AlloyDB Studio 中,运行以下语句。这将使用上下文数据更新 pdt_desc 字段:

UPDATE
 apparels
SET
 pdt_desc = CONCAT('This product category is: ', category, ' and sub_category is: ', sub_category, '. The description of the product is as follows: ', content, '. The product image is stored at: ', uri)
WHERE
 id IS NOT NULL;

此 DML 会使用表中所有可用字段以及其他依赖项(如果您的用例中有)中的信息创建一个简单的情境摘要。为了更精确地分类信息和创建情境,您可以随意以对您的业务有意义的方式对数据进行工程化处理。

7. 为上下文创建嵌入

计算机处理数字比处理文本要容易得多。嵌入系统会将文本转换为一组浮点数,这些浮点数应能代表文本,无论文本的措辞、所用语言等如何。

不妨考虑描述海边地点。这类房源可能被称为“on the water”“beachfront”“从房间步行到海边”“sur la mer”“на берегу океана”等。这些术语看起来各不相同,但它们的语义含义(或用机器学习术语来说,它们的嵌入)应该非常接近。

现在,数据和上下文已准备就绪,我们将运行 SQL 查询,将商品说明的嵌入向量添加到表中的 embedding 字段。您可以使用多种嵌入模型。我们将使用 Vertex AI 中的 text-embedding-004。请务必在整个项目中使用相同的嵌入模型!

注意:如果您使用的是之前创建的现有 Google Cloud 项目,则可能需要继续使用较低版本的文本嵌入模型,例如 textembedding-gecko。

UPDATE
 apparels
SET
 embedding = embedding( 'text-embedding-004',
   pdt_desc)
WHERE
 TRUE;

再次查看 apparels 表,看看其中的一些嵌入。请务必重新运行 SELECT 语句,以查看更改。

SELECT
 id,
 category,
 sub_category,
 content,
 embedding
FROM
 apparels;

这应该会返回查询中示例文本的嵌入向量,该向量看起来像一个浮点数数组,如下所示:

c69c08d085389f74.png

注意:在免费层级下新创建的 Google Cloud 项目在向嵌入模型发出的每秒嵌入请求数方面可能会遇到配额问题。我们建议您使用 ID 过滤查询,然后在生成嵌入时选择 1-5 条记录等。

8. 执行矢量搜索

现在,表格、数据和嵌入都已准备就绪,接下来我们将针对用户搜索文本执行实时向量搜索。

假设用户询问:

“I want womens tops, pink casual only pure cotton.”

您可以通过运行以下查询找到匹配项:

SELECT
id,
category,
sub_category,
content,
pdt_desc AS description
FROM
apparels
ORDER BY
embedding <=> embedding('text-embedding-004',
  'I want womens tops, pink casual only pure cotton.')::vector
LIMIT
5;

我们来详细看看这个查询:

在此查询中,

  1. 用户的搜索文本为:“我想要粉色休闲纯棉女上衣。”
  2. 我们将在 embedding() 方法中使用模型 text-embedding-004 将其转换为嵌入。与上一步(我们将嵌入函数应用于表格中的所有项)相比,此步骤应该会显得熟悉。
  3. <=>”表示使用余弦相似度距离方法。您可以在 pgvector 文档中找到所有可用的相似度衡量方法。
  4. 我们将嵌入方法的结果转换为向量类型,以使其与存储在数据库中的向量兼容。
  5. LIMIT 5 表示我们要提取搜索文本的 5 个最近邻。

结果如下所示:

4193a68737400535.png

如您在结果中所见,匹配项与搜索文本非常接近。尝试更改颜色,看看结果如何变化。

9. 使用 LLM 进行匹配验证

在继续创建服务以针对应用返回最匹配的回答之前,我们先使用生成式 AI 模型来验证这些潜在回答是否确实相关且可以安全地与用户分享。

确保已为实例设置 Gemini

首先,检查您的集群和实例是否已启用 Google ML 集成。在 AlloyDB Studio 中,输入以下命令:

show google_ml_integration.enable_model_support;

如果该值显示为“开启”,您可以跳过接下来的 2 个步骤,直接设置 AlloyDB 与 Vertex AI 模型的集成。

  1. 前往 AlloyDB 集群的主实例,然后点击“修改主实例”

456ffdf292d3c0e0.png

  1. 前往“高级配置选项”中的“标志”部分。并确保将 google_ml_integration.enable_model_support flag 设置为“on”,如下所示:

6a59351fcd2a9d35.png

如果未设置为“开启”,请将其设置为“开启”,然后点击更新实例按钮。此步骤需要几分钟时间。

AlloyDB 与 Vertex AI 模型集成

现在,您可以连接到 AlloyDB Studio,并运行以下 DML 语句,以便在 AlloyDB 中设置 Gemini 模型访问权限,并在指示位置使用您的项目 ID。在运行该命令之前,您可能会收到语法错误警告,但该命令应该可以正常运行。

首先,我们创建 Gemini 1.5 模型连接,如下所示。请务必将以下命令中的 $PROJECT_ID 替换为您的 Google Cloud 项目 ID。

CALL
 google_ml.create_model( model_id => 'gemini-1.5',
   model_request_url => 'https://us-central1-aiplatform.googleapis.com/v1/projects/$PROJECT_ID/locations/us-central1/publishers/google/models/gemini-1.5-pro:streamGenerateContent',
   model_provider => 'google',
   model_auth_type => 'alloydb_service_agent_iam');

您可以在 AlloyDB Studio 中通过以下命令查看已配置为可访问的模型:

select model_id,model_type from google_ml.model_info_view;        

最后,我们需要向数据库用户授予执行 ml_predict_row 函数的权限,以便通过 Google Vertex AI 模型运行预测。运行以下命令:

GRANT EXECUTE ON FUNCTION ml_predict_row to postgres;

注意:如果您使用的是现有的 Google Cloud 项目和一段时间前创建的现有 AlloyDB 集群/实例,则可能需要删除对 gemini-1.5 模型的旧引用,并使用上述 CALL 语句重新创建,然后再次对函数 ml_predict_row 运行 grant execute,以防在接下来的 gemini-1.5 调用中遇到问题。

评估回答

虽然我们最终会在下一部分中使用一个大型查询来确保查询的响应合理,但该查询可能很难理解。我们现在来看看这些部分,并了解它们在几分钟内如何组合在一起。

  1. 首先,我们会向数据库发送请求,以获取与用户查询最相符的 5 个结果。为了简单起见,我们将对查询进行硬编码,但别担心,我们稍后会将其插入查询中。我们将添加 apparels 表中的商品说明,并添加两个新字段,一个用于将说明与索引组合,另一个用于将说明与原始请求组合。所有这些数据都存储在名为 xyz(只是一个临时表名称)的表中。
CREATE TABLE
 xyz AS
SELECT
 id || ' - ' || pdt_desc AS literature,
 pdt_desc AS content,
 'I want womens tops, pink casual only pure cotton.' AS  user_text
FROM
 apparels
ORDER BY
 embedding <=> embedding('text-embedding-004',
   'I want womens tops, pink casual only pure cotton.')::vector
LIMIT
 5;

此查询的输出将是与用户查询最相似的 5 行。新表 xyz 将包含 5 行,每行包含以下列:

  • literature
  • content
  • user_text
  1. 为了确定回答的有效性,我们将使用一个复杂的查询,并在其中说明如何评估回答。它会在查询中使用 xyz 表中的 user_textcontent
"Read this user search text: ', user_text, 
' Compare it against the product inventory data set: ', content, 
' Return a response with 3 values: 1) MATCH: if the 2 contexts are at least 85% matching or not: YES or NO 2) PERCENTAGE: percentage of match, make sure that this percentage is accurate 3) DIFFERENCE: A clear short easy description of the difference between the 2 products. Remember if the user search text says that some attribute should not be there, and the record has it, it should be a NO match."
  1. 然后,我们将使用该查询检查 xyz 表中的回答“好坏”。
CREATE TABLE
  x AS
SELECT
  json_array_elements( google_ml.predict_row( model_id => 'gemini-1.5',
      request_body => CONCAT('{
 "contents": [ 
        { "role": "user", 
          "parts": 
             [ { "text": "Read this user search text: ', user_text, ' Compare it against the product inventory data set: ', content, ' Return a response with 3 values: 1) MATCH: if the 2 contexts are at least 85% matching or not: YES or NO 2) PERCENTAGE: percentage of match, make sure that this percentage is accurate 3) DIFFERENCE: A clear short easy description of the difference between the 2 products. Remember if the user search text says that some attribute should not be there, and the record has it, it should be a NO match." 
             } ]
         } 
] }'
)::json))-> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'
AS LLM_RESPONSE
FROM
    xyz;
  1. predict_row 会以 JSON 格式返回结果。代码“-> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'"”用于从该 JSON 中提取实际文本。如需查看实际返回的 JSON,您可以移除此代码。
  2. 最后,如需获取 LLM 字段,您只需从 x 表中提取该字段即可:
SELECT 
LLM_RESPONSE 
FROM 
        x;
  1. 这可以组合成单个 Next 查询,如下所示。

如果您已运行上述查询来检查中间结果,则需要先从 AlloyDB 数据库中删除/移除 xyz 和 x 表,然后才能运行此查询。

SELECT
 LLM_RESPONSE
FROM (
 SELECT
 json_array_elements( google_ml.predict_row( model_id => 'gemini-1.5',
     request_body => CONCAT('{
     "contents": [
       { "role": "user",
         "parts":
            [ { "text": "Read this user search text: ', user_text, ' Compare it against the product inventory data set: ', content, ' Return a response with 3 values: 1) MATCH: if the 2 contexts are at least 85% matching or not: YES or NO 2) PERCENTAGE: percentage of match, make sure that this percentage is accurate 3) DIFFERENCE: A clear short easy description of the difference between the 2 products. Remember if the user search text says that some attribute should not be there, and the record has it, it should be a NO match."
            } ]
        }
] }'
)::json))-> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'
AS LLM_RESPONSE
   FROM (
         SELECT
           id || ' - ' || pdt_desc AS literature,
           pdt_desc AS content,
         'I want womens tops, pink casual only pure cotton.' user_text
         FROM
           apparels
         ORDER BY
             embedding <=> embedding('text-embedding-004',
             'I want womens tops, pink casual only pure cotton.')::vector
         LIMIT
           5 ) AS xyz ) AS X;

虽然这可能仍然令人望而却步,但希望您能对此有更深入的了解。结果会显示是否有匹配项、匹配百分比,以及对分级的某些说明。

请注意,Gemini 模型默认处于流式传输状态,因此实际回答会分布在多行中:14e74d71293b7b9.png

10. 将应用移植到 Web 平台

准备好将此应用移植到 Web 平台了吗?请按照以下步骤使用 Cloud Run 函数将此知识引擎服务器化:

  1. 前往 Google Cloud 控制台中的 Cloud Run 函数页面,创建新的 Cloud Run 函数,或使用以下链接:https://console.cloud.google.com/functions/add
  2. 将“环境”选择为“Cloud Run 函数”。提供函数名称“retail-engine”,并选择“us-central1”作为区域。将“Authentication”(身份验证)设置为“Allow unauthenticated invocations”(允许未经身份验证的调用),然后点击 NEXT(下一步)。选择 Java 17 作为运行时,并为源代码选择内嵌编辑器
  3. 默认情况下,它会将入口点设置为“gcfv2.HelloHttpFunction”。将 Cloud Run 函数的 HelloHttpFunction.javapom.xml 中的占位符代码分别替换为 Java 文件XML 中的代码。
  4. 请务必在 Java 文件中将 $PROJECT_ID 占位符和 AlloyDB 连接凭据更改为您的值。AlloyDB 凭据是我们在本 Codelab 开始时使用的凭据。如果您使用了其他值,请在 Java 文件中进行相应修改。
  5. 点击部署

部署完成后,为了允许 Cloud Functions 函数访问 AlloyDB 数据库实例,我们将创建 VPC 连接器

重要步骤

开始部署后,您应该可以在 Google Cloud Run Functions 控制台中看到这些函数。搜索新创建的函数 (retail-engine),点击该函数,然后点击修改并更改以下内容:

  1. 前往“运行时、构建、连接和安全设置”
  2. 将超时时间延长至 180 秒
  3. 前往“连接”标签页:

4e83ec8a339cda08.png

  1. 在“入站流量”设置下,确保已选择“允许所有流量”。
  2. 在“出站流量设置”下,点击“网络”下拉菜单,然后选择“添加新的 VPC 连接器”选项,并按照随即弹出的对话框中显示的说明操作:

8126ec78c343f199.png

  1. 为 VPC 连接器提供一个名称,并确保区域与您的实例相同。将“网络”值保留为默认值,并将“子网”设置为“自定义 IP 范围”,IP 范围为 10.8.0.0 或其他可用的类似 IP 地址。
  2. 展开“显示缩放设置”,并确保将配置设置为完全如下所示:

7baf980463a86a5c.png

  1. 点击“创建”,此连接器现在应已列在出站流量设置中。
  2. 选择新创建的连接器
  3. 选择将所有流量都通过此 VPC 连接器路由。
  4. 依次点击下一步部署

11. 测试应用

更新后的 Cloud Functions 函数部署完毕后,您应该会看到采用以下格式的端点:

https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/retail-engine

您可以通过在 Cloud Shell 终端中运行以下命令来对其进行测试:

gcloud functions call retail-engine --region=us-central1 --gen2 --data '{"search": "I want some kids clothes themed on Disney"}'

或者,您也可以按如下所示测试 Cloud Run 函数:

PROJECT_ID=$(gcloud config get-value project)

curl -X POST https://us-central1-$PROJECT_ID.cloudfunctions.net/retail-engine \
  -H 'Content-Type: application/json' \
  -d '{"search":"I want some kids clothes themed on Disney"}' \
  | jq .

结果如下:

88bc1ddfb5644a28.png

大功告成!只需这样,即可使用嵌入模型对 AlloyDB 数据执行相似性矢量搜索。

构建对话式智能体

代理将在本实验的第 2 部分构建。

12. 清理

如果您计划完成本实验的第 2 部分,请跳过此步骤,因为这会删除当前项目。

为避免系统因本博文中使用的资源向您的 Google Cloud 账号收取费用,请按照以下步骤操作:

  1. 在 Google Cloud 控制台中,前往管理资源页面。
  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关停以删除项目。

13. 恭喜

恭喜!您已成功使用 AlloyDB、pgvector 和向量搜索执行相似度搜索。通过结合 AlloyDBVertex AIVector Search 的功能,我们在让情境搜索和矢量搜索变得简单易用、高效且真正以意义为导向方面取得了长足进步。本实验的下一部分将介绍代理构建步骤。