使用 Cloud 数据库、无服务器运行时和开源集成的玩具店搜索应用

1. 概览

想象一下,无论是线上还是线下,您都能轻松找到心仪的礼物。您可以描述自己想要的玩具、上传玩具的照片,甚至设计自己的作品,商店会立即了解您的需求并提供量身定制的体验。这并非未来的幻想,而是由 AI、云技术和个性化电子商务愿景赋能的现实。

挑战:找到符合您想象的完美商品可能很难。宽泛搜索字词、关键字和模糊搜索往往效果不佳,浏览无尽的网页可能会令人厌烦,而想象中的内容与实际可用的内容之间的差异可能会令人沮丧。

解决方案:此演示应用直面这一挑战,利用 AI 的强大功能,通过情境搜索和根据搜索情境自定义生成匹配的产品,提供真正个性化且顺畅的体验。

构建内容

在本实验中,您将:

  1. 创建 AlloyDB 实例并加载 Toys 数据集
  2. 在 AlloyDB 中启用 pgvector 和生成式 AI 模型扩展程序
  3. 根据商品描述生成嵌入,并针对用户搜索文本执行实时余弦相似度搜索
  4. 调用 Gemini 2.0 Flash 来描述用户上传的图片,以便进行情境玩具搜索
  5. 调用 Imagen 3,根据用户兴趣自定义创建玩具
  6. 调用使用生成式 AI 数据库工具箱创建的价格预测工具,以获取自定义玩具的价格详情
  7. 在无服务器 Cloud Run Functions 中部署解决方案

要求

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

2. 架构

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

  1. 借助 AI 赋能的 RAG(检索增强生成)进行情境搜索

您可以这样理解:系统不仅会搜索“红色汽车”,还会理解以下内容:

“适合 3 岁男孩的小型车辆。”

以 AlloyDB 为基础:我们使用 AlloyDB(Google Cloud 的全托管式 PostgreSQL 兼容数据库)来存储玩具数据,包括说明、图片网址和其他相关属性。

用于语义搜索的 pgvector:pgvector 是一项 PostgreSQL 扩展程序,可用于存储玩具说明和用户搜索查询的向量嵌入。这样一来,系统就能进行语义搜索,也就是说,系统能够理解字词背后的含义,而不仅仅是确切的关键字。

用于衡量相关性的余弦相似度:我们使用余弦相似度来衡量用户搜索向量与玩具说明向量之间的语义相似度,从而显示相关性最高的结果。

ScaNN 索引,兼顾速度和准确性:为了确保快速获得准确的结果,尤其是在玩具库存不断增加的情况下,我们集成了 ScaNN(可扩缩的最近邻)索引。这显著提高了向量搜索的效率和召回率。

  1. 使用 Gemini 2.0 Flash 进行基于图片的搜索和理解

假设用户想上传一张熟悉的玩具的照片,然后使用这张照片进行搜索,而不是输入文本作为上下文。用户可以上传自己喜欢的玩具的图片,并获取相关功能。我们利用 Google 的 Gemini 2.0 Flash 模型(通过 LangChain4j 调用)来分析图片并提取相关背景信息,例如玩具的颜色、材质、类型和目标年龄段。

  1. 使用生成式 AI 打造您的梦幻玩具:Imagen 3

当用户决定创建自己的玩具时,真正的魔力就会显现出来。借助 Imagen 3,孩子们可以使用简单的文本提示来描述自己梦想中的玩具。想象一下,您只需说一句“我想要一只长着紫色翅膀、面容友善的毛绒龙”,就能在屏幕上看到这只龙!然后,Imagen 3 会生成自定义设计的玩具的图片,让用户清楚地看到自己的作品。

  1. 由智能体和数据库生成式 AI 工具箱提供支持的价格预测

我们推出了价格预测功能,可估算生产定制设计玩具的费用。该功能由包含复杂价格计算工具的代理提供支持。

生成式 AI 数据库工具箱:此智能体使用 Google 的全新开源工具“生成式 AI 数据库工具箱”与我们的数据库无缝集成。这样,智能体就可以访问有关材料成本、制造流程和其他相关因素的实时数据,从而提供准确的价格估算。如需了解详情,请点击此处

  1. Java Spring Boot、Gemini Code Assist 和 Cloud Run,可实现简化的开发和无服务器部署

