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

1. 概览

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

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

学习内容

所需内容

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

2. 创建和设置 Firebase 项目

如需使用 Firebase 向量搜索扩展程序,您需要拥有一个 Firebase 项目。在此 Codelab 的此部分中,您将创建一个新的 Firebase 项目,并激活所需的服务(例如 Cloud Firestore 和 Firebase Authentication)。

创建 Firebase 项目

  1. 登录 Firebase
  2. 在 Firebase 控制台中,点击添加项目,然后将您的项目命名为 Firestore 矢量搜索实验室创建项目,第 1 步(共 3 步):选择项目名称
  3. 点击各个项目创建选项。如果系统提示,请接受 Firebase 条款。
  4. 在 Google Analytics 屏幕上,取消选中为此项目启用 Google Analytics 复选框,因为您不会对此应用使用 Google Analytics。
  5. 最后,点击创建项目

如需详细了解 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 控制台的左侧面板中,依次点击 Build > Authentication。然后点击开始启用 Firebase Authentication
  2. 您现在进入了“身份验证”信息中心,在这里可以查看已注册的用户、配置登录提供程序和管理设置。
  3. 选择登录方法标签页(或点击此处直接前往该标签页)。
  4. 在提供程序选项中,点击 Anonymous,将开关切换到 Enable,然后点击 Save

设置 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. 为默认的 Storage 存储分区选择位置。
    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 的记事)。
  5. 点击“注册应用”以进入下一步。
  6. 下载 GoogleServices-Info.plist 文件。
  7. GoogleServices-Info.plist 拖动到 Xcode 项目的 Notes 文件夹中。一个不错的方法是将其放在 Assets.xcassets 文件下。将 plist 文件拖动到 Xcode 中
  8. 选择 Copy items if necessary,确保在 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 的此部分中,您将安装“使用 Firestore 的矢量搜索”扩展程序,并根据您正在开发的记事应用的要求对其进行配置。

开始安装扩展程序

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

配置扩展程序

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

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

5. 背景

在等待安装完成期间,请参阅以下有关“使用 Firestore 的向量搜索”扩展程序工作原理的背景信息。

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

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

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

向量搜索的工作原理是将查询向量与数据库中的所有向量进行比较。系统会将与查询矢量最相似的矢量作为搜索结果返回。

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

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

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

阅读文档

Firebase Extensions 包含有关其工作原理的文档。

  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 且类型为字符串的字段,然后将一些文本粘贴到“值”字段中。请务必使用真实的文本,而不是 Lorem Ipsum 或其他随机文本。例如,选择一篇新闻报道。添加文本字段
  4. 点击保存
    • 请注意,该扩展程序如何添加状态字段来指示它正在处理数据。
    • 片刻后,您应该会看到一个值为 vector<768> 的新字段 embedding
    新文档的向量嵌入状态更新

执行查询

“使用 Firestore 的矢量搜索”扩展程序提供了一项实用的小功能,可让您在不连接应用的情况下查询文档索引。

  1. 在 Firebase 控制台的 Firestore 部分,前往 _firestore-vector-search/index 文档
  2. 点击 + 开始收集添加新子集合
  3. 创建一个名为 queries 的新子集合
  4. 创建一个新文档,并将 query 字段设置为某个文档中出现的文本。这种方式最适用于语义查询,例如“如何使用 Swift 映射 Firestore 文档”(前提是您添加的至少一条备注包含讨论此主题的文字)。添加查询字段
  5. 您可能会在状态中看到错误出现错误
  6. 这是因为缺少索引。如需设置缺少的索引配置,请点击此链接前往项目的 Google Cloud 控制台,然后从列表中选择您的项目选择正确的项目
  7. 现在,您应该会在 Cloud Logging 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. 实现语义搜索

现在,您可以将移动应用连接到“使用 Firestore 的矢量搜索”扩展程序,并实现语义搜索功能,让用户能够使用自然语言查询搜索记事。

连接用于执行查询的 Callable 函数

“使用 Firestore 的矢量搜索”扩展程序包含一个 Cloud Functions 函数,您可以从移动应用调用该函数来查询您在此 Codelab 中之前创建的索引。在此步骤中,您将在移动应用与此 Callable 函数之间建立连接。Firebase 的 Swift SDK 包含可让您无缝调用远程函数的 API。

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

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

  1. 前往项目的 Firebase 控制台,然后打开构建部分中的 Functions 菜单项。
  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. 通过调用
    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(或点击“Run”按钮)在 iOS 模拟器上启动应用
  2. 您应该会看到您在此 Codelab 中稍早在应用中添加的相同备注,以及您通过 Firebase 控制台添加的所有备注
  3. 您应该会在备注列表顶部看到一个搜索字段
  4. 输入您添加的文档中显示的某个字词。再次强调,这种方式最适合语义查询,例如“如何从 Swift 调用异步 Firebase API”(前提是您添加的至少一条备注包含讨论此主题的文字)。
  5. 您可能希望看到搜索结果,但列表视图却是空的,并且 Xcode 控制台显示以下错误消息:“使用无效参数调用了函数”

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

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

分析错误消息

  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 中对 Callable 函数的调用
    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 索引,其中包含 embedding 字段中的 userId 和向量嵌入。

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 的可搜索视图修饰符和任务修饰符实现搜索栏。
  • 使用 Firestore SDK 的 Callable 接口调用 Cloud Functions 函数,对数据库执行语义搜索。

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

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