Codelab - 使用 Firestore、Vector Search、Langchain 和 Gemini 构建上下文感知型瑜伽姿势推荐应用(Python 版)

1. 简介

在此 Codelab 中,您将构建一个使用向量搜索来推荐瑜伽姿势的应用。

在此 Codelab 中,您将采用以下分步方法:

  1. 利用现有的瑜伽姿势 Hugging Face 数据集(JSON 格式)。
  2. 使用额外的字段说明增强数据集,该说明使用 Gemini 为每个姿势生成说明。
  3. 使用 Langchain 创建文档,使用 Firestore Langchain 集成在 Firestore 中创建集合和嵌入内容。
  4. 在 Firestore 中创建复合索引,以实现向量搜索。
  5. 在 Flask 应用中使用向量搜索,将所有内容整合在一起,如下所示:

84e1cbf29cbaeedc.png

实践内容

  • 设计、构建和部署一个 Web 应用,该应用利用 Vector Search 来推荐瑜伽姿势。

学习内容

  • 如何使用 Gemini 生成文本内容,以及在本 Codelab 的背景下,如何生成瑜伽体式的说明
  • 如何使用 Langchain Document Loader for Firestore 将增强型数据集中的记录从 Hugging Face 加载到 Firestore 中,同时加载向量嵌入
  • 如何使用 Langchain Vector Store for Firestore 根据自然语言查询搜索数据
  • 如何使用 Google Cloud Text to Speech API 生成音频内容

所需条件

  • Chrome 网络浏览器
  • Gmail 账号
  • 启用了结算功能的 Cloud 项目

此 Codelab 专为各种水平的开发者(包括新手)设计,并在示例应用中使用 Python。不过,您无需具备 Python 知识即可理解所介绍的概念。

2. 准备工作

创建项目

  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 firestore.googleapis.com \
                       compute.googleapis.com \
                       cloudresourcemanager.googleapis.com \
                       servicenetworking.googleapis.com \
                       run.googleapis.com \
                       cloudbuild.googleapis.com \
                       cloudfunctions.googleapis.com \
                       aiplatform.googleapis.com \
                       texttospeech.googleapis.com

成功执行该命令后,您应该会看到类似如下所示的消息:

Operation "operations/..." finished successfully.

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

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

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

克隆代码库并设置环境设置

下一步是克隆我们将在本 Codelab 的其余部分中引用的示例代码库。假设您位于 Cloud Shell 中,请在主目录中运行以下命令:

git clone https://github.com/rominirani/yoga-poses-recommender-python

如需启动编辑器,请点击 Cloud Shell 窗口工具栏上的“打开编辑器”。点击左上角的菜单栏,然后依次选择“文件”→“打开文件夹”,如下所示:

66221fd0d0e5202f.png

选择 yoga-poses-recommender-python 文件夹,您应该会看到该文件夹打开,其中包含以下文件,如下所示:

44699efc7fb1b911.png

现在,我们需要设置将要使用的环境变量。点击 config.template.yaml 文件,您应该会看到如下所示的内容:

project_id: your-project-id
location: us-central1
gemini_model_name: gemini-1.5-flash-002
embedding_model_name: text-embedding-004
image_generation_model_name: imagen-3.0-fast-generate-002
database: (default)
collection: poses
test_collection: test-poses
top_k: "3"

请根据您在创建 Google Cloud 项目和 Firestore 数据库区域时选择的值,更新 project_idlocation 的值。理想情况下,我们希望 Google Cloud 项目和 Firestore 数据库的 location 值相同,例如 us-central1

在本 Codelab 中,我们将使用预配置的值(当然,project_idlocation 除外,您需要根据自己的配置设置这两个值)。

请将此文件另存为 config.yaml,并将其保存在 config.template.yaml 文件所在的文件夹中。

现在的最后一步是创建一个 Python 环境,我们将在本地使用该环境,并且所有 Python 依赖项都已为我们设置好。请查看包含相关详细信息的 pyproject.toml 文件,其内容如下所示:

