透過 Firebase Extensions 在行動應用程式中加入 Firestore Vector Search

1. 總覽

在本程式碼研究室中,您將瞭解如何使用 Firestore 向量相似度搜尋,將強大的搜尋功能新增至應用程式。您將為以 Swift 和 SwiftUI 編寫的記事應用程式,實作語意搜尋功能。

Cloud Firestore 控制台顯示部分文件,這些文件也會顯示在 iOS 應用程式的右側。

學習目標

  • 如何安裝Firestore 擴充功能的 Vector Search,以便計算向量嵌入。
  • 如何從 Swift 應用程式呼叫 Firebase Cloud Functions。
  • 如何根據已登入的使用者預先篩選資料。

您需要準備的項目

  • Xcode 15.3
  • 程式碼研究室的範例程式碼。您會在程式碼研究室的後續步驟中下載此檔案。

2. 建立及設定 Firebase 專案

如要使用 Firebase Vector Search 擴充功能,您需要建立 Firebase 專案。在本程式碼研究室的這個部分,您將建立新的 Firebase 專案,並啟用所需服務,例如 Cloud Firestore 和 Firebase 驗證。

建立 Firebase 專案

  1. 登入 Firebase
  2. 在 Firebase 控制台中,按一下「新增專案」,然後將專案命名為「Firestore Vector Search Lab」建立專案,步驟 1 (3 步驟):選擇專案名稱
  3. 點選專案建立選項。當系統顯示提示時,請接受 Firebase 條款。
  4. 在 Google Analytics 畫面中,取消勾選「啟用這項專案的 Google Analytics」方塊,因為這個應用程式不會用到 Analytics。
  5. 最後,按一下「建立專案」

如要進一步瞭解 Firebase 專案,請參閱「瞭解 Firebase 專案」一文。

升級 Firebase 定價方案

如要使用 Firebase Extensions 及其基礎雲端服務,您的 Firebase 專案必須採用即付即用 (Blaze) 方案,也就是說,必須連結至 Cloud Billing 帳戶

  • 您必須提供付款方式 (例如信用卡),才能建立 Cloud Billing 帳戶。
  • 如果您是 Firebase 和 Google Cloud 的新手,請確認您是否符合 $300 美元的抵免額和免費試用 Cloud Billing 帳戶的資格。
  • 如果您是為了參加活動而進行這個程式碼研究室,請向活動主辦單位詢問是否有任何 Cloud 抵用金。

如要將專案升級至 Blaze 方案,請按照下列步驟操作:

  1. 在 Firebase 控制台中,選取「升級方案」
  2. 選取 Blaze 方案。按照畫面上的指示將 Cloud Billing 帳戶連結至專案。
    如需升級時需要建立 Cloud Billing 帳戶,您可能需要返回 Firebase 控制台的升級流程。

在控制台中啟用及設定 Firebase 產品

您建構的應用程式會使用幾項 Firebase 產品,這些產品都可用於 Apple 應用程式:

  • Firebase 驗證:可讓使用者輕鬆登入您的應用程式。
  • Cloud Firestore:在雲端儲存結構化資料,並在資料變更時立即收到通知。
  • Firebase 安全性規則:可保護資料庫的安全。

部分產品需要特殊設定或透過 Firebase 控制台啟用。

為 Firebase 驗證啟用匿名驗證機制

這個應用程式會使用匿名驗證功能,讓使用者不必先建立帳戶,就能開始使用應用程式。這樣新手上路流程就能順暢無礙。如要進一步瞭解匿名驗證 (以及如何升級為命名帳戶),請參閱「匿名驗證最佳做法」。

  1. 在 Firebase 控制台的左側面板中,依序點選「Build」>「Authentication」。然後按一下「開始使用」啟用 Firebase 驗證
  2. 您現在進入「驗證」資訊主頁。您可以在這裡查看已登入的使用者、設定登入供應商及管理設定。
  3. 選取「登入方式」分頁標籤 (或按這裡直接前往該分頁)。
  4. 在供應商選項中點選「匿名」,將切換鈕切換為「啟用」,然後點選「儲存」

設定 Cloud Firestore

這個 Swift 應用程式會使用 Cloud Firestore 儲存筆記。

以下說明如何在 Firebase 專案中設定 Cloud Firestore:

  1. 在 Firebase 主控台的左側面板中展開「Build」,然後選取「Firestore database」
  2. 按一下 [Create database] (建立資料庫)。
  3. 將「資料庫 ID」設為 (default)
  4. 選取資料庫位置,然後點選「下一步」
    如果是實際的應用程式,建議您選擇靠近使用者的位置。
  5. 按一下「以測試模式啟動」。請詳閱安全性規則免責事項。
    在本程式碼研究室的後續部分,您將新增安全性規則來保護資料。請勿在未新增資料庫安全性規則的情況下公開發行或發布應用程式。
  6. 按一下「建立」

設定 Cloud Storage for Firebase

網頁應用程式會使用 Cloud Storage for Firebase 儲存、上傳及分享圖片。

