使用 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 Vector Search Lab创建项目,第 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 版 Notes)。
  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 模拟器上运行该应用。在 Run Destinations 下拉菜单中,首先选择一个 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 控制台中安装“Vector Search with 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 Extensions 包含有关其工作原理的文档。

  1. 扩展程序安装完毕后,点击 Get started(开始使用)按钮。Firebase 控制台中的 Firebase Extensions 概览页面
  2. 请查看“此扩展程序的运作方式”标签页,其中介绍了:
    • 如何通过将文档添加到 notes 集合来计算文档的嵌入,
    • 如何通过调用 ext-firestore-vector-search-queryCallable Callable 函数来查询索引,
    • 或如何通过向 _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. 点击保存
    • 请注意,该扩展程序如何添加状态字段来指示它正在处理数据。
    • 片刻后,您应该会看到一个值为 vector<768> 的新字段 embedding
    新文档的向量嵌入状态更新

执行查询

Vector Search with 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. 实现语义搜索

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

连接 Callable 函数以执行查询

Vector Search with 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. 查找 Callable 函数规范并更新输入和输出类型
    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 的新向量字段以及如何计算向量嵌入,请参阅文档