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

1. 概览

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

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

学习内容

所需内容

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

2. 创建和设置 Firebase 项目

如需使用 Firebase Vector Search 扩展程序,您需要一个 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 产品

您正在构建的应用使用多款适用于 Apple 应用的 Firebase 产品:

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

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

为 Firebase Authentication 启用匿名身份验证

此应用利用匿名身份验证,使用户无需创建账号即可开始使用。这样可以顺畅地完成新手入门流程。要详细了解匿名身份验证(以及如何升级到已命名的帐号),请参阅匿名身份验证最佳做法

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

设置 Cloud Firestore

此 Swift 应用使用 Cloud Firestore 保存笔记。以下是设置 Cloud Firestore 的方法:

  1. 在 Firebase 控制台的左侧面板中,依次点击 Build > Firestore Database。然后点击创建数据库启用 Cloud Firestore
  2. 选择数据库的位置,确保选择可以使用 Gemini 的位置(您只需使用 us-central1 即可)。不过请注意,此位置以后无法更改。点击下一步
  3. 选择 Start in test mode 选项。阅读有关安全规则的免责声明。测试模式可确保您在开发过程中可以随意向数据库写入数据。为测试模式下的 Firestore 设置安全规则
  4. 点击创建以创建数据库。

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. Add Firebase to your Apple app(将 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 needed,确保在 Add to targets 中选择 Notes 目标,然后点击 Finish在“选择添加文件”对话框中的选项中选择“根据需要复制”
  9. 在 Firebase 控制台中,您现在可以点击完成其余设置流程:在本部分开头下载的示例已经安装了 Firebase Apple SDK,并且进行了初始化设置。您可以点击继续前往控制台以完成此流程。

运行应用

现在是时候试试这款应用了!

  1. 返回 Xcode,在 iOS 模拟器上运行该应用。在 Run Destinations 下拉菜单中,首先选择一个 iOS 模拟器。在“Run Destinations”下拉菜单中选择 iOS 模拟器
  2. 然后,点击 Run 按钮,或按 ⌘ + R
  3. 当应用在模拟器上成功启动后,请添加几项备注。
  4. 在 Firebase 控制台中,导航到 Firestore 数据浏览器,以便您可以看到在应用中添加新笔记时创建的新文档。显示一些文档的 Cloud Firestore 控制台,以及显示相同文档的 iOS 模拟器

4.安装 Vector Search with Firestore 扩展程序

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

开始安装扩展程序

  1. 仍在 Firestore 部分中,点击扩展程序标签页。在 Firestore 控制台中选择“Firebase Extensions”标签页
  2. 点击探索 Extensions HubFirestore 控制台中的“Firebase Extensions”标签页
  3. 输入“矢量”。
  4. 点击“Vector Search with Firestore 扩展程序”。Firebase Extensios Hub 着陆页 然后,您会进入该扩展程序的详情页面,您可以在其中详细了解该扩展程序、其工作原理、所需的 Firebase 服务以及如何对其进行配置。
  5. 点击在 Firebase 控制台中安装“Vector Search with Firestore”扩展程序的安装按钮
  6. 系统随即会显示您的所有项目的列表。
  7. 选择您在此 Codelab 的第一步中创建的项目。Firebase 项目选择器屏幕

配置扩展程序

Firebase Extensions 使用 Cloud Functions for Firebase,这要求您的项目采用随用随付 Blaze 方案。您需要先升级项目,然后才能将 Vector Search 与 Firestore 扩展程序搭配使用。

  1. 点击 Upgrade project 以继续。将项目升级到 Blaze 方案
  2. 选择现有结算帐号,或创建一个新帐号。点击继续选择结算账号
  3. 设置预算(例如 10 美元),然后依次点击继续购买设置预算
  4. 查看已启用的 API 和已创建的资源。查看已启用的 API
  5. 启用所需的服务。启用所需服务
  6. 启用 Cloud Storage 时,为安全规则选择测试模式
  7. 确认 Cloud Storage 将使用与您的 Cloud Firestore 实例相同的位置。
  8. 启用所有服务后,点击下一步启用所有服务后点击“下一步”
  9. 查看已授予此扩展程序的访问权限。
  10. 配置扩展程序:
    • 选择 Vertex AI 作为 LLM
    • 集合路径notes
    • 默认查询上限3
    • 输入字段名称text
    • 输出字段名称: embedding
    • 状态字段名称:* *状态*
    • 嵌入现有文档
    • 更新现有文档
    • Cloud Functions 函数位置us-central1
  11. 点击安装扩展程序以完成安装。

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

5. 背景

在等待安装完成期间,请参阅以下背景信息,了解 Vector Search with Firestore 扩展程序的工作原理。

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

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

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

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

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

6. 尝试使用 Firestore 扩展程序进行矢量搜索

在您之前在本 Codelab 中下载的 iOS 应用中使用矢量搜索与 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 进行 Vector Search”扩展程序的文档
  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>
    新文档的矢量嵌入状态更新

执行查询

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

最后,是时候将您的移动应用连接到 Vector Search with 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("") 所在的行

如需调用 Callable 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. 如需向列表视图添加搜索框,请将 .searchable(text: $searchTerm, prompt: "Search") 视图修饰符添加到 .navigationTitle("Notes") 行的正上方
  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. 输入您添加的某个文档中出现的字词。同样,这非常适合语义查询,例如“如何在 Swift 中调用异步 Firebase API”(前提是您添加的说明中至少有一个包含讨论此主题的文本)。
  5. 您可能想要看到搜索结果,但是列表视图为空,并且 Xcode 控制台显示错误消息:“The function was called with an invalid arguments”

显示空结果列表的“记事”应用

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

分析错误消息

  1. 如需了解问题所在,请前往 Firebase 控制台
  2. 转到函数部分
  3. 找到 ext-firestore-vector-search-queryCallable 函数,点击三个垂直点以打开溢出菜单
  4. 选择查看日志以转到 Logs Explorer
  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 索引,其中包含 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 的可搜索视图修饰符和任务修饰符实现搜索栏。
  • 使用 Firestore SDK 的 Callable 接口调用 Cloud Functions 函数,以对数据库执行语义搜索。

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

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