dependencies = [
    "datasets>=3.2.0",
    "flask>=3.1.0",
    "google-cloud-aiplatform>=1.78.0",
    "google-cloud-texttospeech>=2.24.0",
    "langchain-community>=0.3.15",
    "langchain-core>=0.3.31",
    "langchain-google-community>=2.0.4",
    "langchain-google-firestore>=0.5.0",
    "langchain-google-vertexai>=2.0.7",
    "pydantic-settings>=2.7.1",
    "pyyaml>=6.0.2",
    "tenacity>=9.0.0",
]

这些依赖项已在 requirements.txt 中锁定版本。总而言之,我们需要创建一个虚拟 Python 环境,并将 requirements.txt 中的 Python 软件包依赖项安装到该虚拟环境中。.为此,请在 Cloud Shell IDE 中前往 Command Palette (Ctrl+Shift+P),然后输入 Python: Create Environment。按照接下来几个步骤选择 Virtual Environment(venv)Python 3.x interpreterrequirements.txt 文件。

创建环境后,我们需要使用以下命令激活已创建的环境

source .venv/bin/activate

您应该会在控制台中看到 (.venv)。例如 -> (.venv) yourusername@cloudshell:

太棒了!现在,我们已准备就绪,可以继续执行设置 Firestore 数据库的任务了。

3. 设置 Firestore

Cloud Firestore 是一种全托管式无服务器文档数据库,我们将使用它作为应用数据的后端。Cloud Firestore 中的数据以文档集合的形式进行结构化。

Firestore 数据库初始化

在 Cloud 控制台中,访问 Firestore 页面

如果您之前未在项目中初始化 Firestore 数据库,请点击 Create Database 创建 default 数据库。在创建数据库期间,请使用以下值:

  • Firestore 模式:Native.
  • 选择Region作为“位置类型”,然后为相应区域选择us-central1位置。
  • 对于安全规则,请选择 Test rules
  • 创建数据库。

61d0277510803c8d.png

在下一部分中,我们将为在默认 Firestore 数据库中创建名为 poses 的集合奠定基础。此集合将包含示例数据(文档)或瑜伽姿势信息,我们随后将在应用中使用这些数据。

这样就完成了设置 Firestore 数据库的部分。

4. 准备瑜伽姿势数据集

我们的首要任务是准备将用于应用的瑜伽姿势数据集。我们将从现有的 Hugging Face 数据集开始,然后使用其他信息对其进行增强。

不妨查看 Hugging Face 瑜伽姿势数据集。请注意,虽然此 Codelab 使用的是其中一个数据集,但实际上您可以使用任何其他数据集,并按照演示的相同技巧来增强数据集。

298cfae7f23e4bef.png

如果我们前往 Files and versions 部分,就可以获取所有姿势的 JSON 数据文件。

3fe6e55abdc032ec.png

我们已下载 yoga_poses.json 并将该文件提供给您。该文件名为 yoga_poses_alldata.json,位于 /data 文件夹中。

前往 Cloud Shell 编辑器中的 data/yoga_poses.json 文件,查看 JSON 对象列表,其中每个 JSON 对象都代表一个瑜伽姿势。我们总共有 3 条记录,下面显示了一条示例记录:

{
   "name": "Big Toe Pose",
   "sanskrit_name": "Padangusthasana",
   "photo_url": "https://pocketyoga.com/assets/images/full/ForwardBendBigToe.png",
   "expertise_level": "Beginner",
   "pose_type": ["Standing", "Forward Bend"]
 }

现在,我们正好可以介绍 Gemini,以及如何使用默认模型本身为其生成 description 字段。

在 Cloud Shell 编辑器中,前往 generate-descriptions.py 文件。此文件的内容如下所示:

import json
import time
import logging
import vertexai
from langchain_google_vertexai import VertexAI
from tenacity import retry, stop_after_attempt, wait_exponential
from settings import get_settings

settings = get_settings()
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
# Initialize Vertex AI SDK
vertexai.init(project=settings.project_id, location=settings.location)
logging.info("Done Initializing Vertex AI SDK")


