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 プロジェクトを作成する
- Google アカウントを使用して Firebase コンソールにログインします。
- ボタンをクリックして新しいプロジェクトを作成し、プロジェクト名(例:
Firestore Vector Search Codelab
)を入力します。
- [続行] をクリックします。
- Firebase の利用規約が表示されたら、内容を読み、同意して [続行] をクリックします。
- (省略可)Firebase コンソールで AI アシスタンス(「Gemini in Firebase」)を有効にします。
- この Codelab では 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 コンソールの左側のパネルで、[ビルド] > [Authentication] をクリックします。[開始する] をクリックします。
- 認証ダッシュボードが表示されます。ここで、登録ユーザーの確認、ログイン プロバイダの設定、設定の管理を行うことができます。
- [ログイン方法] タブを選択します(または、ここをクリックしてタブに直接移動します)。
- プロバイダ オプションから [匿名] をクリックし、スイッチを [有効にする] に切り替えて、[保存] をクリックします。
Cloud Firestore を設定する
この Swift アプリケーションは、Cloud Firestore を使用してメモを保存します。
Firebase プロジェクトで Cloud Firestore を設定する方法は次のとおりです。
- Firebase コンソールの左側のパネルで [ビルド] を展開し、[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 の「無料枠」を利用できます。他のすべてのロケーションのバケットは、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 アプリを追加します。
- [Apple アプリに Firebase を追加] 画面で、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 シミュレータでアプリを実行します。[実行先] プルダウンで、まず iOS シミュレータのいずれかを選択します。
- [実行] ボタンをクリックするか、⌘ + R キーを押します。
- アプリがシミュレータで正常に起動したら、メモをいくつか追加します。
- Firebase コンソールで Firestore データ ブラウザに移動すると、アプリで新しいメモを追加するたびに新しいドキュメントが作成されるのを確認できます。
4. Firestore を使用したベクトル検索拡張機能をインストールする
この Codelab のこのパートでは、Vector Search with Firestore 拡張機能をインストールし、作成中のメモアプリの要件に合わせて構成します。
拡張機能のインストールを開始する
- [Firestore] セクションで、[拡張機能] タブをクリックします。
- [Extensions Hub を見る] をクリックします。
- 「vector」と入力します。
- [Vector Search with Firestore extension] をクリックします。
拡張機能の詳細ページが表示されます。このページでは、拡張機能の詳細、仕組み、必要な Firebase サービス、構成方法について確認できます。
- [Firebase コンソールにインストール] をクリックします。
- すべてのプロジェクトのリストが表示されます。
- この Codelab の最初の手順で作成したプロジェクトを選択します。
拡張機能を設定する
- 有効な API と作成済みのリソースを確認します。
- 必要なサービスを有効にします。
- すべてのサービスが有効になったら、[次へ] をクリックします。
- この拡張機能に付与されたアクセス権を確認します。
- 拡張機能を構成します。
- LLM として Vertex AI を選択します。
- コレクション パス: notes
- デフォルトのクエリ上限: 3
- 入力フィールド名: text
- 出力フィールド名: embedding
- ステータス フィールド名:* *status*
- 既存のドキュメントを埋め込む: はい
- 既存のドキュメントを更新する: はい
- Cloud Functions のロケーション: us-central1
- [拡張機能をインストール] をクリックして、インストールを完了します。
完了までに数分かかることがあります。インストールが完了するまで、チュートリアルの次のセクションに進んで、ベクトル エンベディングに関する背景情報を読んでください。
5. 背景
インストールが完了するまで、Firestore 拡張機能を使用したベクトル検索の仕組みについて説明します。
ベクトル、エンベディング、ベクトル データベースとは
- ベクトルは、量の大きさと方向を表す数学的オブジェクトです。これらを使用して、比較や検索が容易になるようにデータを表現できます。
- エンベディングは、単語やフレーズの意味を表すベクトルです。これらは、大規模なテキスト コーパスでニューラル ネットワークをトレーニングし、単語間の関係を学習することで作成されます。
- ベクトル データベースは、ベクトル データの保存と検索用に最適化されたデータベースです。これらを使用すると、効率的な最近傍探索が可能になります。これは、指定されたクエリベクトルに最も類似するベクトルを見つけるプロセスです。
ベクトル検索の仕組み
ベクトル検索は、クエリベクトルをデータベース内のすべてのベクトルと比較することで機能します。クエリベクトルに最も類似したベクトルが検索結果として返されます。
2 つのベクトルの類似度は、さまざまな距離指標を使用して測定できます。最も一般的な距離指標はコサイン類似度です。これは、2 つのベクトル間の角度を測定します。
6. Firestore 拡張機能を使用したベクトル検索を試す
この Codelab の前半でダウンロードした iOS アプリで Vector Search with Firestore 拡張機能を使用する前に、Firebase コンソールで拡張機能を試すことができます。
ドキュメントを読む
Firebase 拡張機能には、その仕組みに関するドキュメントが含まれています。
- 拡張機能のインストールが完了したら、[Get started] ボタンをクリックします。
- [この拡張機能の動作] タブをご覧ください。次の内容が説明されています。
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
コレクションに移動し、中央の列にある [+ ドキュメントを追加] をクリックします。 - [自動 ID] をクリックして、新しい一意のドキュメント ID を生成します。
- 文字列型の
text
という名前のフィールドを追加し、[value] フィールドにテキストを貼り付けます。これは、lorem ipsum などのランダムなテキストではないことが重要です。たとえば、ニュース記事を選択します。 - [保存] をクリックします。
- 拡張機能がステータス フィールドを追加して、データの処理中であることを示していることに注目してください。
- しばらくすると、値が
vector<768>
の新しいフィールドembedding
が表示されます。
クエリを実行する
Firestore 拡張機能を使用したベクトル検索には、アプリを接続せずにドキュメント インデックスに対してクエリを実行できる便利な機能があります。
- Firebase コンソールの Firestore セクションで、
_firestore-vector-search/index
ドキュメントに移動します。 - [+ コレクションを開始]
をクリックします。
queries
という名前の新しいサブコレクションを作成します。- 新しいドキュメントを作成し、
query
フィールドをドキュメントのいずれかに含まれるテキストに設定します。これは、「Swift で Firestore ドキュメントをマッピングするにはどうすればよいですか?」のようなセマンティック クエリに最適です(追加したメモの少なくとも 1 つに、このトピックについて説明するテキストが含まれている場合)。 - ステータスにエラーが表示されることがあります。
- これは、インデックスがないことが原因です。不足しているインデックス構成を設定するには、このリンクに沿ってプロジェクトの Google Cloud コンソールに移動し、リストからプロジェクトを選択します。
- Cloud Log エクスプローラに、「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 コンソールに移動し、[ビルド] セクションの [関数] メニュー項目を開きます。
- 拡張機能によってインストールされた関数のリストが表示されます。
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 秒間デバウンスするよう指示します。つまり、ユーザーが 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 を押す(または [実行] ボタンをクリックする)と、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 コンソールの [拡張機能] セクションに移動します。
- [Manage] ->
をクリックします。
- [この拡張機能の仕組み] セクションには、入出力パラメータの仕様が記載されています。
- 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 インデックスの更新が必要です。
コマンドラインから次のコマンドを実行して、userId
と embedding
フィールドのベクトル エンベディングの両方を含む新しい 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 の searchable ビュー修飾子と task 修飾子を使用して検索バーを実装します。
- Firestore SDK の Callable インターフェースを使用して、Cloud 関数を呼び出し、データベースでセマンティック検索を実行します。
この Codelab で学んだ知識を活用して、Cloud Firestore のセマンティック検索機能を活用し、ユーザーに直感的で効率的な検索エクスペリエンスを提供する強力なアプリケーションを構築できるようになりました。
Firestore の新しいベクトル フィールドとベクトル エンベディングの計算方法の詳細については、ドキュメントをご覧ください。