使用 Firebase Extensions 将 Firestore 矢量搜索添加到您的移动应用

1. 概览

在此 Codelab 中,您将学习如何使用 Firestore 向量相似性搜索向应用添加强大的搜索功能。您将为一款使用 Swift 和 SwiftUI 编写的记事应用实现语义搜索功能。

Cloud Firestore 控制台显示了一些文档,这些文档也显示在右侧的 iOS 应用中。

学习内容

  • 如何安装使用 Firestore 扩展向量搜索来计算向量嵌入。
  • 如何从 Swift 应用调用 Firebase Cloud Functions。
  • 如何根据已登录用户预先过滤数据。

所需内容

  • Xcode 15.3
  • Codelab 示例代码。您将在本 Codelab 的后续步骤中下载此文件。

2. 创建和设置 Firebase 项目

如需使用 Firebase Vector Search 扩展程序,您需要一个 Firebase 项目。在此 Codelab 的这一部分中,您将创建一个新的 Firebase 项目,并激活所需的服务,例如 Cloud Firestore 和 Firebase Authentication。

创建 Firebase 项目

  1. 使用您的 Google 账号登录 Firebase 控制台
  2. 点击相应按钮以创建新项目,然后输入项目名称(例如 Firestore Vector Search Codelab)。
  3. 点击继续
  4. 如果看到相关提示,请查看并接受 Firebase 条款,然后点击继续
  5. (可选)在 Firebase 控制台中启用 AI 辅助功能(称为“Gemini in Firebase”)。
  6. 在此 Codelab 中,您不需要使用 Google Analytics,因此请关闭 Google Analytics 选项。
  7. 点击创建项目,等待项目完成预配,然后点击继续

如需详细了解 Firebase 项目,请参阅了解 Firebase 项目

升级您的 Firebase 定价方案

如需使用 Firebase Extensions 及其底层云服务,您的 Firebase 项目必须采用随用随付 (Blaze) 定价方案,这意味着该项目需要与一个 Cloud Billing 账号相关联。

  • Cloud Billing 账号要求提供付款方式,例如信用卡。
  • 如果您刚开始接触 Firebase 和 Google Cloud,请确认您是否有资格获得 $300 赠金和免费试用 Cloud Billing 账号
  • 如果您是在活动中完成此 Codelab,请询问活动组织者是否有可用的 Cloud 积分。

如需将项目升级到 Blaze 方案,请按以下步骤操作:

  1. 在 Firebase 控制台中,选择升级您的方案
  2. 选择 Blaze 方案。按照屏幕上的说明将 Cloud Billing 账号与您的项目相关联。
    如果您需要在此升级过程中创建 Cloud Billing 账号,则可能需要返回 Firebase 控制台中的升级流程以完成升级。

在控制台中启用和设置 Firebase 产品

您所构建的应用会使用多个适用于 Apple 应用的 Firebase 产品:

  • Firebase Authentication,可让用户轻松登录您的应用。
  • Cloud Firestore:用于在云端保存结构化数据,并在数据发生变化时即时收到通知。
  • Firebase 安全规则,用于保护您的数据库的安全。

其中一些产品需要进行特殊配置,或需要使用 Firebase 控制台启用。

为 Firebase Authentication 启用匿名身份验证

此应用使用匿名身份验证,以便用户无需先创建账号即可开始使用该应用。这样可实现顺畅的初始配置流程。如需详细了解匿名身份验证(以及如何升级到具名账号),请参阅有关匿名身份验证的最佳实践

  1. 在 Firebase 控制台的左侧面板中,依次点击构建 > 身份验证。然后点击开始使用启用 Firebase Authentication
  2. 您现在位于“身份验证”信息中心,可以在其中查看注册用户、配置登录提供方和管理设置。
  3. 选择登录方法标签页(或点击此处直接前往该标签页)。
  4. 在提供方选项中点击 Anonymous,将开关切换为启用,然后点击保存

设置 Cloud Firestore

此 Swift 应用使用 Cloud Firestore 保存笔记。