整个应用均使用 Java Spring Boot(一种强大且可扩缩的框架)构建。我们在整个开发过程中(尤其是前端开发)都利用了 Gemini Code Assist,从而显著缩短了开发周期并提高了代码质量。我们使用 Cloud Run 部署整个应用,并使用 Cloud Run Functions 将数据库和智能体功能部署为独立的端点。

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. 在 Cloud Shell 终端中逐一运行以下命令,以启用所需的 API:

您还可以使用单个命令运行以下操作,但如果您是试用账号用户,尝试批量启用这些服务时可能会遇到配额问题。因此,每个命令都单独显示在一行中。

gcloud services enable alloydb.googleapis.com
gcloud services enable compute.googleapis.com 
gcloud services enable cloudresourcemanager.googleapis.com 
gcloud services enable servicenetworking.googleapis.com 
gcloud services enable run.googleapis.com 
gcloud services enable cloudbuild.googleapis.com 
gcloud services enable cloudfunctions.googleapis.com 
gcloud services enable aiplatform.googleapis.com

除了使用 gcloud 命令,您还可以通过控制台搜索每个产品或使用此链接

如果遗漏了任何 API,您始终可以在实施过程中启用它。

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

4. 数据库设置

在本实验中,我们将使用 AlloyDB 作为用于存储玩具店数据的数据库。它使用集群来保存所有资源,例如数据库和日志。每个集群都有一个主实例,可提供对数据的接入点。表将包含实际数据。

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

创建集群和实例

  1. 在 Cloud 控制台中浏览 AlloyDB 页面。在 Cloud 控制台中查找大多数页面的简单方法是使用控制台的搜索栏进行搜索。
  2. 在该页面中选择创建集群

f76ff480c8c889aa.png

  1. 您会看到如下所示的界面。使用以下值创建 集群和实例(如果您要从代码库克隆应用代码,请确保这些值匹配):
  • 集群 ID:“vector-cluster
  • 密码:“alloydb
  • 兼容 PostgreSQL 15
  • 区域:“us-central1
  • 网络:“default

538dba58908162fb.png

  1. 选择默认网络后,您会看到如下所示的界面。

选择设置连接
7939bbb6802a91bf.png

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

e06623e55195e16e.png

请务必将实例 ID 更改为

vector-instance

如果您无法更改,请务必在所有后续引用中更改实例 ID

请注意,创建集群大约需要 10 分钟。成功后,您应该会看到一个屏幕,其中显示了您刚刚创建的集群的概览。

5. 数据注入

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

847e35f1bf8a8bd8.png

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

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

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

91a86d9469d499c4.png

您将在编辑器窗口中输入 AlloyDB 命令,并根据需要使用“运行”“格式”和“清除”选项。

启用扩展程序

在构建此应用时,我们将使用扩展程序 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 toys ( id VARCHAR(25), name VARCHAR(25), description VARCHAR(20000), quantity INT, price FLOAT, image_url VARCHAR(200), text_embeddings vector(768)) ;

成功执行上述命令后,您应该能够在数据库中查看该表。

注入数据

在此实验中,我们在此 SQL 文件中提供了大约 72 条记录的测试数据。它包含 id, name, description, quantity, price, image_url 字段。其他字段将在稍后的实验中填写。

仅复制前 5 行/插入语句,然后将这些行粘贴到空白编辑器标签页中,并选择“运行”。如果您使用的不是试用结算账号,则可能可以复制所有 INSERT 语句并运行。

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

cfaa52b717f9aaed.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. 为上下文创建嵌入

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

考虑描述海边位置。它可能称为“水上”“海滨”“从客房步行到海边”“sur la mer”“на берегу океана”等。这些术语看起来各不相同,但它们的语义或机器学习术语中的嵌入向量应该非常接近。

现在,数据和上下文已准备就绪,我们将运行 SQL,以将商品描述的嵌入内容添加到字段 embedding 中的表中。您可以使用各种嵌入模型。我们使用的是 Vertex AI 中的 text-embedding-005。请务必在整个项目中使用相同的嵌入模型!

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

返回 AlloyDB Studio 标签页,然后输入以下 DML:

UPDATE toys set text_embeddings = embedding( 'text-embedding-005', description);

再次查看 toys 表,查看一些嵌入内容。请务必重新运行 SELECT 语句,以查看更改。

SELECT id, name, description, price, quantity, image_url, text_embeddings FROM toys;