@retry(
    stop=stop_after_attempt(5),
    wait=wait_exponential(multiplier=1, min=4, max=10),
)
def generate_description(pose_name, sanskrit_name, expertise_level, pose_types):
    """Generates a description for a yoga pose using the Gemini API."""

    prompt = f"""
    Generate a concise description (max 50 words) for the yoga pose: {pose_name}
    Also known as: {sanskrit_name}
    Expertise Level: {expertise_level}
    Pose Type: {", ".join(pose_types)}

    Include key benefits and any important alignment cues.
    """
    try:
        model = VertexAI(model_name=settings.gemini_model_name, verbose=True)
        response = model.invoke(prompt)
        return response
    except Exception as e:
        logging.info(f"Error generating description for {pose_name}: {e}")
        return ""


def add_descriptions_to_json(input_file, output_file):
    """Loads JSON data, adds descriptions, and saves the updated data."""

    with open(input_file, "r") as f:
        yoga_poses = json.load(f)

    total_poses = len(yoga_poses)
    processed_count = 0

    for pose in yoga_poses:
        if pose["name"] != " Pose":
            start_time = time.time()  # Record start time
            pose["description"] = generate_description(
                pose["name"],
                pose["sanskrit_name"],
                pose["expertise_level"],
                pose["pose_type"],
            )
            end_time = time.time()  # Record end time

            processed_count += 1
            end_time = time.time()  # Record end time
            time_taken = end_time - start_time
            logging.info(
                f"Processed: {processed_count}/{total_poses} - {pose['name']} ({time_taken:.2f} seconds)"
            )

        else:
            pose["description"] = ""
            processed_count += 1
            logging.info(
                f"Processed: {processed_count}/{total_poses} - {pose['name']} ({time_taken:.2f} seconds)"
            )
        # Adding a delay to avoid rate limit
        time.sleep(30)

    with open(output_file, "w") as f:
        json.dump(yoga_poses, f, indent=2)


def main():
    # File paths
    input_file = "./data/yoga_poses.json"
    output_file = "./data/yoga_poses_with_descriptions.json"

    # Add descriptions and save the updated JSON
    add_descriptions_to_json(input_file, output_file)


if __name__ == "__main__":
    main()

此应用将向每个瑜伽姿势 JSON 记录添加一个新的 description 字段。它将通过调用 Gemini 模型来获取说明,我们会向该模型提供必要的提示。该字段会添加到 JSON 文件中,并且新文件会写入到 data/yoga_poses_with_descriptions.json 文件中。

下面我们来了解一下主要步骤:

  1. main() 函数中,您会发现它调用了 add_descriptions_to_json 函数,并提供了输入文件和预期输出文件。
  2. add_descriptions_to_json 函数会针对每个 JSON 记录(即瑜伽姿势信息)执行以下操作:
  3. 它会提取 pose_namesanskrit_nameexpertise_levelpose_types
  4. 它会调用 generate_description 函数来构建提示,然后调用 Langchain VertexAI 模型类来获取回答文本。
  5. 然后,将此响应文本添加到 JSON 对象中。
  6. 然后,更新后的 JSON 对象列表会被写入目标文件。

让我们运行此应用。启动新的终端窗口 (Ctrl+Shift+C),然后输入以下命令:

python generate-descriptions.py

如果系统要求您进行任何授权,请继续操作并提供相应授权。

您会发现应用开始执行。我们在记录之间添加了 30 秒的延迟,以避免新 Google Cloud 账号可能存在的任何速率限制配额,因此请耐心等待。

正在进行的运行示例如下所示:

8e830d9ea9b6c60.png

通过 Gemini 调用增强所有 3 条记录后,系统会生成一个文件 data/yoga_poses_with_description.json。您可以查看一下。

现在,我们已经准备好数据文件,下一步是了解如何使用该文件填充 Firestore 数据库,以及如何生成嵌入内容。

5. 将数据导入 Firestore 并生成向量嵌入