以下是在 Firebase 项目中设置 Cloud Firestore 的方法:

  1. 在 Firebase 控制台的左侧面板中,展开构建,然后选择 Firestore 数据库
  2. 点击创建数据库
  3. 数据库 ID 保留为 (default)
  4. 为数据库选择一个位置,然后点击下一步
    对于真实应用,您需要选择靠近用户的位置。
  5. 点击以测试模式开始。阅读有关安全规则的免责声明。
    在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在没有为数据库添加安全规则的情况下,请不要公开分发或公开应用。
  6. 点击创建

设置 Cloud Storage for Firebase

该 Web 应用使用 Cloud Storage for Firebase 来存储、上传和分享图片。

以下是在 Firebase 项目中设置 Cloud Storage for Firebase 的方法:

  1. 在 Firebase 控制台的左侧面板中,展开构建,然后选择存储
  2. 点击开始使用
  3. 为默认存储分区选择位置。
    US-WEST1US-CENTRAL1US-EAST1 中的存储分区可为 Google Cloud Storage 使用“始终免费”层级。所有其他位置的存储分区都遵循 Google Cloud Storage 价格和用量
  4. 点击以测试模式开始。阅读有关安全规则的免责声明。
    在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在未为您的存储桶添加安全规则的情况下,请不要公开分发或公开应用。
  5. 点击创建

3. 关联移动应用

在此 Codelab 部分中,您将下载一个简单的记事应用的源代码,并将其连接到您刚刚创建的 Firebase 项目。

下载示例应用

  1. 前往 https://github.com/FirebaseExtended/codelab-firestore-vectorsearch-ios,并将代码库克隆到本地机器
  2. 在 Xcode 中打开 Notes.xcodeproj 项目

将应用关联到 Firebase 项目

为了让您的应用能够访问 Firebase 服务,您需要在 Firebase 控制台中设置该应用。您可以将多个客户端应用连接到同一个 Firebase 项目,例如,如果您创建了 Android 应用或 Web 应用,则应将它们连接到同一个 Firebase 项目。

如需详细了解 Firebase 项目,请参阅了解 Firebase 项目

  1. 在 Firebase 控制台中,前往 Firebase 项目的概览页面。Firebase 控制台的概览页面
  2. 点击 iOS+ 图标以添加 iOS 应用。
  3. 将 Firebase 添加到您的 Apple 应用界面中,插入 Xcode 项目中的软件包 ID (com.google.firebase.codelab.Notes)。
  4. 您可以根据需要输入应用别名(例如 iOS 版 Google Keep)。
  5. 点击“注册应用”,进入下一步。
  6. 下载 GoogleServices-Info.plist 文件。
  7. GoogleServices-Info.plist 拖动到 Xcode 项目的 Notes 文件夹中。一种不错的方法是将其放置在 Assets.xcassets 文件下方。将 plist 文件拖动到 Xcode 中
  8. 选择 Copy items if needed,确保在 Add to targets 中选择了 Notes 目标,然后点击 Finish在“选择添加文件的选项”对话框中选择“Copy if needed”
  9. 在 Firebase 控制台中,您现在可以点击完成其余的设置流程:您在本部分开头下载的示例已安装 Firebase Apple SDK 并设置了初始化。您可以点击继续前往控制台来完成此流程。

运行应用

现在,是时候试用一下该应用了!

  1. 返回 Xcode,在 iOS 模拟器上运行应用。在运行目的地下拉菜单中,先选择一个 iOS 模拟器。在“运行目的地”下拉菜单中选择 iOS 模拟器
  2. 然后,点击运行按钮,或按 ⌘ + R
  3. 在模拟器上成功启动应用后,添加几条笔记。
  4. 在 Firebase 控制台中,前往 Firestore 数据浏览器,这样您就可以在应用中添加新笔记时看到新文档的创建过程。Cloud Firestore 控制台显示了一些文档,旁边的 iOS 模拟器也显示了相同的文档

4. 安装“使用 Firestore 进行向量搜索”扩展程序