此方法应返回玩具说明的嵌入向量(看起来像一个浮点数数组),如下所示:

7d32f7cd7204e1f3.png

注意:在免费层级下新建的 Google Cloud 项目在每秒允许的嵌入请求数方面可能会遇到配额问题。我们建议您使用 ID 的过滤查询,然后在生成嵌入内容时,有选择地选择 1-5 条记录,依此类推。

7. 执行向量搜索

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

假设用户提出以下问题:

I want a white plush teddy bear toy with a floral pattern。”

您可以运行以下查询来查找匹配项:

select * from toys
ORDER BY text_embeddings <=> CAST(embedding('text-embedding-005', 'I want a white plush teddy bear toy with a floral pattern') as vector(768))
LIMIT 2;

我们来详细了解一下此查询:

在此查询中,

  1. 用户的搜索文字为:“I want a white plush teddy bear toy with a floral pattern.
  2. 我们正在使用模型 text-embedding-005embedding() 方法中将其转换为嵌入。在最后一步中,我们已将嵌入函数应用于表中的所有项,因此这一步应该很熟悉。
  3. <=>”表示使用余弦相似度距离方法。您可以在 pgvector 的文档中找到所有可用的相似度衡量指标。
  4. 我们将嵌入方法的结果转换为向量类型,以使其与存储在数据库中的向量兼容。
  5. LIMIT 5 表示我们希望提取搜索文本的 5 个最近邻项。

结果如下所示:

fa7f0fc3a4c68804.png

从搜索结果中可以看出,匹配项与搜索文本非常接近。尝试更改文字,看看结果有何变化。

重要提示

现在,假设我们想使用 ScaNN 索引来提高此向量搜索结果的性能(查询时间)、效率和召回率。请阅读这篇博客中的步骤,比较有无索引时结果的差异。

可选步骤:使用 ScaNN 索引提高效率和召回率

如果您的记录数少于 100,请忽略此步骤。

为方便起见,这里仅列出索引创建步骤:

  1. 由于我们已经创建了集群、实例、上下文和嵌入,因此只需使用以下语句安装 ScaNN 扩展程序:
CREATE EXTENSION IF NOT EXISTS alloydb_scann;
  1. 接下来,我们将创建索引 (ScaNN):
CREATE INDEX toysearch_index ON toys
USING scann (text_embeddings cosine)
WITH (num_leaves=9);

在上述 DDL 中,apparel_index 是索引的名称

“toys”是我的表格

“scann”是索引方法

“embedding”是表格中我想编入索引的列

“cosine”是我要用于索引的距离方法

“8”是要应用于此索引的分区数量。设置为介于 1 到 1048576 之间的任意值。如需详细了解如何确定此值,请参阅对 ScaNN 索引进行调优

我使用了数据点数量的平方根,如 ScaNN 仓库中所建议的那样(在分区时,num_leaves 应大致为数据点数量的平方根)。

  1. 使用以下查询检查索引是否已创建:
SELECT * FROM pg_stat_ann_indexes;
  1. 使用与不含索引时相同的查询执行 Vector Search:
select * from toys
ORDER BY text_embeddings <=> CAST(embedding('text-embedding-005', 'I want a white plush teddy bear toy with a floral pattern') as vector(768))
LIMIT 5;

上述查询与我们在实验的第 8 步中使用的查询相同。不过,现在我们已将该字段编入索引。

  1. 通过以下方式测试简单搜索查询(使用和不使用索引):

此使用情形只有 72 条记录,因此索引实际上并未生效。对于在其他使用情形下进行的测试,结果如下:

对已编入索引的嵌入数据执行相同的 Vector Search 查询,可获得优质的搜索结果并提高效率。使用索引后,效率大幅提高(就执行时间而言:不使用 ScaNN 时为 10.37 毫秒,使用 ScaNN 时为 0.87 毫秒)。如需详细了解此主题,请参阅这篇博文

8. 使用 LLM 进行匹配验证

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

确保实例已为 Gemini 完成设置

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

show google_ml_integration.enable_model_support;

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

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

cb76b934ba3735bd.png

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

6a59351fcd2a9d35.png

如果未设置为“on”,请将其设置为“on”,然后点击 UPDATE INSTANCE 按钮。此步骤需要几分钟时间。

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 语句重新创建,并再次运行 grant execute on function ml_predict_row,以免在即将到来的 gemini-1.5 调用中遇到问题。