以下說明如何在 Firebase 專案中設定 Cloud Storage for Firebase:

  1. 在 Firebase 主控台的左側面板中,展開「Build」,然後選取「Storage」
  2. 按一下「開始使用」
  3. 選取預設儲存體值區的位置。
    US-WEST1US-CENTRAL1US-EAST1 中的值區可利用 Google Cloud Storage 的「永遠免費」方案。其他所有位置的值區都會按照 Google Cloud Storage 的定價和用量收費。
  4. 按一下「以測試模式啟動」。請詳閱安全性規則免責事項。
    在本程式碼研究室的後續部分,您將新增安全性規則來保護資料。請勿發布或公開應用程式,否則請為儲存空間值區新增安全規則。
  5. 按一下「建立」

3. 連結行動應用程式

在本節的程式碼研究室中,您將下載簡易記事應用程式的原始碼,並將其連結至剛建立的 Firebase 專案。

下載範例應用程式

  1. 前往 https://github.com/FirebaseExtended/codelab-firestore-vectorsearch-ios,然後將存放區複製到本機電腦
  2. 在 Xcode 中開啟 Notes.xcodeproj 專案

將應用程式連結至 Firebase 專案

您必須在 Firebase 控制台設定應用程式,應用程式才能存取 Firebase 服務。您可以將多個用戶端應用程式連結至同一個 Firebase 專案。舉例來說,假設您建立了 Android 或網頁應用程式,就必須將這些應用程式連結至同一個 Firebase 專案。

如要進一步瞭解 Firebase 專案,請參閱「瞭解 Firebase 專案」一文。

  1. 在 Firebase 控制台中,前往 Firebase 專案的總覽頁面。Firebase 控制台的總覽頁面
  2. 按一下 iOS+ 圖示,即可新增 iOS 應用程式。
  3. 在「Add Firebase to your Apple app」畫面中,插入 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. 選取「視需要複製項目」,確認「新增至指定目標」中已選取「附註」指定目標,然後按一下「完成」在選擇新增檔案選項對話方塊中選取「如有需要,請複製」
  9. 您現在可以在 Firebase 主控台中點選完成其餘設定程序:您在本節開頭下載的範例已安裝 Firebase Apple SDK,並完成初始化設定。按一下「Continue to console」即可完成程序。

執行應用程式

現在可以試用應用程式了!

  1. 回到 Xcode,在 iOS 模擬器上執行應用程式。在「Run Destinations」下拉式選單中,先選取其中一個 iOS 模擬器。在「執行目的地」下拉式選單中選取 iOS 模擬器
  2. 接著,點選「Run」按鈕,或按下 ⌘ + R 鍵。
  3. 應用程式順利在模擬器上啟動後,請加入幾則附註。
  4. 在 Firebase 控制台中前往 Firestore 資料瀏覽器,即可在應用程式中新增筆記時,查看建立的新文件。Cloud Firestore 主控台顯示部分文件,而 iOS 模擬器則顯示相同的文件

4. 安裝 Vector Search with Firestore 擴充功能

在本程式碼研究室的這個部分,您將安裝 Vector Search with Firestore 擴充功能,並根據您正在使用的筆記應用程式需求進行設定。

開始安裝擴充功能

  1. 在 Firestore 部分,點選「Extensions」分頁。在 Firestore 控制台中選取「Firebase Extensions」分頁
  2. 按一下「探索 Extensions Hub」Firestore 控制台中的「Firebase Extensions」分頁
  3. 輸入「向量」。
  4. 按一下「含有 Firestore 擴充功能的 Vector Search」。Firebase Extensions 中心的到達網頁 系統會將您帶往擴充功能的詳細資料頁面,您可以在其中進一步瞭解擴充功能、運作方式、所需的 Firebase 服務,以及如何設定擴充功能。
  5. 按一下「在 Firebase 控制台中安裝」含有 Firestore 擴充功能的 Vector Search 安裝按鈕
  6. 系統會列出所有專案。
  7. 選擇您在本程式碼研究室的第一個步驟中建立的專案。Firebase 專案選取器畫面

設定擴充功能

  1. 請檢查已啟用的 API 和已建立的資源。查看已啟用的 API
  2. 啟用必要服務。啟用必要服務
  3. 所有服務都已啟用後,請點選「下一步」啟用所有服務後,按一下「下一步」
  4. 審查授予這項擴充功能的存取權。
  5. 設定擴充功能:
    • 選取 Vertex AI 做為 LLM
    • 收集路徑notes
    • 預設查詢限制3
    • 輸入欄位名稱text
    • 輸出欄位名稱: 嵌入
    • 狀態欄位名稱:* *status*
    • 嵌入現有文件
    • 更新現有文件
    • Cloud 函式位置us-central1
  6. 按一下「Install extension」即可完成安裝。

這可能需要幾分鐘的時間。在等待安裝作業完成的同時,您可以繼續閱讀教學課程的下一節,瞭解向量嵌入的相關背景資訊。