我们已经有了 data/yoga_poses_with_description.json 文件,现在需要使用该文件填充 Firestore 数据库,更重要的是,为每条记录生成向量嵌入。稍后,当我们需要对这些向量嵌入与用户以自然语言提供的查询进行相似度搜索时,它们将非常有用。

我们将使用 Langchain Firestore 组件来实现上述流程。

具体操作步骤如下:

  1. 我们将把 JSON 对象列表转换为 Langchain Document 对象列表。每个文档都将包含两个属性:page_contentmetadata。元数据对象将包含具有 namedescriptionsanskrit_name 等属性的整个 JSON 对象。page_content 将是一个字符串文本,它是几个字段的串联。
  2. 获得 Document 对象列表后,我们将使用 FirestoreVectorStore Langchain 类,特别是 from_documents 方法,并提供此文档列表、集合名称(我们使用指向 test-posesTEST_COLLECTION 变量)、Vertex AI Embedding 类和 Firestore 连接详细信息(PROJECT_IDDATABASE 名称)。这会创建集合,还会为每个属性生成一个 embedding 字段。

import-data.py 的代码如下所示(为简洁起见,部分代码已截断):

... 

def create_langchain_documents(poses):
   """Creates a list of Langchain Documents from a list of poses."""
   documents = []
   for pose in poses:
       # Convert the pose to a string representation for page_content
       page_content = (
           f"name: {pose.get('name', '')}\n"
           f"description: {pose.get('description', '')}\n"
           f"sanskrit_name: {pose.get('sanskrit_name', '')}\n"
           f"expertise_level: {pose.get('expertise_level', 'N/A')}\n"
           f"pose_type: {pose.get('pose_type', 'N/A')}\n"
       ).strip()
       # The metadata will be the whole pose
       metadata = pose

       document = Document(page_content=page_content, metadata=metadata)
       documents.append(document)
   logging.info(f"Created {len(documents)} Langchain documents.")
   return documents

def main():
    all_poses = load_yoga_poses_data_from_local_file(
        "./data/yoga_poses_with_descriptions.json"
    )
    documents = create_langchain_documents(all_poses)
    logging.info(
        f"Successfully created langchain documents. Total documents: {len(documents)}"
    )

    embedding = VertexAIEmbeddings(
        model_name=settings.embedding_model_name,
        project=settings.project_id,
        location=settings.location,
    )

    client = firestore.Client(project=settings.project_id, database=settings.database)

    vector_store = FirestoreVectorStore.from_documents(
        client=client,
        collection=settings.test_collection,
        documents=documents,
        embedding=embedding,
    )
    logging.info("Added documents to the vector store.")


if __name__ == "__main__":
    main()

让我们运行此应用。启动新的终端窗口 (Ctrl+Shift+C),然后输入以下命令:

python import-data.py

如果一切顺利,您应该会看到类似于以下内容的消息:

2025-01-21 14:50:06,479 - INFO - Added documents to the vector store.

如需检查记录是否已成功插入以及是否已生成嵌入内容,请访问 Cloud 控制台中的 Firestore 页面

504cabdb99a222a5.png

点击(默认)数据库,系统应会显示 test-poses 集合以及该集合下的多个文档。每个文档都代表一个瑜伽姿势。

d0708499e403aebc.png

点击任意文档即可调查相应字段。除了我们导入的字段之外,您还会看到 embedding 字段,这是一个向量字段,已通过我们使用的 Langchain VertexAIEmbeddings 类自动为您生成,我们在其中提供了 text-embedding-004 Vertex AI 嵌入模型。

d67113e2dc63cd6b.png

现在,我们已将记录上传到 Firestore 数据库,并已添加嵌入,接下来可以进入下一步,了解如何在 Firestore 中执行向量相似度搜索。

6. 将完整的瑜伽姿势导入到 Firestore 数据库集合中