在此 Codelab 的这一部分中,您将安装“Vector Search with Firestore”扩展程序,并根据您正在处理的记事应用的要求对其进行配置。

开始安装扩展程序

  1. 在“Firestore”部分中,点击扩展程序标签页。在 Firestore 控制台中选择“Firebase 扩展程序”标签页
  2. 点击探索 Extensions HubFirestore 控制台中的“Firebase 扩展程序”标签页
  3. 输入“矢量”。
  4. 点击“Vector Search with Firestore extension”。Firebase Extensions Hub 着陆页 系统会将您带到扩展程序的详情页面,您可以在其中详细了解该扩展程序、其运作方式、所需的 Firebase 服务以及配置方式。
  5. 点击 在 Firebase 控制台中安装“使用 Firestore 进行向量搜索”扩展程序的“安装”按钮
  6. 系统会显示您的所有项目的列表。
  7. 选择您在本 Codelab 的第一步中创建的项目。Firebase 项目选择器界面

配置扩展程序

  1. 查看已启用的 API 和已创建的资源。查看已启用的 API
  2. 启用必需的服务。启用必需服务
  3. 启用所有服务后,点击下一步启用所有服务后,点击“下一步”
  4. 审核授予此扩展程序的访问权限。
  5. 配置扩展程序:
    • 选择 Vertex AI 作为 LLM
    • 收集路径notes
    • 默认查询限制3
    • 输入字段名称text
    • 输出字段名称: embedding
    • 状态字段名称:*status*
    • 嵌入现有文档
    • 更新现有文档
    • Cloud Functions 位置us-central1
  6. 点击安装扩展程序以完成安装。

这可能需要几分钟的时间。在等待安装完成期间,您可以继续学习本教程的下一部分,了解一些关于向量嵌入的背景信息。

5. 背景

在等待安装完成期间,您可以先了解一下“Vector Search with Firestore”扩展程序的工作原理。

什么是向量、嵌入和向量数据库?

  • 向量是一种数学对象,用于表示某个量的大小和方向。它们可用于以更易于比较和搜索的方式表示数据。
  • 嵌入是表示字词或短语含义的向量。它们是通过使用大量文本语料库训练神经网络并学习字词之间的关系来创建的。
  • 向量数据库是经过优化,可用于存储和搜索向量数据的数据库。它们可实现高效的最近邻搜索,即查找与给定查询向量最相似的向量的过程。

向量搜索的工作原理是什么?

向量搜索通过将查询向量与数据库中的所有向量进行比较来运作。与查询向量最相似的向量会作为搜索结果返回。

可以使用各种距离指标来衡量两个向量之间的相似度。最常见的距离指标是余弦相似度,用于衡量两个向量之间的夹角。

6. 试用“使用 Firestore 进行向量搜索”扩展程序

在您使用本 Codelab 前面下载的 iOS 应用中的 Vector Search with Firestore 扩展程序之前,可以在 Firebase 控制台中试用该扩展程序。

阅读文档

Firebase 扩展程序包含有关其工作原理的文档。

  1. 扩展程序安装完成后,点击开始使用按钮。Firebase 控制台中的 Firebase Extensions 概览页面
  2. 请查看“此扩展程序的运作方式”标签页,其中介绍了以下内容:
    • 如何通过将文档添加到 notes 集合来计算文档的嵌入,
    • 如何通过调用 ext-firestore-vector-search-queryCallable 可调用函数来查询索引,
    • 或者如何通过向 _firestore-vector-search/index/queries 集合添加查询文档来查询索引。
    • 本页面还介绍了如何设置自定义嵌入函数。如果该扩展程序支持的 LLM 均不符合您的要求,并且您想使用其他 LLM 来计算嵌入,那么此功能非常有用。“使用 Firestore 进行向量搜索”扩展程序的文档
  3. 点击 Cloud Firestore 信息中心链接,前往您的 Firestore 实例
  4. 前往 _firestore-vector-search/index 文档。它应显示扩展程序已完成计算您在此 Codelab 的较早步骤中创建的所有笔记文档的嵌入内容。Firestore 控制台内的索引配置
  5. 如需验证这一点,请打开其中一个笔记文档,您应该会看到一个名为 embedding 的附加字段(类型为 vector<768>)以及一个 status 字段。Firestore 控制台内的向量嵌入字段