5. 背景

在等待安裝完成的同時,請參閱以下背景資訊,瞭解 Vector Search with Firestore 擴充功能的運作方式。

向量、嵌入和向量資料庫分別是什麼?

  • 向量是數學物件,用來表示數量的大小和方向。可用於以更容易比較和搜尋的方式呈現資料。
  • 嵌入是代表字詞或詞組意義的向量。這類模型是透過大量文字資料訓練類神經網路,並學習字詞之間的關係而建立。
  • 向量資料庫是專為儲存及搜尋向量資料而最佳化的資料庫。這類向量可進行最鄰近搜尋,也就是找出與指定查詢向量最相似的向量。

向量搜尋如何運作?

向量搜尋會將查詢向量與資料庫中的所有向量進行比較。系統會傳回與查詢向量最相似的向量,做為搜尋結果。

您可以運用多種距離指標,測量兩個向量之間的相似度。最常見的距離指標是餘弦相似度,用於測量兩個向量之間的角度。

6. 試用 Vector Search 與 Firestore 擴充功能

在本程式碼研究室中先前往下載 iOS 應用程式,然後在 Vector Search with Firestore 擴充功能中試用 Firebase 控制台。

閱讀說明文件

Firebase 擴充功能包含說明文件,說明這些功能的運作方式。

  1. 擴充功能安裝完成後,按一下「Get started」按鈕。Firebase 控制台中的 Firebase 擴充功能總覽頁面
  2. 請參閱「這項擴充功能的運作方式」分頁,說明:
    • 如何透過將文件加入 notes 集合來計算文件的嵌入資料
    • 如何透過呼叫 ext-firestore-vector-search-queryCallable 可呼叫函式查詢索引。
    • 或如何透過在 _firestore-vector-search/index/queries 集合中新增查詢文件來查詢索引。
    • 也會說明如何設定自訂嵌入函式。如果擴充功能支援的 LLM 都不符合您的需求,而且您想使用其他大型語言模型計算嵌入,這種做法就非常實用。搭配使用 Firestore 擴充功能的 Vector Search 說明文件
  3. 按一下「Cloud Firestore 資訊主頁」連結,前往 Firestore 執行個體
  4. 前往 _firestore-vector-search/index 文件。這應該會顯示擴充功能已完成計算所有筆記文件的嵌入資料。您可以在本程式碼研究室的先前步驟中建立這些筆記文件。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 Logging Explorer 中,您現在應該會看到「FAILED_PRECONDITION: Missing vector index configuration」(「FAILED_PRECONDITION:向量索引設定遺漏」) 的錯誤訊息。請使用下列 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。在文件清單中尋找文件 ID

7. 實作語意搜尋

最後,您可以將行動應用程式連結至 Vector Search with Firestore 擴充功能,並實作語意搜尋功能,讓使用者能透過自然語言查詢搜尋筆記。

連結可呼叫的函式,以便執行查詢

Vector Search with Firestore 擴充功能包含 Cloud Functions,您可以透過行動應用程式呼叫該函式,查詢先前在本程式碼研究室中建立的索引。在這個步驟中,您將在行動應用程式和這個可呼叫的函式之間建立連線。Firebase 的 Swift SDK 包含 API,可讓您順暢地呼叫遠端函式。

  1. 返回 Xcode,並確認您在本程式碼研究室中先前步驟複製的專案。
  2. 開啟 NotesRepository.swift 檔案。
  3. 找出包含 private lazy var vectorSearchQueryCallable: Callable = functions.httpsCallable("") 的那一行

如要叫用可呼叫的 Cloud 函式,您必須提供要呼叫的函式名稱。

  1. 前往專案的 Firebase 主控台,然後在「Build」部分開啟「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 []
      }
    }
    

連結 UI

如要讓使用者搜尋筆記,您可以在筆記清單畫面中加入搜尋列。使用者輸入搜尋字詞時,您需要叫用先前步驟中實作的 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 秒的時間來debounce 使用者輸入內容。也就是說,只有在使用者暫停輸入超過 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. 您應該會看到先前在本程式碼研究室中新增的應用程式附註,以及透過 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 擴充功能管理 Vector Search
  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. 恭喜!

恭喜您順利完成本程式碼研究室!

在本程式碼研究室中,您已瞭解如何:

  • 設定已啟用語意搜尋功能的 Cloud Firestore 資料庫。
  • 建立簡單的 SwiftUI 應用程式,與資料庫互動。
  • 使用 SwiftUI 的可搜尋檢視畫面修飾符和工作修飾符,實作搜尋列。
  • 呼叫 Cloud 函式,使用 Firestore SDK 的 Callable 介面,在資料庫上執行語義搜尋。

有了本程式碼研究室的知識,您現在可以建構功能強大的應用程式,運用 Cloud Firestore 的語意搜尋功能,為使用者提供更直覺且有效率的搜尋體驗。

如要進一步瞭解 Firestore 的新向量欄位,以及如何計算向量嵌入資料,請參閱說明文件