现在,我们将创建 poses 集合,其中包含 160 个瑜伽姿势的完整列表,我们已为此集合生成数据库导入文件,您可以直接导入。这样做是为了节省实验时间。生成包含说明和嵌入内容的数据库的过程与我们在上一部分中看到的过程相同。

按照以下步骤导入数据库:

  1. 使用以下 gsutil 命令在项目中创建存储分区。将以下命令中的 <PROJECT_ID> 变量替换为您的 Google Cloud 项目 ID。
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
  1. 现在,存储分区已创建完毕,我们需要将准备好的数据库导出内容复制到此存储分区中,然后才能将其导入到 Firebase 数据库中。使用以下命令:
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615  gs://<PROJECT_ID>-my-bucket

现在,我们已经准备好要导入的数据,接下来可以进入最后一步,将数据导入到我们创建的 Firebase 数据库 (default) 中。

  1. 使用以下 gcloud 命令:
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615

导入过程需要几秒钟,完成后,您可以访问 https://console.cloud.google.com/firestore/databases 来验证您的 Firestore 数据库和集合,选择 default 数据库和 poses 集合,如下所示:

a8f5a6ba69bec69b.png

这样就完成了 Firestore 集合的创建,我们将在应用中使用该集合。

7. 在 Firestore 中执行向量相似度搜索

为了执行向量相似度搜索,我们将接收用户的查询。此查询的一个示例可以是 "Suggest me some exercises to relieve back pain"

查看 search-data.py 文件。要查看的关键函数是搜索函数,如下所示。从高层级来看,它会创建一个嵌入类,用于生成用户查询的嵌入。然后,它使用 FirestoreVectorStore 类来调用其 similarity_search 函数。

def search(query: str):
    """Executes Firestore Vector Similarity Search"""
    embedding = VertexAIEmbeddings(
        model_name=settings.embedding_model_name,
        project=settings.project_id,
        location=settings.location,
    )

    client = firestore.Client(project=settings.project_id, database=settings.database)

    vector_store = FirestoreVectorStore(
        client=client, collection=settings.collection, embedding_service=embedding
    )

    logging.info(f"Now executing query: {query}")
    results: list[Document] = vector_store.similarity_search(
        query=query, k=int(settings.top_k), include_metadata=True
    )
    for result in results:
        print(result.page_content)

在运行此代码并提供一些查询示例之前,您必须先生成一个 Firestore 复合索引,这是成功执行搜索查询所必需的。如果您在未创建索引的情况下运行应用,系统会显示一条错误消息,指出您需要先创建索引,并提供用于先创建索引的命令。

用于创建复合索引的 gcloud 命令如下所示:

gcloud firestore indexes composite create --project=<YOUR_PROJECT_ID> --collection-group=poses --query-scope=COLLECTION --field-config=vector-config='{"dimension":"768","flat": "{}"}',field-path=embedding

由于数据库中存在 150 多条记录,因此索引需要几分钟才能完成。完成后,您可以通过以下命令查看索引:

gcloud firestore indexes composite list

您应该会在列表中看到刚刚创建的索引。

现在,请尝试运行以下命令:

python search-data.py --prompt "Recommend me some exercises for back pain relief"

系统应该会为您提供一些建议。运行示例如下所示:

2025-01-21 15:48:51,282 - INFO - Now executing query: Recommend me some exercises for back pain relief
name: Supine Spinal Twist Pose
description: A gentle supine twist (Supta Matsyendrasana), great for beginners.  Releases spinal tension, improves digestion, and calms the nervous system.  Keep shoulders flat on the floor and lengthen the spine.

sanskrit_name: Supta Matsyendrasana
expertise_level: Beginner
pose_type: ['Supine', 'Twist']
name: Cow Pose
description: Cow Pose (Bitilasana) is a gentle backbend, stretching the chest, shoulders, and abdomen.  Maintain a neutral spine, lengthen the tailbone, and avoid hyperextension.  Benefits include improved posture and stress relief.