创建示例文档

您可以在 Firebase 控制台中创建一个新文档,以查看扩展程序的实际效果。

  1. 在 Firestore 数据浏览器中,前往 notes 集合,然后点击中间列中的 + 添加文档添加新文档
  2. 点击自动生成的 ID 以生成新的唯一文档 ID。
  3. 添加一个名为 text 的字符串类型字段,然后将一些文本粘贴到 value 字段中。请务必注意,此处不能使用 lorem ipsum 或其他随机文本。选择一篇新闻报道,例如。添加文本字段
  4. 点击保存
    • 请注意,扩展程序会添加一个状态字段来指示它正在处理数据。
    • 稍等片刻后,您应该会看到一个新字段 embedding,其值为 vector<768>
    新文档的向量嵌入状态更新

执行查询

“使用 Firestore 进行矢量搜索”扩展程序提供了一项实用的小功能,可让您查询文档索引,而无需连接应用。

  1. 在 Firebase 控制台的 Firestore 部分中,前往 _firestore-vector-search/index 文档
  2. 点击 + 开始收集图标 添加新的子集合
  3. 创建名为 queries 的新子集合
  4. 创建新文档,并将 query 字段设置为您某个文档中出现的文本。此功能最适合语义查询,例如“如何使用 Swift 映射 Firestore 文档”(前提是您添加的笔记中至少有一篇包含讨论此主题的文字)。添加查询字段
  5. 您可能会在状态中看到错误出现错误
  6. 这是因为缺少索引。如需设置缺失的索引配置,请点击此链接前往 Google Cloud 控制台,然后从列表选择正确的项目中选择您的项目
  7. 在 Cloud Log Explorer 中,您现在应该会看到一条错误消息,指出“FAILED_PRECONDITION: Missing vector index configuration. 请使用以下 gcloud 命令创建所需的索引:..."日志浏览器中的错误消息
  8. 错误消息还包含一个 gcloud 命令,您需要运行该命令来配置缺失的索引。
  9. 从命令行运行以下命令。如果您尚未在机器上安装 gcloud CLI,请按照此处的说明进行安装。
    gcloud alpha firestore indexes composite create --project=INSERT-YOUR=PROJECT-ID-HERE --collection-group=notes --query-scope=COLLECTION --field-config=vector-config='{"dimension":"768","flat": "{}"}',field-path=embedding
    
    创建索引需要几分钟时间。您可以在 Firebase 控制台的 Firestore 部分的索引标签页中查看进度。新索引的状态
  10. 设置好索引后,您可以创建新的查询文档。
  11. 现在,您应该会在结果字段执行语义查询的结果中看到匹配的文档 ID 列表
  12. 复制其中一个 ID,然后返回到 notes 集合。
  13. 使用 ⌘+F 搜索您复制的文档 ID - 此文档与您的查询最匹配。在文档列表中查找文档 ID

7. 实现语义搜索

现在,您终于可以将移动应用连接到 Vector Search with Firestore 扩展程序,并实现语义搜索功能,让用户能够使用自然语言查询来搜索笔记。

连接用于执行查询的可调用函数

Vector Search with Firestore 扩展程序包含一个 Cloud Function,您可以从移动应用中调用该函数,以查询您在本 Codelab 前面部分创建的索引。在此步骤中,您将建立移动应用与此可调用函数之间的连接。Firebase 的 Swift SDK 包含可让您无缝调用远程函数的 API。

  1. 返回到 Xcode,并确保您位于本 Codelab 前面步骤中克隆的项目中。
  2. 打开 NotesRepository.swift 文件。
  3. 查找包含 private lazy var vectorSearchQueryCallable: Callable = functions.httpsCallable("") 的行