评估回答

虽然我们最终会在下一部分中使用一个大型查询来确保查询的响应合理,但该查询可能难以理解。我们现在来看看这些组件,稍后会介绍它们如何协同工作。

  1. 首先,我们会向数据库发送请求,以获取与用户查询最接近的 10 个匹配项。
  2. 为了确定回答的有效性,我们将使用一个外部查询来解释如何评估回答。它使用内部表的 recommended_text 字段(即搜索文本)和 content(即玩具说明字段)作为查询的一部分。
  3. 然后,我们将使用该指标来评估返回的回答的“优劣”。
  4. predict_row 以 JSON 格式返回结果。代码“-> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'"”用于从该 JSON 中提取实际文本。如需查看返回的实际 JSON,您可以移除此代码。
  5. 最后,为了获得 LLM 回答,我们使用 REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') 提取回答
SELECT id,
       name,
       content,
       quantity,
       price,
       image_url,
       recommended_text,
       REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') AS gemini_validation
  FROM (SELECT id,
               name,
               content,
               quantity,
               price,
               image_url,
               recommended_text,
               CAST(ARRAY_AGG(LLM_RESPONSE) AS TEXT) AS gemini_validation
          FROM (SELECT id,
                       name,
                       content,
                       quantity,
                       price,
                       image_url,
                       recommended_text,
                       json_array_elements(google_ml.predict_row(model_id => 'gemini-1.5',
                                                                   request_body => CONCAT('{ "contents": [ { "role": "user", "parts": [ { "text": "User wants to buy a toy and this is the description of the toy they wish to buy: ',                                                                                              recommended_text,                                                                                              '. Check if the following product items from the inventory are close enough to really, contextually match the user description. Here are the items: ',                                                                                         content,                                                                                         '. Return a ONE-LINE response with 3 values: 1) MATCH: if the 2 contexts are reasonably matching in terms of any of the color or color family specified in the list, approximate style match with any of the styles mentioned in the user search text: This should be a simple YES or NO. Choose NO only if it is completely irrelevant to users search criteria. 2) PERCENTAGE: percentage of match, make sure that this percentage is accurate 3) DIFFERENCE: A clear one-line 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' :: TEXT AS LLM_RESPONSE
                  FROM (SELECT id,
                               name,
                               description AS content,
                               quantity,
                               price,
                               image_url,
                               'Pink panther standing' AS recommended_text
                          FROM toys
                         ORDER BY text_embeddings <=> embedding('text-embedding-005',
                                                                'Pink panther standing')::VECTOR
                         LIMIT 1) AS xyz) AS X
         GROUP BY id,
                  name,
                  content,
                  quantity,
                  price,
                  image_url,
                  recommended_text) AS final_matches

-- WHERE REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') LIKE '%MATCH%:%YES%';

虽然这看起来可能仍然令人望而却步,但希望您能从中获得更多启发。结果会显示是否存在匹配项、匹配百分比以及分级说明。

请注意,Gemini 模型默认开启了流式传输,因此实际回答会分布在多行中:

c2b006aeb3f3a2fc.png

9. 将玩具搜索应用无服务器地迁移到云端

准备好将此应用迁移到 Web 上了吗?请按以下步骤操作,使用 Cloud Run Functions 将此知识引擎设为无服务器:

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

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

重要步骤

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

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

4e83ec8a339cda08.png

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

8126ec78c343f199.png

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

7baf980463a86a5c.png

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

10. 测试 Cloud Run 函数

部署更新后的 Cloud Functions 函数后,您应该会看到生成的端点。复制该内容并替换以下命令中的相应部分:

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

PROJECT_ID=$(gcloud config get-value project)

curl -X POST <<YOUR_ENDPOINT>> \
  -H 'Content-Type: application/json' \
  -d '{"search":"I want a standing pink panther toy"}' \
  | jq .

结果:

23861e9091565a64.png

大功告成!使用 AlloyDB 数据上的嵌入模型执行相似度向量搜索就是这么简单。

11. 构建 Web 应用客户端!

在本部分中,我们将构建一个 Web 应用,供用户进行互动,并根据文本、图片查找匹配的玩具,甚至根据自己的需求创建新玩具。由于应用已构建完成,您可以按照以下步骤将其复制到 IDE 中,并让应用正常运行。

  1. 由于我们使用 Gemini 2.0 Flash 来描述用户可能上传的图片,以便找到匹配的玩具,因此我们需要获取此应用的 API 密钥。为此,请前往 https://aistudio.google.com/apikey,获取您正在实现此应用的有效 Google Cloud 项目的 API 密钥,并将该密钥保存到某个位置:

ae2db169e6a94e4a.png

  1. 前往 Cloud Shell 终端
  2. 使用以下命令克隆代码库:
git clone https://github.com/AbiramiSukumaran/toysearch

cd toysearch
  1. 克隆代码库后,您应该能够通过 Cloud Shell 编辑器访问项目。
  2. 您需要从克隆的项目中删除“get-toys-alloydb”和“toolbox-toys”文件夹,因为这两个文件夹是 Cloud Run Functions 代码,您可以在需要时从代码库中引用它们。
  3. 前往 web 文件夹中的 GenerateToy.java,找到以下行移除它,因为允许成人内容可能需要特殊权限,而某些试用结算账号可能没有此权限:

paramsMap.put("personGeneration", "allow_adult");

  1. 在构建和部署应用之前,请确保已设置所有必要的环境变量。前往 Cloud Shell 终端并执行以下操作:
PROJECT_ID=$(gcloud config get-value project)

export PROJECT_ID=$PROJECT_ID

export GOOGLE_API_KEY=<YOUR API KEY that you saved>
  1. 在本地构建并运行应用:

确保您位于项目目录中,然后运行以下命令:

mvn package

mvn spring-boot:run 
  1. 在 Cloud Run 上部署
gcloud run deploy --source .

12. 了解生成式 AI 的详细信息

您无需执行任何操作。仅供参考:

现在您已经获得了要部署的应用,请花点时间了解我们是如何实现搜索(文本和图片)和生成功能的。

  1. 基于用户文本的 Vector Search:

我们在“将 Vector Search 搜索应用部署为 Web 应用”部分中部署的 Cloud Run 函数已解决此问题。

  1. 基于图片上传的 Vector Search:

假设用户想上传一张熟悉的玩具的照片,然后使用这张照片进行搜索,而不是输入文本作为上下文。用户可以上传自己喜欢的玩具的图片,并获取相关功能。

我们利用 Google 的 Gemini 2.0 Flash 模型(通过 LangChain4j 调用)来分析图片并提取相关背景信息,例如玩具的颜色、材质、类型和目标年龄段。

只需 5 个步骤,我们就能使用开源框架,通过调用大语言模型,将用户多模态数据输入转换为匹配的结果。探索以下问题:

package cloudcode.helloworld.web;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import java.util.Base64;
import java.util.Optional;

public class GeminiCall {
  public String imageToBase64String(byte[] imageBytes) {
    String base64Img = Base64.getEncoder().encodeToString(imageBytes);
    return base64Img;
  }

  public String callGemini(String base64ImgWithPrefix) throws Exception {
    String searchText = "";

    // 1. Remove the prefix
    String base64Img = base64ImgWithPrefix.replace("data:image/jpeg;base64,", "");

    // 2. Decode base64 to bytes
    byte[] imageBytes = Base64.getDecoder().decode(base64Img);
    String image = imageToBase64String(imageBytes);

    // 3. Get API key from environment variable
        String apiKey = Optional.ofNullable(System.getenv("GOOGLE_API_KEY"))
                .orElseThrow(() -> new IllegalArgumentException("GOOGLE_API_KEY environment variable not set"));

    // 4. Invoke Gemini 2.0
    ChatLanguageModel gemini = GoogleAiGeminiChatModel.builder()
        .apiKey(apiKey)
        .modelName("gemini-2.0-flash-001")
        .build();

    Response<AiMessage> response = gemini.generate(
        UserMessage.from(
            ImageContent.from(image, "image/jpeg"),
            TextContent.from(
                "The picture has a toy in it. Describe the toy in the image in one line. Do not add any prefix or title to your description. Just describe that toy that you see in the image in one line, do not describe the surroundings and other objects around the toy in the image. If you do not see any toy in the image, send  response stating that no toy is found in the input image.")));
   
    // 5. Get the text from the response and send it back to the controller
    searchText = response.content().text().trim();
    System.out.println("searchText inside Geminicall: " + searchText);
    return searchText;
  }
}
  1. 了解我们如何使用 Imagen 3 和生成式 AI 根据用户要求打造定制玩具。

然后,Imagen 3 会生成自定义设计的玩具的图片,让用户清楚地看到自己的作品。我们仅用了 5 个步骤就完成了这项工作:

// Generate an image using a text prompt using an Imagen model
    public String generateImage(String projectId, String location, String prompt)
        throws ApiException, IOException {
      final String endpoint = String.format("%s-aiplatform.googleapis.com:443", location);
      PredictionServiceSettings predictionServiceSettings =
      PredictionServiceSettings.newBuilder().setEndpoint(endpoint).build();
     
      // 1. Set up the context and prompt
      String context = "Generate a photo-realistic image of a toy described in the following input text from the user. Make sure you adhere to all the little details and requirements mentioned in the prompt. Ensure that the user is only describing a toy. If it is anything unrelated to a toy, politely decline the request stating that the request is inappropriate for the current context. ";
      prompt = context + prompt;

      // 2. Initialize a client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      try (PredictionServiceClient predictionServiceClient =
          PredictionServiceClient.create(predictionServiceSettings)) {
 
      // 3. Invoke Imagen 3
        final EndpointName endpointName =
            EndpointName.ofProjectLocationPublisherModelName(
                projectId, location, "google", "imagen-3.0-generate-001"); //"imagegeneration@006"; imagen-3.0-generate-001
        Map<String, Object> instancesMap = new HashMap<>();
        instancesMap.put("prompt", prompt);
        Value instances = mapToValue(instancesMap);
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("sampleCount", 1);
        paramsMap.put("aspectRatio", "1:1");
        paramsMap.put("safetyFilterLevel", "block_few");
        paramsMap.put("personGeneration", "allow_adult");
        paramsMap.put("guidanceScale", 21);
        paramsMap.put("imagenControlScale", 0.95); //Setting imagenControlScale
        Value parameters = mapToValue(paramsMap);
       
      // 4. Get prediction response image
        PredictResponse predictResponse =
            predictionServiceClient.predict(
                endpointName, Collections.singletonList(instances), parameters);

      // 5. Return the Base64 Encoded String to the controller
        for (Value prediction : predictResponse.getPredictionsList()) {
          Map<String, Value> fieldsMap = prediction.getStructValue().getFieldsMap();
          if (fieldsMap.containsKey("bytesBase64Encoded")) {
            bytesBase64EncodedOuput = fieldsMap.get("bytesBase64Encoded").getStringValue();
        }
      }
      return bytesBase64EncodedOuput.toString();
    }
  }

价格预测

在上面的上一部分中,我们讨论了 Imagen 如何生成用户希望自行设计的玩具的图片。为了让用户能够购买,应用需要为该商品设置价格,我们采用了一种直观的逻辑来定义定制玩具的价格。逻辑是使用用户设计的玩具的前 5 个最接近的匹配玩具(在说明方面)的平均价格。

生成的玩具的价格预测是此应用的重要组成部分,我们采用了智能体方法来生成此预测。推出生成式 AI 数据库工具包。

13. 生成式 AI 数据库工具箱

生成式 AI 数据库工具箱是 Google 提供的一款开源服务器,可让您更轻松地构建用于与数据库交互的生成式 AI 工具。它通过处理连接池、身份验证等复杂问题,让您能够更轻松、更快速、更安全地开发工具。它可帮助您构建生成式 AI 工具,让智能体能够访问数据库中的数据。

您需要按照以下步骤操作,才能设置此功能,从而准备好工具并使我们的应用成为智能体应用:指向工具箱 Codelab 的链接

现在,您的应用可以使用此已部署的 Cloud Run 函数端点来填充价格,以及为定制玩具图片生成 Imagen 结果。

14. 测试 Web 应用

现在,应用的所有组件都已构建并部署完毕,可以开始在云端提供服务了。针对所有场景测试您的应用。以下视频链接可让您了解预期效果:

https://www.youtube.com/shorts/ZMqUAWsghYQ

着陆页如下所示:

241db19e7176e93e.png

15. 清理

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

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

16. 恭喜

恭喜!您已成功使用 AlloyDB、pgvector、Imagen 和 Gemini 2.0 执行了 Toystore 上下文搜索和生成,同时利用开源库构建了强大的集成。通过结合 AlloyDBVertex AIVector Search 的功能,我们在实现情境搜索和矢量搜索方面取得了巨大进步,让搜索变得更易于使用、更高效,并且真正以含义为导向。