1. 概要
この Codelab では、Firestore ベクトル類似性検索を使用して、高度な検索機能をアプリに追加する方法を学びます。Swift と SwiftUI で記述したメモ作成アプリ用のセマンティック検索機能を実装します。
ラボの内容
- Firestore 拡張機能を使用したベクトル検索をインストールしてベクトル エンベディングを計算する方法。
- Swift アプリケーションから Firebase Cloud Functions を呼び出す方法。
- ログイン ユーザーに基づいてデータを事前にフィルタする方法
必要なもの
- Xcode 15.3
- Codelab のサンプルコード。この Codelab の後のステップでダウンロードします。
2. Firebase プロジェクトを作成して設定する
Firebase Vector Search 拡張機能を使用するには Firebase プロジェクトが必要です。Codelab のこのパートでは、新しい Firebase プロジェクトを作成し、Cloud Firestore や Firebase Authentication などの必要なサービスを有効にします。
Firebase プロジェクトを作成する
- Firebase にログインします。
- Firebase コンソールで [プロジェクトを追加] をクリックし、プロジェクトに「Firestore Vector Search Lab」という名前を付けます。
- プロジェクト作成オプションをクリックします。Firebase の利用規約が表示されたら同意します。
- このアプリではアナリティクスを使用しないため、Google アナリティクスの画面で [このプロジェクトの Google アナリティクスを有効にする] チェックボックスをオフにします。
- 最後に、[プロジェクトを作成] をクリックします。
Firebase プロジェクトの詳細については、Firebase プロジェクトについて理解するをご覧ください。
コンソールで Firebase プロダクトを有効にして設定する
作成しているアプリでは、Apple アプリで使用できる次の Firebase プロダクトを使用します。
- ユーザーがアプリに簡単にログインできるようにする Firebase Authentication。
- 構造化されたデータをクラウドに保存し、データが変更されたときに即座に通知を受け取る Cloud Firestore。
- Firebase セキュリティ ルールでデータベースを保護します。
これらのプロダクトの一部は、特別な構成を必要とするか、Firebase コンソールを使用して有効にする必要があります。
Firebase Authentication で匿名認証を有効にする
このアプリケーションは匿名認証を使用しているため、ユーザーは最初にアカウントを作成しなくてもアプリを使い始めることができます。これにより、オンボーディング プロセスがスムーズになります。匿名認証の詳細と名前付きアカウントへのアップグレード方法については、匿名認証のベスト プラクティスをご覧ください。
- Firebase コンソールの左側のパネルで、[ビルド] > [認証] をクリックします。[開始] をクリックします。
- 認証ダッシュボードでは、登録ユーザーの確認、ログイン プロバイダの設定、設定の管理を行えます。
- [ログイン方法] タブを選択します(または、こちらをクリックしてタブに直接移動します)。
- プロバイダ オプションから [Anonymous] をクリックし、スイッチを [Enable] に切り替えてから [Save] をクリックします。
Cloud Firestore を設定する
この Swift アプリケーションでは Cloud Firestore を使用してメモを保存します。Cloud Firestore を設定する方法は次のとおりです。
- Firebase コンソールの左側のパネルで、[構築] > [Firestore Database] をクリックします。[データベースを作成] をクリックします。
- データベースのロケーションを選択し、Gemini を利用できるロケーションを選択します(us-central1 を使用できます)。ただし、この場所は後で変更できません。[Next] をクリックします。
- [テストモードで開始] オプションを選択します。セキュリティ ルールに関する免責条項を確認します。テストモードでは、開発中にデータベースに自由に書き込めます。
- [作成] をクリックしてデータベースを作成します。
3. モバイルアプリを接続する
Codelab のこのセクションでは、シンプルなメモ作成アプリのソースコードをダウンロードし、先ほど作成した Firebase プロジェクトに接続します。
サンプルアプリをダウンロードする
- https://github.com/FirebaseExtended/codelab-firestore-vectorsearch-ios にアクセスし、リポジトリのクローンをローカルマシンに作成します。
- Xcode で Notes.xcodeproj プロジェクトを開きます。
アプリを Firebase プロジェクトに接続する
アプリが Firebase サービスにアクセスできるようにするには、Firebase コンソールでアプリを設定する必要があります。複数のクライアント アプリケーションを同じ Firebase プロジェクトに接続できます。たとえば、Android アプリやウェブアプリを作成する場合、それらを同じ Firebase プロジェクトに接続する必要があります。
Firebase プロジェクトの詳細については、Firebase プロジェクトについて理解するをご覧ください。
- Firebase コンソールで、Firebase プロジェクトの概要ページに移動します。
- iOS+ アイコンをクリックして iOS アプリを追加します。
- [Add Firebase to your Apple app] 画面で、Xcode プロジェクトのバンドル ID(com.google.firebase.codelab.Notes)を入力します。
- 必要に応じて、アプリのニックネームを入力できます(iOS 向けの注意事項)。
- [アプリの登録] をクリックして次のステップに進みます。
- GoogleServices-Info.plist ファイルをダウンロードします。
- GoogleServices-Info.plist を Xcode プロジェクトの Notes フォルダにドラッグします。そのためには、Assets.xcassets ファイルの下にドロップすると便利です。
- [Copy items if needed] を選択し、[Add to targets] で [Notes] ターゲットが選択されていることを確認し、[Finish] をクリックします。
- これで、Firebase コンソールで残りの設定プロセスを進めることができます。このセクションの冒頭でダウンロードしたサンプルには、すでに Firebase Apple SDK がインストールされ、初期化が設定されています。[コンソールに進む] をクリックすると、プロセスが終了します。
アプリを実行する
それでは、アプリを使ってみましょう。
- Xcode に戻り、iOS Simulator でアプリを実行します。[Run Destinations] プルダウンで、まずいずれかの iOS シミュレータを選択します。
- [実行] ボタンをクリックするか、⌘+R キーを押します。
- Simulator でアプリが正常に起動したら、いくつかのメモを追加します。
- Firebase コンソールで Firestore データブラウザに移動すると、アプリに新しいメモを追加したときに、新しいドキュメントが作成されることを確認できます。
4. Vector Search with Firestore 拡張機能をインストールする
Codelab のこのパートでは、Vector Search with Firestore 拡張機能をインストールし、作業中のメモ作成アプリの要件に合わせて構成します。
拡張機能のインストールを開始する
- 引き続き [Firestore] セクションで [拡張機能] タブをクリックします。
- [Extension Extensions Hub を見る] をクリックします。
- 「ベクター」と入力します。
- [Firestore 拡張機能を使用したベクトル検索] をクリックします。拡張機能の詳細ページが表示されます。このページでは、拡張機能の詳細、仕組み、必要な Firebase サービス、設定方法を確認できます。
- [Firebase コンソールでインストール] をクリックします。
- すべてのプロジェクトのリストが表示されます。
- この Codelab の最初のステップで作成したプロジェクトを選択します。
拡張機能を設定する
Firebase Extensions は Cloud Functions for Firebase を使用するため、プロジェクトを従量課金制の Blaze プランにする必要があります。Firestore 拡張機能でベクトル検索を使用するには、プロジェクトをアップグレードする必要があります。
- [プロジェクトをアップグレードする] をクリックして続行します。
- 既存の請求先アカウントを選択するか、新しいアカウントを作成します。[続行] をクリックします。
- 予算(例: 1,000 円)を設定し、[続行]、[購入] の順にクリックします。
- 有効になっている API と作成されたリソースを確認します。
- 必要なサービスを有効にします。
- Cloud Storage を有効にするときに、セキュリティ ルールにテストモードを選択します。
- Cloud Storage が Cloud Firestore インスタンスと同じロケーションを使用することを確認します。
- すべてのサービスが有効になったら、[次へ] をクリックします。
- この拡張機能に付与されているアクセス権を確認します。
- 拡張機能を構成します。
- LLM として [Vertex AI] を選択します。
- コレクション パス: notes
- デフォルトのクエリの上限: 3
- 入力フィールド名: text
- 出力フィールド名: embedding
- ステータス フィールド名:* *status*
- 既存のドキュメントを埋め込む: あり
- 既存のドキュメントを更新する: はい
- Cloud Functions の関数のロケーション: us-central1
- [拡張機能をインストール] をクリックしてインストールを完了します。
これには数分かかることがあります。インストールの完了を待っている間は、チュートリアルの次のセクションに進んで、ベクトル エンベディングに関する背景情報をお読みください。
5. 背景
インストールの完了を待つ間に、Firestore 拡張機能を使用したベクトル検索の仕組みに関する背景情報をいくつか紹介します。
ベクトル、エンべディング、ベクトル データベースとは
- ベクトルは、数値の大きさと方向を表す数学的オブジェクトです。比較や検索がしやすいようにデータを表現するために使用できます。
- エンベディングは、単語やフレーズの意味を表すベクトルです。LLM は、大規模なテキストのコーパスでニューラル ネットワークをトレーニングし、単語間の関係を学習することで作成されます。
- ベクトル データベースは、ベクトルデータの保存と検索用に最適化されたデータベースです。これにより、特定のクエリベクトルに最も類似したベクトルを見つけるプロセスである、効率的な最近傍探索が可能になります。
ベクトル検索の仕組み
ベクトル検索は、クエリベクトルをデータベース内のすべてのベクトルと比較することで機能します。クエリベクトルに最も類似したベクトルが検索結果として返されます。
2 つのベクトル間の類似性は、さまざまな距離指標を使用して測定できます。最も一般的な距離指標はコサイン類似度で、2 つのベクトル間の角度を測定します。
6. Firestore 拡張機能でベクトル検索を試す
この Codelab で前にダウンロードした iOS アプリで Firestore によるベクトル検索拡張機能を使用する前に、Firebase コンソールで拡張機能を試すことができます。
ドキュメントを読む
Firebase Extensions には、その仕組みに関するドキュメントが含まれています。
- 拡張機能のインストールが完了したら、[使ってみる] ボタンをクリックします。
- [この拡張機能の仕組み] タブで、次の内容について確認できます。
- ドキュメントのエンベディングを
notes
コレクションに追加して、エンベディングを計算する方法 - 呼び出し可能関数
ext-firestore-vector-search-queryCallable
を呼び出してインデックスをクエリする方法 - または、クエリ ドキュメントを
_firestore-vector-search/index/queries
コレクションに追加してインデックスをクエリする方法。 - また、カスタム エンベディング関数の設定方法についても説明します。これは、拡張機能でサポートされている LLM がいずれも要件を満たしておらず、別の LLM を使用してエンベディングを計算したい場合に役立ちます。
- ドキュメントのエンベディングを
- Cloud Firestore ダッシュボードのリンクをクリックして Firestore インスタンスに移動します。
_firestore-vector-search/index
ドキュメントに移動します。拡張機能によって、この Codelab の前のステップで作成したすべてのメモ ドキュメントのエンベディングの計算が完了したことがわかります。- これを確認するには、メモのドキュメントの 1 つを開くと、
vector<768>
型のembedding
という名前の追加フィールドと、status
フィールドが表示されます。
サンプル ドキュメントを作成する
Firebase コンソールで新しいドキュメントを作成して、拡張機能の動作を確認できます。
- 引き続き Firestore データブラウザで、
notes
コレクションに移動し、中央の列にある [+ Add document] をクリックします。 - [自動 ID] をクリックして、新しい一意のドキュメント ID を生成します。
- string 型の
text
という名前のフィールドを追加し、[value] フィールドにテキストを貼り付けます。これは lorem ipsum やその他のランダムなテキストではないことが重要です。たとえば、ニュース記事を選択します。 - [保存] をクリックします。
- この拡張機能では、データを処理していることを示すステータス フィールドが追加されています。
- しばらくすると、値が
vector<768>
の新しいフィールドembedding
が表示されます。
クエリを実行する
Firestore 拡張機能を使用したベクトル検索には、アプリを接続せずにドキュメント インデックスをクエリできる便利な機能があります。
- Firebase コンソールの Firestore セクションで、
_firestore-vector-search/index
ドキュメントに移動します。 - [+ コレクションを開始] をクリックします。
queries
という名前の新しいサブコレクションを作成します。- 新しいドキュメントを作成し、いずれかのドキュメントに含まれるテキストに
query
フィールドを設定します。これは、「Firestore ドキュメントを Swift とマッピングする方法」のようなセマンティック クエリに最適です(追加したメモの少なくとも 1 つにこのトピックに関するテキストが含まれている場合)。 - ステータスにエラーが表示される場合があります。
- これはインデックスがないためです。不足しているインデックス構成を設定するには、こちらのリンクからプロジェクトの Google Cloud コンソールに移動し、リストからプロジェクトを選択します。
- Cloud ログ エクスプローラに「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 拡張機能を使用したベクトル検索に接続し、セマンティック検索機能を実装して、ユーザーが自然言語クエリを使用してメモを検索できるようにします。
呼び出し可能関数を接続してクエリを実行する
Firestore のベクトル検索拡張機能には、モバイルアプリから呼び出して、この Codelab の前半で作成したインデックスをクエリできる Cloud Functions の関数が含まれています。このステップでは、モバイルアプリとこの呼び出し可能関数の間の接続を確立します。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 [] } }
UI を接続する
ユーザーがメモを検索できるようにするには、メモリスト画面に検索バーを実装します。ユーザーが検索キーワードを入力したときに、前のステップで実装した performQuery
メソッドを呼び出す必要があります。SwiftUI が提供する searchable
および task
ビュー修飾子のおかげで、数行のコードだけで済みます。
- まず、
NotesListScreen.swift
を開きます - リストビューに検索ボックスを追加するには、
.navigationTitle("Notes")
行のすぐ上に.searchable(text: $searchTerm, prompt: "Search")
ビュー修飾子を追加します。 - 次に、そのすぐ下に次のコードを追加して、SEARCH 関数を呼び出します。
.task(id: searchTerm, debounce: .milliseconds(800)) {
await notesRepository.semanticSearch(searchTerm: searchTerm)
}
このコード スニペットでは、semanticSearch
メソッドを非同期で呼び出します。タイムアウトを 800 ミリ秒に設定すると、ユーザーの入力を 0.8 秒デバウンスするようにタスク修飾子に指示します。つまり、semanticSearch
はユーザーが入力を 0.8 秒以上一時停止した場合にのみ呼び出されます。
コードは次のようになります。
...
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 キーを押す(または [実行] ボタンをクリック)して、iOS シミュレータでアプリを起動します
- この Codelab の前半でアプリで追加したメモと、Firebase コンソールで追加したメモが表示されます。
- [メモ] リストの上部に検索フィールドが表示されます。
- 追加したドキュメントのいずれかに出現する用語を入力します。繰り返しになりますが、これは「Swift から非同期 Firebase API を呼び出すにはどうすればよいか」のようなセマンティック クエリに最適です(追加したメモの少なくとも 1 つにこのトピックに関するテキストが含まれている場合)。
- 検索結果が表示されるはずなのに、リストビューは空になり、Xcode コンソールに「The function was called with an invalid arguments」というエラー メッセージが表示されます。
間違った形式でデータを送っている可能性があります。
エラー メッセージを分析する
- 問題を確認するには、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
は、拡張機能のレスポンスの構造と一致します。 - 呼び出し可能関数の仕様を確認し、入力型と出力型を更新します。
private lazy var vectorSearchQueryCallable: Callable<QueryRequest, QueryResponse> = functions.httpsCallable("ext-firestore-vector-search-queryCallable")
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 [] } }
アプリを再実行する
- アプリを再実行する
- メモに含まれている語句を含む検索クエリを入力します
- フィルタされたメモのリストが表示されます。
ユーザーデータを事前にフィルタする
お祝いのためにダンスでブレイクアウトする前に、アプリの最新版には問題があります。結果セットにはすべてのユーザーのデータが含まれています。
別のシミュレータでアプリを実行し、ドキュメントを追加することでこれを確認できます。新しいドキュメントはそのシミュレータにのみ表示されます。別のシミュレータでアプリを再度実行すると、最初に作成したドキュメントのみが表示されます。
検索を行うと、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 インデックスを更新する必要があります。
コマンドラインから次のコマンドを実行して、embedding
フィールドに userId
とベクトル エンベディングの両方を含む新しい Firestore インデックスを定義します。
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 の検索可能なビュー修飾子とタスク修飾子を使用して検索バーを実装します。
- Cloud Functions の関数を呼び出し、Firestore SDK の呼び出し可能インターフェースを使用して、データベースに対してセマンティック検索を実行します。
この Codelab で学んだ知識をもとに、Cloud Firestore のセマンティック検索機能を活用した強力なアプリケーションを構築し、より直感的で効率的な検索エクスペリエンスを提供できるようになりました。
Firestore の新しいベクトル フィールドと、ベクトル エンベディングの計算方法の詳細については、ドキュメントをご覧ください。