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 ベクトル検索ラボという名前を付けます。
- プロジェクト作成オプションをクリックします。プロンプトが表示されたら、Firebase の利用規約に同意します。
- Google アナリティクスの画面で、[このプロジェクトで Google アナリティクスを有効にする] チェックボックスをオフにします。このアプリではアナリティクスを使用しないためです。
- 最後に、[プロジェクトを作成] をクリックします。
Firebase プロジェクトの詳細については、Firebase プロジェクトについて理解するをご覧ください。
Firebase の料金プランをアップグレードする
Firebase Extensions とその基盤となるクラウド サービスを使用するには、Firebase プロジェクトが従量課金制(Blaze)のお支払いプランに登録されている必要があります。つまり、Cloud 請求先アカウントにリンクされている必要があります。
- Cloud 請求先アカウントには、クレジット カードなどのお支払い方法が必要です。
- Firebase と Google Cloud を初めて使用する場合は、$300 のクレジットと無料トライアル用 Cloud 請求先アカウントを利用できるかどうか確認してください。
- この Codelab をイベントの一環として実施する場合は、利用可能な Cloud クレジットがあるかどうかを主催者に確認してください。
プロジェクトを Blaze プランにアップグレードする手順は次のとおりです。
- Firebase コンソールで、プランをアップグレードを選択します。
- Blaze プランを選択します。画面上の手順に沿って、Cloud 請求先アカウントをプロジェクトにリンクします。
このアップグレードの一環として Cloud 請求先アカウントを作成する必要があった場合は、Firebase コンソールでアップグレード フローに移動してアップグレードを完了する必要があります。
コンソールで Firebase プロダクトを有効にして設定する
作成しているアプリでは、Apple アプリで使用できる次の Firebase プロダクトを使用します。
- ユーザーがアプリに簡単にログインできるようにする Firebase Authentication。
- 構造化されたデータをクラウドに保存し、データが変更されたときに即座に通知を受け取る Cloud Firestore。
- データベースを保護するための Firebase セキュリティ ルール。
この中には、特別な設定が必要になるプロダクトや、Firebase コンソールを使用して有効化する必要があるプロダクトがあります。
Firebase Authentication で匿名認証を有効にする
このアプリは匿名認証を使用しているため、ユーザーはアカウントを作成せずにアプリの使用を開始できます。これにより、スムーズなオンボーディング プロセスが実現します。匿名認証(および名前付きアカウントにアップグレードする方法)の詳細については、匿名認証のベスト プラクティスをご覧ください。
- Firebase コンソールの左側のパネルで、[Build] > [Authentication] をクリックします。[開始] をクリックします。
- 認証ダッシュボードでは、登録ユーザーの確認、ログイン プロバイダの設定、設定の管理を行えます。
- [ログイン方法] タブを選択します(または、ここをクリックしてタブに直接移動します)。
- プロバイダ オプションで [匿名] をクリックし、スイッチを [有効にする] に切り替えて、[保存] をクリックします。
Cloud Firestore を設定する
この Swift アプリケーションは、Cloud Firestore を使用してメモを保存します。
Firebase プロジェクトで Cloud Firestore を設定する方法は次のとおりです。
- Firebase コンソールの左側のパネルで [Build] を開き、[Firestore データベース] を選択します。
- [データベースを作成] をクリックします。
- [データベース ID] は
(default)
のままにします。 - データベースのロケーションを選択し、[次へ] をクリックします。
実際のアプリの場合は、ユーザーに近いロケーションを選択します。 - [テストモードで開始] をクリックします。セキュリティ ルールに関する免責条項を確認します。
この Codelab の後半で、セキュリティ ルールを追加してデータを保護します。データベースのセキュリティ ルールを追加せずに、アプリを配布または公開しないでください。 - [作成] をクリックします。
Cloud Storage for Firebase を設定する
このウェブアプリは Cloud Storage for Firebase を使用して画像ファイルを保存、アップロード、共有します。
Firebase プロジェクトで Cloud Storage for Firebase を設定する方法は次のとおりです。
- Firebase コンソールの左側のパネルで [ビルド] を開き、[Storage] を選択します。
- [開始] をクリックします。
- デフォルトの Storage バケットのロケーションを選択します。
US-WEST1
、US-CENTRAL1
、US-EAST1
のバケットでは、Google Cloud Storage の「Always Free」階層を利用できます。他のすべてのロケーションのバケットは、Google Cloud Storage の料金と使用量に従います。 - [テストモードで開始] をクリックします。セキュリティ ルールに関する免責条項を確認します。
この Codelab の後半で、セキュリティ ルールを追加してデータを保護します。Storage バケットのセキュリティ ルールを追加せずに、アプリを配布または公開しないでください。 - [作成] をクリックします。
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 necessary] を選択し、[Add to targets] で [Notes] ターゲットが選択されていることを確認して、[Finish] をクリックします。
- Firebase コンソールで、残りの設定プロセスを進めることができます。このセクションの冒頭でダウンロードしたサンプルには、Firebase Apple SDK がすでにインストールされており、初期化も設定されています。[コンソールに進む] をクリックしてプロセスを完了します。
アプリを実行する
それでは、アプリを使ってみましょう。
- Xcode に戻り、iOS シミュレータでアプリを実行します。[実行先] プルダウンで、まずいずれかの iOS シミュレータを選択します。
- [実行] ボタンをクリックするか、⌘ + R キーを押します。
- Simulator でアプリが正常に起動したら、いくつかのメモを追加します。
- Firebase コンソールで Firestore データブラウザに移動すると、アプリで新しいメモを追加するたびに作成される新しいドキュメントを確認できます。
4. Vector Search with Firestore 拡張機能をインストールする
この Codelab のパートでは、Firestore を使用したベクトル検索拡張機能をインストールし、作成中のメモアプリの要件に合わせて構成します。
拡張機能のインストールを開始する
- [Firestore] セクションで、[拡張機能] タブをクリックします。
- [Extensions Hub を見る] をクリックします。
- 「vector」と入力します。
- [Firestore 拡張機能によるベクトル検索] をクリックします。拡張機能の詳細ページが表示され、この拡張機能の詳細、仕組み、必要な Firebase サービス、設定方法を確認できます。
- [Firebase コンソールでインストール] をクリックします。
- すべてのプロジェクトのリストが表示されます。
- この Codelab の最初の手順で作成したプロジェクトを選択します。
拡張機能の設定
- 有効な API と作成済みのリソースを確認します。
- 必要なサービスを有効にします。
- すべてのサービスが有効になったら、[次へ] をクリックします。
- この拡張機能に付与されているアクセス権を確認します。
- 拡張機能を構成します。
- 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 の前の手順で作成したすべてのメモドキュメントのエンベディングの計算が拡張機能によって完了したことが示されます。- これを確認するには、メモドキュメントのいずれかを開きます。
vector<768>
型のembedding
という名前の追加フィールドと、status
フィールドが表示されます。
サンプル ドキュメントを作成する
Firebase コンソールで新しいドキュメントを作成して、拡張機能の動作を確認できます。
- 引き続き Firestore データブラウザで、
notes
コレクションに移動し、中央の列にある [+ Add document] をクリックします。 - [自動 ID] をクリックして、新しい一意のドキュメント ID を生成します。
- 文字列型の
text
という名前のフィールドを追加し、[値] フィールドにテキストを貼り付けます。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. セマンティック検索を実装する
これで、モバイルアプリを Vector Search with Firestore 拡張機能に接続し、ユーザーが自然言語クエリを使用してメモを検索できるセマンティック検索機能を実装できます。
クエリを実行する呼び出し可能関数を接続する
Firestore のベクトル検索拡張機能には、モバイルアプリから呼び出して、この Codelab の前半で作成したインデックスをクエリできる Cloud Functions の関数が含まれています。このステップでは、モバイルアプリとこの呼び出し可能な関数の間に接続を確立します。Firebase の Swift SDK には、リモート関数の呼び出しをシームレスに行う API が含まれています。
- Xcode に戻り、この Codelab の前のステップでクローンを作成したプロジェクトにいることを確認します。
NotesRepository.swift
ファイルを開きます。private lazy var vectorSearchQueryCallable: Callable
を含む行を見つけます。= functions.httpsCallable("")
呼び出し可能な Cloud Functions の関数を呼び出すには、呼び出す関数の名前を指定する必要があります。
- プロジェクトの Firebase コンソールに移動し、[Build] セクションで [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")
ビュー修飾子を追加します。 - 次に、検索関数を呼び出すため、そのすぐ下に次のコードを追加します。
.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 argument」というエラー メッセージが表示される
これは、データの形式が正しくないことを意味します。
エラー メッセージを分析する
- 問題を確認するには、Firebase コンソールに移動します。
- [関数] セクションに移動します。
ext-firestore-vector-search-queryCallable
関数を見つけ、縦に並んだ 3 つの点をクリックしてオーバーフロー メニューを開きます。- [ログを表示] を選択してログ エクスプローラに移動します。
- エラーが表示されます
Unhandled error ZodError: [
{
"code": "invalid_type",
"expected": "object",
"received": "string",
"path": [],
"message": "Expected object, received string"
}
]
これは、データの形式が間違っていることを意味します。
適切なデータ型を使用する
拡張機能がパラメータに想定している形式を確認するには、拡張機能のドキュメントをご覧ください。
- Firebase コンソールの [Extensions] セクションに移動します。
- [管理 ->] をクリックします。
- 入力パラメータと出力パラメータの仕様については、この拡張機能の仕組みをご覧ください。
- 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 の Callable インターフェースを使用してデータベースでセマンティック検索を実行します。
この Codelab で学んだ知識を活用して、Cloud Firestore のセマンティック検索機能を活用し、より直感的で効率的な検索エクスペリエンスをユーザーに提供する強力なアプリケーションを構築できます。
Firestore の新しいベクトル フィールドとベクトル エンベディングの計算方法について詳しくは、ドキュメントをご覧ください。