如需调用可调用的 Cloud Functions 函数,您需要提供要调用的函数的名称。

  1. 前往项目的 Firebase 控制台,然后打开构建部分中的函数菜单项。
  2. 您会看到扩展程序已安装的函数列表。
  3. 搜索名为 ext-firestore-vector-search-queryCallable 的文件,然后复制其名称。
  4. 将该名称粘贴到代码中。现在应显示
    private lazy var vectorSearchQueryCallable: Callable<String, String> = functions.httpsCallable("ext-firestore-vector-search-queryCallable")
    

调用查询函数

  1. 找到方法 performQuery
  2. 通过调用来调用 Callable 函数
    let result = try await vectorSearchQueryCallable(searchTerm)
    

由于这是远程调用,因此可能会失败。

  1. 添加一些基本的错误处理,以捕获任何错误并将其记录到 Xcode 的控制台中。
    private func performQuery(searchTerm: String) async -> [String] {
      do {
        let result = try await vectorSearchQueryCallable(searchTerm)
        return [result]
      }
      catch {
        print(error.localizedDescription)
        return []
      }
    }
    

连接界面

为了让用户能够搜索笔记,您将在笔记列表界面中实现一个搜索栏。当用户输入搜索字词时,您需要调用在上一步中实现的 performQuery 方法。借助 SwiftUI 提供的 searchabletask 视图修饰符,只需编写几行代码即可实现此效果。

  1. 首先,打开 NotesListScreen.swift
  2. 如需向列表视图添加搜索框,请在 .navigationTitle("Notes") 行的正上方添加 .searchable(text: $searchTerm, prompt: "Search") 视图修饰符
  3. 然后,在下方添加以下代码来调用搜索功能:
.task(id: searchTerm, debounce: .milliseconds(800)) {
  await notesRepository.semanticSearch(searchTerm: searchTerm)
}

此代码段会异步调用 semanticSearch 方法。通过提供 800 毫秒的超时时间,您可以指示任务修饰符将用户输入去抖动 0.8 秒。这意味着,只有当用户暂停输入超过 0.8 秒时,才会调用 semanticSearch

现在,您的代码应如下所示:

...
List(repository.notes) { note in
  NavigationLink(value: note) {
    NoteRowView(note: note)
  }
  .swipeActions {
    Button(role: .destructive, action: { deleteNote(note: note) }) {
      Label("Delete", systemImage: "trash")
    }
  }
}
.searchable(text: $searchTerm, prompt: "Search")
.task(id: searchTerm, debounce: .milliseconds(800)) {
  await notesRepository.semanticSearch(searchTerm: searchTerm)
}
.navigationTitle("Notes")
...

运行应用

  1. ⌘ + R(或点击“运行”按钮)以在 iOS 模拟器上启动应用
  2. 您应该会看到您在此 Codelab 中之前在应用中添加的相同笔记,以及您通过 Firebase 控制台添加的任何笔记
  3. 您应该会在笔记列表顶部看到一个搜索字段
  4. 输入您添加的某个文档中显示的字词。同样,此功能最适合语义查询,例如“How can I call asynchronous Firebase APIs from Swift”(前提是您添加的笔记中至少有一条包含讨论此主题的文本)。
  5. 您可能希望看到搜索结果,但实际上,列表视图为空,并且 Xcode 控制台会显示一条错误消息:“The function was called with an invalid argument”(该函数是使用无效实参调用的)

“记事本”应用,其中显示了空白的结果列表

这意味着您发送的数据格式有误。

分析错误消息

  1. 如需了解问题出在哪里,请前往 Firebase 控制台
  2. 前往函数部分
  3. 找到 ext-firestore-vector-search-queryCallable 函数,然后点击三个竖点打开溢出菜单
  4. 选择查看日志,前往日志浏览器
  5. 您应该会看到一条错误消息
Unhandled error ZodError: [
  {
    "code": "invalid_type",
    "expected": "object",
    "received": "string",
    "path": [],
    "message": "Expected object, received string"
  }
]

这意味着您发送的数据格式有误。

使用正确的数据类型