sanskrit_name: Bitilasana
expertise_level: Beginner
pose_type: ['Arm Leg Support', 'Back Bend']
name: Locust I Pose
description: Locust Pose I (Shalabhasana A) strengthens the back, glutes, and shoulders.  Lie prone, lift chest and legs simultaneously, engaging back muscles.  Keep hips grounded and gaze slightly forward.

sanskrit_name: Shalabhasana A
expertise_level: Intermediate
pose_type: ['Prone', 'Back Bend']

完成上述操作后,我们现在已经了解如何使用 Firestore 向量数据库来上传记录、生成嵌入和执行向量相似度搜索。我们现在可以创建一个 Web 应用,将向量搜索集成到 Web 前端。

8. Web 应用

Python Flask Web 应用位于 main.py 文件中,前端 HTML 文件位于 templates/index.html.

建议您查看这两个文件。首先,从包含 /search 处理程序的 main.py 文件开始,该处理程序会接收从前端 HTML index.html 文件传递的提示。然后,此方法会调用搜索方法,后者会执行我们在上一部分中看到的向量相似度搜索。

然后,系统会将包含推荐列表的响应发送回 index.html。然后,index.html 会以不同卡片的形式显示建议。

在本地运行应用

启动新的终端窗口 (Ctrl+Shift+C) 或任何现有终端窗口,然后输入以下命令:

python main.py

下面展示了一个执行示例:

 * Serving Flask app 'main'
 * Debug mode: on
2025-01-21 16:02:37,473 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080
 * Running on http://10.88.0.4:8080
2025-01-21 16:02:37,473 - INFO - Press CTRL+C to quit
2025-01-21 16:02:37,474 - INFO -  * Restarting with stat
2025-01-21 16:02:41,462 - WARNING -  * Debugger is active!
2025-01-21 16:02:41,484 - INFO -  * Debugger PIN: 440-653-555

应用启动并运行后,点击下方显示的“网页预览”按钮,访问应用的主网址:

de297d4cee10e0bf.png

它应显示所提供的 index.html 文件,如下所示:

20240a0e885ac17b.png

提供示例查询(例如:Provide me some exercises for back pain relief),然后点击 Search 按钮。这应该会从数据库中检索一些推荐内容。您还会看到一个 Play Audio 按钮,点击该按钮可根据说明生成音频串流,您可以直接收听。

789b4277dc40e2be.png

9. (可选)部署到 Google Cloud Run

最后一步是将此应用部署到 Google Cloud Run。部署命令如下所示,请务必在部署之前将变量 (<<YOUR_PROJECT_ID>>) 的值替换为您的项目专属的值。这些值将能够从 config.yaml 文件中检索。

gcloud run deploy yogaposes --source . \
  --port=8080 \
  --allow-unauthenticated \
  --region=us-central1 \
  --platform=managed  \
  --project=<<YOUR_PROJECT_ID>> \
  --env-vars-file=config.yaml

从应用的根文件夹执行上述命令。系统可能还会要求您启用 Google Cloud API,并确认您已了解各种权限,请按照要求操作。

部署过程大约需要 5-7 分钟才能完成,请耐心等待。

3a6d86fd32e4a5e.png

成功部署后,部署输出将提供 Cloud Run 服务网址。其格式如下:

Service URL: https://yogaposes-<<UNIQUEID>.us-central1.run.app

访问该公共网址,您应该会看到已成功部署并运行的同一 Web 应用。

84e1cbf29cbaeedc.png

您还可以通过 Google Cloud 控制台访问 Cloud Run,然后查看 Cloud Run 中的服务列表。yogaposes 服务应是其中列出的服务之一(如果不是唯一一个)。

f2b34a8c9011be4c.png

您可以点击特定服务名称(在本例中为 yogaposes)来查看服务的详细信息,例如网址、配置、日志等。

faaa5e0c02fe0423.png

至此,我们已在 Cloud Run 上完成瑜伽姿势推荐器 Web 应用的开发和部署。

10. 恭喜

恭喜!您已成功构建一个应用,该应用可将数据集上传到 Firestore,生成嵌入,并根据用户查询执行向量相似度搜索。

参考文档