1. 概览
在此 Codelab 中,您将学习如何使用 Firestore 矢量相似搜索功能为应用添加强大的搜索功能。您将为使用 Swift 和 SwiftUI 编写的记事应用实现语义搜索功能。
学习内容
- 如何安装“使用 Firestore 的向量搜索”扩展程序来计算向量嵌入。
- 如何从 Swift 应用调用 Firebase Cloud Functions。
- 如何根据已登录的用户预过滤数据。
所需内容
- Xcode 15.3
- Codelab 示例代码。您将在 Codelab 的后续步骤中下载此文件。
2. 创建和设置 Firebase 项目
如需使用 Firebase 向量搜索扩展程序,您需要拥有一个 Firebase 项目。在 Codelab 的这一部分中,您将创建一个新的 Firebase 项目,并启用所需的服务,例如 Cloud Firestore 和 Firebase Authentication。
创建 Firebase 项目
- 登录 Firebase
- 在 Firebase 控制台中,点击添加项目,然后将您的项目命名为 Firestore Vector Search Lab
- 点击各个项目创建选项。如果出现提示,请接受 Firebase 条款。
- 在 Google Analytics 屏幕上,取消选中为此项目启用 Google Analytics 复选框,因为您不会对此应用使用 Google Analytics。
- 最后,点击创建项目。
如需详细了解 Firebase 项目,请参阅了解 Firebase 项目。
升级您的 Firebase 定价方案
如需使用 Firebase Extensions 及其底层云服务,您的 Firebase 项目需要采用随用随付 (Blaze) 定价方案,这意味着该项目已与 Cloud Billing 账号关联。
- Cloud Billing 账号要求提供付款方式,例如信用卡。
- 如果您刚开始接触 Firebase 和 Google Cloud,请确认您是否有资格获得 $300 赠金和免费试用 Cloud Billing 账号。
- 如果您是在活动中进行此 Codelab,请询问您的组织者,看看是否有可用的 Cloud 赠金。
如需将项目升级到 Blaze 方案,请按以下步骤操作:
- 在 Firebase 控制台中,选择升级您的方案。
- 选择 Blaze 方案。按照屏幕上的说明将 Cloud Billing 账号与您的项目相关联。
如果您需要在此升级过程中创建 Cloud Billing 账号,则可能需要返回 Firebase 控制台中的升级流程以完成升级。
在控制台中启用并设置 Firebase 产品
您所构建的应用会使用多个适用于 Apple 应用的 Firebase 产品:
- Firebase Authentication,可让用户轻松登录您的应用。
- Cloud Firestore:用于在云端保存结构化数据,并在数据发生变化时即时收到通知。
- Firebase 安全规则,用于保护您的数据库的安全。
其中一些产品需要进行特殊配置,或需要使用 Firebase 控制台启用。
为 Firebase Authentication 启用匿名身份验证
此应用使用匿名身份验证,让用户无需先创建账号即可开始使用该应用。这样可以简化新手入门流程。如需详细了解匿名身份验证(以及如何升级为命名账号),请参阅有关匿名身份验证的最佳实践。
- 在 Firebase 控制台的左侧面板中,依次点击 Build > Authentication。然后点击开始。
- 您现在已进入身份验证信息中心,您可以在其中查看已登录的用户、配置登录提供方以及管理设置。
- 选择登录方法标签页(或点击此处直接前往该标签页)。
- 在提供程序选项中,点击 Anonymous,将开关切换到 Enable,然后点击 Save。
设置 Cloud Firestore
此 Swift 应用使用 Cloud Firestore 保存记事。
如需在 Firebase 项目中设置 Cloud Firestore,请按以下步骤操作:
- 在 Firebase 控制台的左侧面板中,展开构建,然后选择 Firestore 数据库。
- 点击创建数据库。
- 将数据库 ID 设置为
(default)
。 - 为数据库选择一个位置,然后点击下一步。
对于真实应用,您需要选择靠近用户的位置。 - 点击以测试模式启动。阅读有关安全规则的免责声明。
在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在没有为数据库添加安全规则的情况下,请不要公开分发或公开应用。 - 点击创建。
设置 Cloud Storage for Firebase
Web 应用使用 Cloud Storage for Firebase 存储、上传和分享图片。
以下是在 Firebase 项目中设置 Cloud Storage for Firebase 的方法:
- 在 Firebase 控制台的左侧面板中,展开构建,然后选择存储。
- 点击开始使用。
- 为默认的 Storage 存储桶选择位置。
US-WEST1
、US-CENTRAL1
和US-EAST1
中的存储桶可以使用 Google Cloud Storage 的“始终免费”层级。所有其他位置的存储桶均遵循 Google Cloud Storage 价格和使用量。 - 点击以测试模式启动。阅读有关安全规则的免责声明。
在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在未为您的存储桶添加安全规则的情况下,请不要公开分发或公开应用。 - 点击创建。
3. 关联移动应用
在此 Codelab 的此部分中,您将下载一个简单记事应用的源代码,并将其关联到您刚刚创建的 Firebase 项目。
下载示例应用
- 前往 https://github.com/FirebaseExtended/codelab-firestore-vectorsearch-ios,将代码库克隆到本地机器
- 在 Xcode 中打开 Notes.xcodeproj 项目
将应用关联到您的 Firebase 项目
为了让您的应用能够访问 Firebase 服务,您需要在 Firebase 控制台中设置该应用。您可以将多个客户端应用关联到同一 Firebase 项目,例如,如果您创建了 Android 应用或 Web 应用,则应将它们关联到同一 Firebase 项目。
如需详细了解 Firebase 项目,请参阅了解 Firebase 项目。
- 在 Firebase 控制台中,前往 Firebase 项目的概览页面。
- 点击 iOS+ 图标以添加您的 iOS 应用。
- 在将 Firebase 添加到您的 Apple 应用界面中,插入 Xcode 项目中的软件包 ID (com.google.firebase.codelab.Notes)。
- 您可以视需要输入应用别名(iOS 版 Notes)。
- 点击“注册应用”进入下一步。
- 下载 GoogleServices-Info.plist 文件。
- 将 GoogleServices-Info.plist 拖动到 Xcode 项目的 Notes 文件夹中。为此,一个好方法是将其放在 Assets.xcassets 文件下。
- 选择 Copy items if necessary,确保在 Add to targets 中选择了 Notes 目标,然后点击 Finish。
- 现在,您可以在 Firebase 控制台中点击完成其余设置流程:您在本部分开头下载的示例已安装 Firebase Apple SDK 并完成了初始化设置。点击继续前往控制台即可完成流程。
运行应用
现在,是时候试用一下应用了!
- 返回 Xcode,在 iOS 模拟器上运行该应用。在 Run Destinations 下拉菜单中,首先选择一个 iOS 模拟器。
- 然后,点击运行按钮,或按 ⌘ + R
- 在模拟器上成功启动应用后,添加一些备注。
- 在 Firebase 控制台中,前往 Firestore 数据浏览器,以便在应用中添加新记事时查看系统创建的新文档。
4. 安装“使用 Firestore 进行向量搜索”扩展程序
在此 Codelab 的此部分中,您将安装“使用 Firestore 的矢量搜索”扩展程序,并根据您正在开发的记事应用的要求对其进行配置。
开始安装扩展程序
- 仍然在 Firestore 部分,点击扩展程序标签页。
- 点击探索 Extensions Hub。
- 输入“vector”。
- 点击“使用 Firestore 扩展程序的矢量搜索”。 然后,您会进入该扩展程序的详情页面,您可以在其中详细了解该扩展程序、其工作原理、所需的 Firebase 服务以及如何对其进行配置。
- 点击在 Firebase 控制台中安装。
- 系统随即会显示您的所有项目的列表。
- 选择您在此 Codelab 的第一步中创建的项目。
配置扩展程序
- 查看已启用的 API 和已创建的资源。
- 启用所需的服务。
- 启用所有服务后,点击下一步。
- 查看为此扩展程序授予的访问权限。
- 配置扩展程序:
- 选择 Vertex AI 作为 LLM
- 集合路径:notes
- 默认查询上限:3
- 输入字段名称:text
- 输出字段名称:embedding
- 状态字段名称:* *status*
- 嵌入现有文档:是
- 更新现有文档:是
- Cloud Functions 函数位置:us-central1
- 点击安装扩展程序以完成安装。
这可能需要几分钟时间。在等待安装完成期间,您可以随时继续学习本教程的下一部分,并阅读一些有关向量嵌入的背景信息。
5. 背景
在等待安装完成期间,请参阅以下背景信息,了解 Vector Search with Firestore 扩展程序的工作原理。
什么是向量、嵌入和向量数据库?
- 向量是表示某个量大小和方向的数学对象。它们可用于以更易于比较和搜索的方式表示数据。
- 嵌入是表示字词或短语含义的矢量。它们的创建方式是,对大量文本进行神经网络训练,并学习字词之间的关系。
- 矢量数据库是为存储和搜索矢量数据而优化的数据库。它们支持高效的最近邻搜索,即查找与给定查询向量最相似的向量的过程。
向量搜索的工作原理是什么?
向量搜索的工作原理是将查询向量与数据库中的所有向量进行比较。系统会将与查询矢量最相似的矢量作为搜索结果返回。
您可以使用各种距离度量来衡量两个向量之间的相似度。最常见的距离指标是余弦相似度,用于衡量两个向量之间的夹角。
6. 尝试使用 Firestore 扩展程序进行矢量搜索
在本 Codelab 中先前下载的 iOS 应用中使用 Vector Search with Firestore 扩展程序之前,您可以在 Firebase 控制台中试用该扩展程序。
阅读文档
Firebase Extensions 包含有关其工作原理的文档。
- 扩展程序安装完毕后,点击 Get started(开始使用)按钮。
- 请查看“此扩展程序的运作方式”标签页,其中介绍了:
- 如何通过将文档添加到
notes
集合来计算文档的嵌入, - 如何通过调用
ext-firestore-vector-search-queryCallable
Callable 函数来查询索引, - 或如何通过向
_firestore-vector-search/index/queries
集合添加查询文档来查询索引。 - 文中还介绍了如何设置自定义嵌入函数。如果扩展程序支持的所有 LLM 均无法满足您的要求,并且您想要使用其他 LLM 计算嵌入,那么自定义嵌入函数会非常有用。
- 如何通过将文档添加到
- 点击 Cloud Firestore 信息中心链接以转到您的 Firestore 实例
- 前往
_firestore-vector-search/index
文档。它应该显示,扩展程序已针对您在此 Codelab 的上一步中创建的所有笔记文档完成嵌入的计算。 - 如需验证这一点,请打开其中一个记事文档,您应该会看到一个名为
embedding
的额外字段(类型为vector<768>
),以及一个status
字段。
创建示例文档
您可以在 Firebase 控制台中创建新文档,以查看扩展程序的实际运行情况。
- 还是在 Firestore 数据浏览器中,前往
notes
集合,然后点击中间列中的 + 添加文档。 - 点击自动生成的 ID以生成新的唯一文档 ID。
- 添加一个名为
text
、类型为字符串的字段,并将一些文本粘贴到 value 字段中。请务必使用真实的文本,而不是 Lorem Ipsum 或其他随机文本。例如,选择一篇新闻报道。 - 点击保存。
- 请注意,该扩展程序如何添加状态字段来指示它正在处理数据。
- 片刻后,您应该会看到一个值为
vector<768>
的新字段embedding
。
执行查询
Vector Search with Firestore 扩展程序有一个很棒的小功能,让您无需连接应用即可查询文档索引。
- 在 Firebase 控制台的 Firestore 部分中,转到
_firestore-vector-search/index
文档 - 点击 + 开始收集
- 创建一个名为
queries
的新子合集 - 创建一个新文档,并将
query
字段设置为某个文档中出现的文本。这种方式最适合语义查询,例如“如何使用 Swift 映射 Firestore 文档”(前提是您添加的至少一条备注包含讨论此主题的文字)。 - 您可能会在以下状态中看到错误:
- 这是因为缺少索引。如需设置缺少的索引配置,请点击此链接前往项目的 Google Cloud 控制台,然后从列表中选择您的项目
- 在 Cloud Log Explorer 中,您现在应该看到一条错误消息,内容为“FAILED_PRECONDITION: Missing vector index configuration. 请使用以下 gcloud 命令创建所需的索引:..."
- 错误消息还包含一个
gcloud
命令,您需要运行该命令才能配置缺失的索引。 - 从命令行运行以下命令。如果您的计算机上未安装
gcloud
CLI,请按照此处的说明进行安装。 创建索引需要几分钟时间。您可以在 Firebase 控制台的 Firestore 部分的索引标签页中查看进度。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
- 设置索引后,您可以创建新的查询文档。
- 现在,您应该会在“结果”字段中看到匹配的文档 ID 列表
- 复制其中一个 ID,然后返回
notes
集合。 - 使用 ⌘+F 搜索您复制的文档 ID,此文档即是最符合您的查询的文档。
7. 实现语义搜索
现在,您可以将移动应用连接到“使用 Firestore 的矢量搜索”扩展程序,并实现语义搜索功能,让用户能够使用自然语言查询搜索记事。
连接 Callable 函数以执行查询
Vector Search with Firestore 扩展程序包含一个 Cloud Functions 函数,您可以在自己的移动应用中调用该函数,以查询您之前在此 Codelab 中创建的索引。在此步骤中,您将在移动应用与此 Callable 函数之间建立连接。Firebase 的 Swift SDK 包含可让您无缝调用远程函数的 API。
- 返回 Xcode,并确保您处于在此 Codelab 的前面步骤中克隆的项目中。
- 打开
NotesRepository.swift
文件。 - 找到包含
private lazy var vectorSearchQueryCallable: Callable
的行= functions.httpsCallable("")
如需调用可调用的 Cloud Functions 函数,您需要提供要调用的函数的名称。
- 前往项目的 Firebase 控制台,然后打开构建部分中的 Functions 菜单项。
- 您将看到该扩展程序已安装的函数的列表。
- 搜索名为
ext-firestore-vector-search-queryCallable
的文件,然后复制其名称。 - 将该名称粘贴到您的代码中。现在应显示
private lazy var vectorSearchQueryCallable: Callable<String, String> = functions.httpsCallable("ext-firestore-vector-search-queryCallable")
调用查询函数
- 找到方法
performQuery
- 通过调用
let result = try await vectorSearchQueryCallable(searchTerm)
由于这是远程调用,因此可能会失败。
- 添加一些基本的错误处理,以捕获所有错误并将其记录到 Xcode 的控制台。
private func performQuery(searchTerm: String) async -> [String] { do { let result = try await vectorSearchQueryCallable(searchTerm) return [result] } catch { print(error.localizedDescription) return [] } }
连接界面
为了让用户能够搜索记事,您将在记事列表屏幕中实现一个搜索栏。当用户输入搜索字词时,您需要调用在上一步中实现的 performQuery
方法。得益于 SwiftUI 提供的 searchable
和 task
视图修饰符,只需几行代码即可实现此目的。
- 首先,打开
NotesListScreen.swift
- 如需向列表视图添加搜索框,请在
.navigationTitle("Notes")
行上方添加.searchable(text: $searchTerm, prompt: "Search")
视图修饰符 - 然后,在下方添加以下代码,以调用搜索函数:
.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")
...
运行应用
- 按 ⌘ + R(或点击“Run”按钮),在 iOS 模拟器上启动应用
- 您应该会看到您在此 Codelab 中稍早在应用中添加的相同备注,以及您通过 Firebase 控制台添加的所有备注
- 您应该会在备注列表顶部看到一个搜索字段
- 输入您添加的文档中显示的某个字词。同样,这种方式最适合语义查询,例如“如何从 Swift 调用异步 Firebase API”(前提是您添加的至少一条备注包含讨论此主题的文字)。
- 您可能希望看到搜索结果,但列表视图却是空的,并且 Xcode 控制台显示一条错误消息:“使用无效参数调用了函数”
这意味着您发送的数据格式有误。
分析错误消息
- 如需了解问题所在,请前往 Firebase 控制台
- 前往函数部分
- 找到
ext-firestore-vector-search-queryCallable
函数,点击三个垂直点打开菜单 - 选择查看日志以前往日志浏览器
- 您应该会看到错误
Unhandled error ZodError: [
{
"code": "invalid_type",
"expected": "object",
"received": "string",
"path": [],
"message": "Expected object, received string"
}
]
这意味着您发送数据的格式有误。
使用正确的数据类型
如需了解扩展程序预期参数采用的格式,请参阅扩展程序的文档。
- 前往 Firebase 控制台的扩展程序部分
- 依次点击管理 ->
- 在此扩展程序的工作原理部分,您会看到输入和输出参数的规范。
- 返回 Xcode,然后前往
NotesRepository.swift
- 在文件开头添加以下代码:
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
与扩展程序响应的结构匹配。 - 查找 Callable 函数规范并更新输入和输出类型
private lazy var vectorSearchQueryCallable: Callable<QueryRequest, QueryResponse> = functions.httpsCallable("ext-firestore-vector-search-queryCallable")
- 更新
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 [] } }
再次运行应用
- 再次运行应用
- 输入包含某个记事中包含的字词的搜索查询
- 您现在应该会看到经过过滤的记事列表
预过滤用户数据
不过,在您跳起舞来庆祝之前,请先注意当前版本的应用存在一个问题:结果集包含所有用户的数据。
您可以通过在其他模拟器上运行应用并添加更多文档来验证这一点。新文档只会显示在该模拟器中,如果您在另一个模拟器上再次运行应用,则只会看到您在第一次运行时创建的文档。
如果您执行搜索,会发现对 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 的新向量字段以及如何计算向量嵌入,请参阅文档。