如需了解扩展程序希望参数采用哪种格式,请查看扩展程序的文档。

  1. 前往 Firebase 控制台中的扩展程序部分
  2. 点击管理 ->管理“使用 Firestore 进行向量搜索”扩展程序
  3. 此扩展程序的运作方式部分,您可以找到输入和输出参数的规范。输入参数和结果值的文档
  4. 返回 Xcode,然后前往 NotesRepository.swift
  5. 在文件开头添加以下代码:
    private struct QueryRequest: Codable {
      var query: String
      var limit: Int?
      var prefilters: [QueryFilter]?
    }
    
    private struct QueryFilter: Codable {
      var field: String
      var `operator`: String
      var value: String
    
    }
    
    private struct QueryResponse: Codable {
      var ids: [String]
    }
    
    QueryRequest 与扩展程序根据其文档预期接收的输入参数的结构相匹配。它还包含一个嵌套的 prefilter 属性,您稍后会用到该属性。QueryResponse 与扩展程序响应的结构相匹配。
  6. 找到可调用函数的规范,并更新输入和输出类型
    private lazy var vectorSearchQueryCallable: Callable<QueryRequest, QueryResponse> = functions.httpsCallable("ext-firestore-vector-search-queryCallable")
    
  7. 更新了 performQuery 中对可调用函数的调用
    private func performQuery(searchTerm: String) async -> [String] {
      do {
        let queryRequest = QueryRequest(query: searchTerm,
                                        limit: 2)
        let result = try await vectorSearchQueryCallable(queryRequest)
        print(result.ids)
        return result.ids
      }
      catch {
        print(error.localizedDescription)
        return []
      }
    }
    

再次运行应用

  1. 再次运行应用
  2. 输入包含笔记中字词的搜索查询
  3. 您现在应该会看到过滤后的笔记列表

显示预期结果的应用的屏幕截图

预过滤用户数据

在您欣喜若狂地跳起舞来庆祝之前,请注意,当前版本的应用存在一个问题:结果集包含所有用户的数据。

您可以通过在其他模拟器上运行应用并添加更多文档来验证这一点。新文档只会显示在该模拟器中;如果您在另一个模拟器上再次运行该应用,则只会看到您第一次创建的文档。

如果您执行搜索,会发现对 vectorSearchQueryCallable 的调用返回的文档 ID 可能属于其他用户。为防止这种情况,我们需要使用预过滤

performQuery 中,按如下方式更新代码:

  let prefilters: [QueryFilter] = if let uid = user?.uid {
    [QueryFilter(field: "userId", operator: "==", value: uid)]
  }
  else {
    []
  }

  let queryRequest = QueryRequest(query: searchTerm,
                                  limit: 2,
                                  prefilters: prefilters)

这会根据登录用户的 ID 预先过滤数据。正如您可能预料到的那样,这需要更新 Firestore 索引。

在命令行中运行以下命令,以定义新的 Firestore 索引,该索引同时包含 userIdembedding 字段中的向量嵌入。

gcloud alpha firestore indexes composite create --project=INSERT-YOUR-PROJECT-ID-HERE --collection-group=notes --query-scope=COLLECTION --field-config=order=ASCENDING,field-path=userId --field-config=vector-config='{"dimension":"768","flat": "{}"}',field-path=embedding

索引构建完成后,再次运行应用以验证其是否按预期运行

预过滤后的结果集

8. 恭喜

恭喜您成功完成此 Codelab!

在此 Codelab 中,您学习了如何执行以下操作:

  • 设置启用了语义搜索的 Cloud Firestore 数据库。
  • 创建一个简单的 SwiftUI 应用来与数据库互动。
  • 使用 SwiftUI 的 searchable 视图修饰符和 task 修饰符实现搜索栏。
  • 使用 Firestore SDK 的 Callable 接口调用 Cloud Functions 函数,以对数据库执行语义搜索。

通过在此 Codelab 中获得的知识,您现在可以构建强大的应用,利用 Cloud Firestore 的语义搜索功能为用户提供更直观、更高效的搜索体验。

如需详细了解 Firestore 的新向量字段以及如何计算向量嵌入,请参阅文档