Codelab - Firestore、ベクトル検索、Langchain、Gemini を使用してコンテキストに応じたヨガのポーズ レコメンダー アプリを作成する(Node.js バージョン)

1. はじめに

この Codelab では、ベクトル検索を使用してヨガのポーズをおすすめするアプリを作成します。

この Codelab では、次の手順で進めていきます。

  1. ヨガのポーズの既存の Hugging Face データセット(JSON 形式)を利用します。
  2. Gemini を使用して各ポーズの説明を生成するフィールドの説明を追加して、データセットを拡張します。
  3. 生成されたエンベディングを使用して、ヨガのポーズデータを Firestore コレクションのドキュメントのコレクションとして読み込みます。
  4. Firestore で複合インデックスを作成して、ベクトル検索を可能にします。
  5. 以下に示すように、Node.js アプリケーションでベクトル検索を使用して、すべてを統合します。

84e1cbf29cbaeedc.png

演習内容

  • ベクトル検索を使用してヨガのポーズをおすすめするウェブ アプリケーションを設計、構築、デプロイします。

学習内容

  • Gemini を使用してテキスト コンテンツを生成する方法と、この Codelab のコンテキスト内でヨガのポーズの説明を生成する方法
  • Hugging Face の拡張データセットのレコードをベクトル エンベディングとともに Firestore に読み込む方法
  • Firestore ベクトル検索を使用して自然言語クエリに基づいてデータを検索する方法
  • Google Cloud Text to Speech API を使用して音声コンテンツを生成する方法

必要なもの

  • Chrome ウェブブラウザ
  • Gmail アカウント
  • 課金が有効になっている Cloud プロジェクト

この Codelab は、初心者を含むあらゆるレベルのデベロッパーを対象としており、サンプル アプリケーションで JavaScript と Node.js を使用します。ただし、ここで説明するコンセプトを理解するために JavaScript と Node.js の知識は必要ありません。

2. 始める前に

プロジェクトを作成する

  1. Google Cloud コンソールのプロジェクト選択ページで、Google Cloud プロジェクトを選択または作成します。
  2. Cloud プロジェクトに対して課金が有効になっていることを確認します。詳しくは、プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
  3. Cloud Shell(Google Cloud で動作するコマンドライン環境)を使用します。この環境には bq がプリロードされています。Google Cloud コンソールの上部にある [Cloud Shell をアクティブにする] をクリックします。

Cloud Shell を有効にするボタンの画像

  1. Cloud Shell に接続したら、次のコマンドを使用して、認証が完了していることと、プロジェクトがプロジェクト ID に設定されていることを確認します。
gcloud auth list
  1. Cloud Shell で次のコマンドを実行して、gcloud コマンドがプロジェクトを認識していることを確認します。
gcloud config list project
  1. プロジェクトが設定されていない場合は、次のコマンドを使用して設定します。
gcloud config set project <YOUR_PROJECT_ID>
  1. 次のコマンドを使用して、必要な API を有効にします。この処理には数分かかることがありますので、少々お待ちください。
gcloud services enable firestore.googleapis.com \
                       compute.googleapis.com \
                       cloudresourcemanager.googleapis.com \
                       servicenetworking.googleapis.com \
                       run.googleapis.com \
                       cloudbuild.googleapis.com \
                       cloudfunctions.googleapis.com \
                       aiplatform.googleapis.com \
                       texttospeech.googleapis.com

コマンドが正常に実行されると、次のようなメッセージが表示されます。

Operation "operations/..." finished successfully.

gcloud コマンドの代わりに、コンソールで各プロダクトを検索するか、このリンクを使用します。

いずれかの API が不足している場合は、実装中にいつでも有効にできます。

gcloud コマンドとその使用方法については、ドキュメントをご覧ください。

リポジトリのクローンを作成して環境設定を設定する

次のステップでは、Codelab の残りの部分で参照するサンプル リポジトリのクローンを作成します。Cloud Shell を使用している場合は、ホーム ディレクトリから次のコマンドを実行します。

git clone https://github.com/rominirani/yoga-poses-recommender-nodejs

エディタを起動するには、Cloud Shell ウィンドウのツールバーで [エディタを開く] をクリックします。左上のメニューバーをクリックし、[ファイル] → [フォルダを開く] の順に選択します。

66221fd0d0e5202f.png

yoga-poses-recommender-nodejs フォルダを選択すると、次のようにフォルダが開き、次のファイルが表示されます。

7dbe126ee112266d.png

次に、使用する環境変数を設定する必要があります。env-template ファイルをクリックすると、次のような内容が表示されます。

PROJECT_ID=<YOUR_GOOGLE_CLOUD_PROJECT_ID>
LOCATION=us-<GOOGLE_CLOUD_REGION_NAME>
GEMINI_MODEL_NAME=<GEMINI_MODEL_NAME>
EMBEDDING_MODEL_NAME=<GEMINI_EMBEDDING_MODEL_NAME>
IMAGE_GENERATION_MODEL_NAME=<IMAGEN_MODEL_NAME>
DATABASE=<FIRESTORE_DATABASE_NAME>
COLLECTION=<FIRESTORE_COLLECTION_NAME>
TEST_COLLECTION=test-poses
TOP_K=3

Google Cloud プロジェクトと Firestore データベースのリージョンの作成時に選択した内容に従って、PROJECT_IDLOCATION の値を更新してください。理想的には、LOCATION の値は Google Cloud プロジェクトと Firestore データベースで同じにする必要があります(us-central1 など)。

この Codelab では、次の値を使用します(ただし、PROJECT_IDLOCATION は、構成に応じて設定する必要があります)。

PROJECT_ID=<YOUR_GOOGLE_CLOUD_PROJECT_ID>
LOCATION=us-<GOOGLE_CLOUD_REGION_NAME>
GEMINI_MODEL_NAME=gemini-1.5-flash-002
EMBEDDING_MODEL_NAME=text-embedding-004
IMAGE_GENERATION_MODEL_NAME=imagen-3.0-fast-generate-001
DATABASE=(default)
COLLECTION=poses
TEST_COLLECTION=test-poses
TOP_K=3

このファイルを env-template ファイルと同じフォルダに .env として保存してください。

Cloud Shell IDE の左上にあるメインメニュー、Terminal → New Terminal の順に移動します。

次のコマンドを使用して、クローンを作成したリポジトリのルートフォルダに移動します。

cd yoga-poses-recommender-nodejs

次のコマンドを使用して Node.js の依存関係をインストールします。

npm install

これで準備が整いました。これで、Firestore データベースの設定に進むことができます。

3. Firestore を設定する

Cloud Firestore は、アプリケーション データのバックエンドとして使用するフルマネージドのサーバーレス ドキュメント データベースです。Cloud Firestore のデータは、ドキュメントコレクションで構成されます。

Firestore データベースの初期化

Cloud コンソールの [Firestore] ページに移動します。

プロジェクトで Firestore データベースを初期化したことがない場合は、[Create Database] をクリックして default データベースを作成します。データベースの作成時に、次の値を使用します。

  • Firestore モード: Native.
  • 位置情報: デフォルトの位置情報設定を使用します。
  • セキュリティ ルールには Test rules を選択します。
  • データベースを作成します。

504cabdb99a222a5.png

次のセクションでは、デフォルトの Firestore データベースに poses という名前のコレクションを作成する準備を行います。このコレクションには、サンプルデータ(ドキュメント)またはヨガポーズに関する情報を格納します。これらのデータは、アプリケーションで使用します。

これで、Firestore データベースの設定セクションは完了です。

4. ヨガのポーズ データセットを準備する

まず、アプリケーションで使用するヨガポーズ データセットを準備します。まず、既存の Hugging Face データセットから始め、追加情報を追加して拡張します。

ヨガのポーズ用の Hugging Face データセットをご覧ください。この Codelab ではデータセットの 1 つを使用しますが、実際には他の任意のデータセットを使用して、ここで説明した手法に沿ってデータセットを拡張できます。

298cfae7f23e4bef.png

[Files and versions] セクションに移動すると、すべてのポーズの JSON データファイルを取得できます。

3fe6e55abdc032ec.png

yoga_poses.json をダウンロードし、そのファイルをお送りしました。このファイルの名前は yoga_poses_alldata.json で、/data フォルダにあります。

Cloud Shell エディタで data/yoga_poses.json ファイルに移動し、JSON オブジェクトのリストを調べます。各 JSON オブジェクトはヨガのポーズを表します。レコードは合計 3 つあり、サンプル レコードを以下に示します。

{
   "name": "Big Toe Pose",
   "sanskrit_name": "Padangusthasana",
   "photo_url": "https://pocketyoga.com/assets/images/full/ForwardBendBigToe.png",
   "expertise_level": "Beginner",
   "pose_type": ["Standing", "Forward Bend"]
 }

ここで、Gemini と、デフォルト モデル自体を使用して description フィールドを生成する方法について説明します。

Cloud Shell エディタで、generate-descriptions.js ファイルに移動します。このファイルの内容は次のとおりです。

import { VertexAI } from "@langchain/google-vertexai";
import fs from 'fs/promises'; // Use fs/promises for async file operations
import dotenv from 'dotenv';
import pRetry from 'p-retry';
import { promisify } from 'util';

const sleep = promisify(setTimeout);

// Load environment variables
dotenv.config();

async function callGemini(poseName, sanskritName, expertiseLevel, poseTypes) {

   const prompt = `
   Generate a concise description (max 50 words) for the yoga pose: ${poseName}
   Also known as: ${sanskritName}
   Expertise Level: ${expertiseLevel}
   Pose Type: ${poseTypes.join(', ')}

   Include key benefits and any important alignment cues.
   `;

   try {
     // Initialize Vertex AI Gemini model
     const model = new VertexAI({
       model: process.env.GEMINI_MODEL_NAME,
       location: process.env.LOCATION,
       project: process.env.PROJECT_ID,
     });
      // Invoke the model
     const response = await model.invoke(prompt);
      // Return the response
     return response;
   } catch (error) {
     console.error("Error calling Gemini:", error);
     throw error; // Re-throw the error for handling in the calling function
   }
 }

// Configure logging (you can use a library like 'winston' for more advanced logging)
const logger = {
 info: (message) => console.log(`INFO - ${new Date().toISOString()} - ${message}`),
 error: (message) => console.error(`ERROR - ${new Date().toISOString()} - ${message}`),
};

async function generateDescription(poseName, sanskritName, expertiseLevel, poseTypes) {
 const prompt = `
   Generate a concise description (max 50 words) for the yoga pose: ${poseName}
   Also known as: ${sanskritName}
   Expertise Level: ${expertiseLevel}
   Pose Type: ${poseTypes.join(', ')}

   Include key benefits and any important alignment cues.
   `;

 const req = {
   contents: [{ role: 'user', parts: [{ text: prompt }] }],
 };

 const runWithRetry = async () => {
   const resp = await generativeModel.generateContent(req);
   const response = await resp.response;
   const text = response.candidates[0].content.parts[0].text;
   return text;
 };

 try {
   const text = await pRetry(runWithRetry, {
     retries: 5,
     onFailedAttempt: (error) => {
       logger.info(
         `Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left. Waiting ${error.retryDelay}ms...`
       );
     },
     minTimeout: 4000, // 4 seconds (exponential backoff will adjust this)
     factor: 2, // Exponential factor
   });
   return text;
 } catch (error) {
   logger.error(`Error generating description for ${poseName}: ${error}`);
   return '';
 }
}

async function addDescriptionsToJSON(inputFile, outputFile) {
 try {
   const data = await fs.readFile(inputFile, 'utf-8');
   const yogaPoses = JSON.parse(data);

   const totalPoses = yogaPoses.length;
   let processedCount = 0;

   for (const pose of yogaPoses) {
     if (pose.name !== ' Pose') {
       const startTime = Date.now();
       pose.description = await callGemini(
         pose.name,
         pose.sanskrit_name,
         pose.expertise_level,
         pose.pose_type
       );

       const endTime = Date.now();
       const timeTaken = (endTime - startTime) / 1000;
       processedCount++;
       logger.info(`Processed: ${processedCount}/${totalPoses} - ${pose.name} (${timeTaken.toFixed(2)} seconds)`);
     } else {
       pose.description = '';
       processedCount++;
       logger.info(`Processed: ${processedCount}/${totalPoses} - ${pose.name} (${timeTaken.toFixed(2)} seconds)`);
     }

     // Add a delay to avoid rate limit
     await sleep(30000); // 30 seconds
   }

   await fs.writeFile(outputFile, JSON.stringify(yogaPoses, null, 2));
   logger.info(`Descriptions added and saved to ${outputFile}`);
 } catch (error) {
   logger.error(`Error processing JSON file: ${error}`);
 }
}

async function main() {
 const inputFile = './data/yoga_poses.json';
 const outputFile = './data/yoga_poses_with_descriptions.json';

 await addDescriptionsToJSON(inputFile, outputFile);
}

main();

このアプリケーションは、各ヨガのポーズ JSON レコードに新しい description フィールドを追加します。Gemini モデルを呼び出して説明を取得します。このモデルには、必要なプロンプトが提供されます。フィールドが JSON ファイルに追加され、新しいファイルが data/yoga_poses_with_descriptions.json ファイルに書き込まれます。

主な手順は次のとおりです。

  1. main() 関数では、add_descriptions_to_json 関数が呼び出され、想定される入力ファイルと出力ファイルが指定されています。
  2. add_descriptions_to_json 関数は、JSON レコード(ヨガ投稿情報)ごとに次のことを行います。
  3. pose_namesanskrit_nameexpertise_levelpose_types が抽出されます。
  4. callGemini 関数を呼び出してプロンプトを作成し、LangchainVertexAI モデルクラスを呼び出してレスポンス テキストを取得します。
  5. このレスポンス テキストが JSON オブジェクトに追加されます。
  6. 更新されたオブジェクトの JSON リストが宛先ファイルに書き込まれます。

このアプリケーションを実行してみましょう。新しいターミナル ウィンドウ(Ctrl+Shift+C)を起動し、次のコマンドを入力します。

npm run generate-descriptions

承認を求められた場合は、承認してください。

アプリケーションの実行が開始されます。新しい Google Cloud アカウントで発生する可能性があるレート制限の割り当てを回避するため、レコード間に 30 秒の遅延を追加しました。ご不便をおかけしますが、ご容赦ください。

実行中の実行例を以下に示します。

469ede91ba007c1f.png

3 つのレコードがすべて Gemini 呼び出しで拡張されると、ファイル data/yoga_poses_with_description.json が生成されます。ご確認いただけます。

これでデータファイルの準備が整いました。次のステップでは、エンベディングの生成とともに、Firestore データベースにデータを入力する方法について説明します。

5. データを Firestore にインポートしてベクトル エンベディングを生成する

data/yoga_poses_with_description.json ファイルを作成したので、Firestore データベースにこのファイルを入力し、各レコードのベクトル エンベディングを生成する必要があります。ベクトル エンベディングは、後で自然言語で指定されたユーザーのクエリと類似性検索を行う際に役立ちます。

手順は次のとおりです。

  1. JSON オブジェクトのリストをオブジェクトのリストに変換します。各ドキュメントには、contentmetadata の 2 つの属性があります。メタデータ オブジェクトには、namedescriptionsanskrit_name などの属性を持つ JSON オブジェクト全体が含まれます。content は、いくつかのフィールドを連結した文字列テキストです。
  2. ドキュメントのリストが作成されたら、Vertex AI Embeddings クラスを使用して、content フィールドのエンベディングを生成します。このエンベディングは各ドキュメント レコードに追加され、Firestore API を使用して、このドキュメント オブジェクトのリストをコレクションに保存します(test-poses を参照する TEST_COLLECTION 変数を使用しています)。

import-data.js のコードは次のとおりです(簡潔にするため、コードの一部を省略しています)。

import { Firestore,
        FieldValue,
} from '@google-cloud/firestore';
import { VertexAIEmbeddings } from "@langchain/google-vertexai";
import * as dotenv from 'dotenv';
import fs from 'fs/promises';

// Load environment variables
dotenv.config();

// Configure logging
const logger = {
 info: (message) => console.log(`INFO - ${new Date().toISOString()} - ${message}`),
 error: (message) => console.error(`ERROR - ${new Date().toISOString()} - ${message}`),
};

async function loadYogaPosesDataFromLocalFile(filename) {
 try {
   const data = await fs.readFile(filename, 'utf-8');
   const poses = JSON.parse(data);
   logger.info(`Loaded ${poses.length} poses.`);
   return poses;
 } catch (error) {
   logger.error(`Error loading dataset: ${error}`);
   return null;
 }
}

function createFirestoreDocuments(poses) {
 const documents = [];
 for (const pose of poses) {
   // Convert the pose to a string representation for pageContent
   const pageContent = `
name: ${pose.name || ''}
description: ${pose.description || ''}
sanskrit_name: ${pose.sanskrit_name || ''}
expertise_level: ${pose.expertise_level || 'N/A'}
pose_type: ${pose.pose_type || 'N/A'}
   `.trim();

   // The metadata will be the whole pose
   const metadata = pose;
   documents.push({ pageContent, metadata });
 }
 logger.info(`Created ${documents.length} Langchain documents.`);
 return documents;
}

async function main() {
 const allPoses = await loadYogaPosesDataFromLocalFile('./data/yoga_poses_with_descriptions.json');
 const documents = createFirestoreDocuments(allPoses);
 logger.info(`Successfully created Firestore documents. Total documents: ${documents.length}`);

 const embeddings = new VertexAIEmbeddings({
   model: process.env.EMBEDDING_MODEL_NAME,
 });
  // Initialize Firestore
 const firestore = new Firestore({
   projectId: process.env.PROJECT_ID,
   databaseId: process.env.DATABASE,
 });

 const collectionName = process.env.TEST_COLLECTION;

 for (const doc of documents) {
   try {
     // 1. Generate Embeddings
     const singleVector = await embeddings.embedQuery(doc.pageContent);

     // 2. Store in Firestore with Embeddings
     const firestoreDoc = {
       content: doc.pageContent,
       metadata: doc.metadata, // Store the original data as metadata
       embedding: FieldValue.vector(singleVector), // Add the embedding vector
     };

     const docRef = firestore.collection(collectionName).doc();
     await docRef.set(firestoreDoc);
     logger.info(`Document ${docRef.id} added to Firestore with embedding.`);
   } catch (error) {
     logger.error(`Error processing document: ${error}`);
   }
 }

 logger.info('Finished adding documents to Firestore.');
}

main();

このアプリケーションを実行してみましょう。新しいターミナル ウィンドウ(Ctrl+Shift+C)を起動し、次のコマンドを入力します。

npm run import-data

問題がなければ、次のようなメッセージが表示されます。

INFO - 2025-01-28T07:01:14.463Z - Loaded 3 poses.
INFO - 2025-01-28T07:01:14.464Z - Created 3 Langchain documents.
INFO - 2025-01-28T07:01:14.464Z - Successfully created Firestore documents. Total documents: 3
INFO - 2025-01-28T07:01:17.623Z - Document P46d5F92z9FsIhVVYgkd added to Firestore with embedding.
INFO - 2025-01-28T07:01:18.265Z - Document bjXXISctkXl2ZRSjUYVR added to Firestore with embedding.
INFO - 2025-01-28T07:01:19.285Z - Document GwzZMZyPfTLtiX6qBFFz added to Firestore with embedding.
INFO - 2025-01-28T07:01:19.286Z - Finished adding documents to Firestore.

レコードが正常に挿入され、エンベディングが生成されたかどうかを確認するには、Cloud コンソールの Firestore ページに移動します。

504cabdb99a222a5.png

(デフォルトの)データベースをクリックすると、test-poses コレクションとそのコレクション内の複数のドキュメントが表示されます。各ドキュメントは 1 つのヨガのポーズです。

9f37aa199c4b547a.png

ドキュメントをクリックして、フィールドを調べます。インポートしたフィールドに加えて、embedding フィールド(ベクトル フィールド)もあります。このフィールドの値は、text-embedding-004 Vertex AI エンベディング モデルによって生成されています。

f0ed92124519beaf.png

エンベディングが設定されたレコードが Firestore データベースにアップロードされたので、次のステップに進み、Firestore でベクトル類似度検索を行う方法を確認します。

6. ヨガのポーズ全体を Firestore データベース コレクションにインポートする

次に、160 種類のヨガポーズの完全なリストである poses コレクションを作成します。このコレクション用に、直接インポートできるデータベース インポート ファイルを生成しました。これは、ラボでの時間を節約するためです。説明とエンベディングを含むデータベースを生成するプロセスは、前のセクションで説明したものと同じです。

次の手順に沿ってデータベースをインポートします。

  1. 次の gsutil コマンドを使用して、プロジェクトにバケットを作成します。次のコマンドの <PROJECT_ID> 変数を、Google Cloud プロジェクト ID に置き換えます。
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
  1. バケットが作成されたので、Firebase データベースにインポートする前に、準備したデータベース エクスポートをこのバケットにコピーする必要があります。次のコマンドを使用します。
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615  gs://<PROJECT_ID>-my-bucket

インポートするデータが用意できたので、作成した Firebase データベース(default)にデータをインポートする最後のステップに進みます。

  1. 次の gcloud コマンドを使用します。
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615

インポートには数秒かかります。準備ができたら、https://console.cloud.google.com/firestore/databases にアクセスして、default データベースと poses コレクションを選択して、Firestore データベースとコレクションを検証します。

561f3cb840de23d8.png

これで、アプリケーションで使用する Firestore コレクションの作成が完了しました。

7. Firestore でベクトル類似性検索を行う

ベクトル類似検索を実行するには、ユーザーからクエリを受け取ります。このクエリの例は "Suggest me some exercises to relieve back pain" です。

search-data.js ファイルを確認します。注目すべき主な関数は、以下の search 関数です。大まかに言うと、ユーザークエリのエンベディングの生成に使用されるエンベディング クラスが作成されます。その後、Firestore データベースとコレクションへの接続を確立します。次に、コレクションで findNearest メソッドを呼び出して、ベクトル類似性検索を行います。

async function search(query) {
 try {

   const embeddings = new VertexAIEmbeddings({
       model: process.env.EMBEDDING_MODEL_NAME,
     });
  
   // Initialize Firestore
   const firestore = new Firestore({
       projectId: process.env.PROJECT_ID,
       databaseId: process.env.DATABASE,
   });

   log.info(`Now executing query: ${query}`);
   const singleVector = await embeddings.embedQuery(query);

   const collectionRef = firestore.collection(process.env.COLLECTION);
   let vectorQuery = collectionRef.findNearest(
   "embedding",
   FieldValue.vector(singleVector), // a vector with 768 dimensions
   {
       limit: process.env.TOP_K,
       distanceMeasure: "COSINE",
   }
   );
   const vectorQuerySnapshot = await vectorQuery.get();

   for (const result of vectorQuerySnapshot.docs) {
     console.log(result.data().content);
   }
 } catch (error) {
   log.error(`Error during search: ${error.message}`);
 }
}

いくつかのクエリ例でこのコードを実行する前に、まず Firestore 複合インデックスを生成する必要があります。これは、検索クエリを成功させるために必要です。インデックスを作成せずにアプリケーションを実行すると、まずインデックスを作成する必要があることを示唆するエラーと、まずインデックスを作成するコマンドが返されます。

複合インデックスを作成する gcloud コマンドは次のとおりです。

gcloud firestore indexes composite create --project=<YOUR_PROJECT_ID> --collection-group=poses --query-scope=COLLECTION --field-config=vector-config='{"dimension":"768","flat": "{}"}',field-path=embedding

データベースに 150 を超えるレコードがあるため、インデックスの作成には数分かかります。完了したら、次のコマンドを使用してインデックスを表示できます。

gcloud firestore indexes composite list

作成したインデックスがリストに表示されます。

次のコマンドを試してみましょう。

node search-data.js --prompt "Recommend me some exercises for back pain relief"

いくつかの推奨事項が表示されます。サンプル実行は次のとおりです。

2025-01-28T07:09:05.250Z - INFO - Now executing query: Recommend me some exercises for back pain relief
name: Sphinx Pose
description: A gentle backbend, Sphinx Pose (Salamba Bhujangasana) strengthens the spine and opens the chest.  Keep shoulders relaxed, lengthen the tailbone, and engage the core for optimal alignment. Beginner-friendly.

sanskrit_name: Salamba Bhujangasana
expertise_level: Beginner
pose_type: ['Prone']
name: Supine Spinal Twist Pose
description: A gentle supine twist (Supta Matsyendrasana), great for beginners.  Releases spinal tension, improves digestion, and calms the nervous system.  Keep shoulders flat on the floor and lengthen your spine throughout the twist.

sanskrit_name: Supta Matsyendrasana
expertise_level: Beginner
pose_type: ['Supine', 'Twist']
name: Reverse Corpse Pose
description: Reverse Corpse Pose (Advasana) is a beginner prone pose.  Lie on your belly, arms at your sides, relaxing completely.  Benefits include stress release and spinal decompression. Ensure your forehead rests comfortably on the mat.

sanskrit_name: Advasana
expertise_level: Beginner
pose_type: ['Prone']

これで、Firestore ベクトル データベースを使用してレコードをアップロードし、エンベディングを生成してベクトル類似性検索を行う方法を理解できました。これで、ベクトル検索をウェブ フロントエンドに統合するウェブ アプリケーションを作成できます。

8. ウェブ アプリケーション

Python Flask ウェブ アプリケーションは app.js ファイルで使用でき、フロントエンド HTML ファイルは views/index.html. にあります。

両方のファイルを確認することをおすすめします。まず、app.js ファイルから始めます。このファイルには、フロントエンドの HTML index.html ファイルから渡されたプロンプトを受け取る /search ハンドラが含まれています。これにより、検索メソッドが呼び出され、前のセクションで説明したベクトル類似検索が実行されます。

その後、レスポンスが推奨事項のリストとともに index.html に返されます。index.html に、推奨事項が異なるカードとして表示されます。

アプリケーションをローカルで実行する

新しいターミナル ウィンドウ(Ctrl+Shift+C)または既存のターミナル ウィンドウを起動し、次のコマンドを入力します。

npm run start

実行例を以下に示します。

...
Server listening on port 8080

アプリが稼働したら、下のウェブでプレビュー ボタンをクリックして、アプリのホームページ URL にアクセスします。

de297d4cee10e0bf.png

次のように、提供された index.html ファイルが表示されます。

20240a0e885ac17b.png

サンプルクエリ(例 : Provide me some exercises for back pain relief)を指定し、[Search] ボタンをクリックします。これにより、データベースからいくつかのおすすめが取得されます。[Play Audio] ボタンも表示されます。このボタンをクリックすると、説明に基づいて音声ストリームが生成され、直接聞くことができます。

789b4277dc40e2be.png

9. (省略可)Google Cloud Run へのデプロイ

最後のステップでは、このアプリケーションを Google Cloud Run にデプロイします。デプロイ コマンドは次のとおりです。デプロイする前に、以下の太字の値を置き換えてください。これらの値は、.env ファイルから取得できます。

gcloud run deploy yogaposes --source . \
  --port=8080 \
  --allow-unauthenticated \
  --region=<<YOUR_LOCATION>> \
  --platform=managed  \
  --project=<<YOUR_PROJECT_ID>> \
--set-env-vars=PROJECT_ID="<<YOUR_PROJECT_ID>>",LOCATION="<<YOUR_LOCATION>>",EMBEDDING_MODEL_NAME="<<EMBEDDING_MODEL_NAME>>",DATABASE="<<FIRESTORE_DATABASE_NAME>>",COLLECTION="<<FIRESTORE_COLLECTION_NAME>>",TOP_K=<<YOUR_TOP_K_VALUE>>

アプリケーションのルートフォルダから上記のコマンドを実行します。Google Cloud APIs を有効にするよう求められたり、さまざまな権限について承認を求められたりします。その場合は、承認してください。

デプロイ プロセスが完了するまでに 5 ~ 7 分ほどかかりますので、しばらくお待ちください。

3a6d86fd32e4a5e.png

デプロイが正常に完了すると、デプロイの出力に Cloud Run サービス URL が表示されます。形式は次のようになります。

Service URL: https://yogaposes-<UNIQUEID>.us-central1.run.app

その公開 URL にアクセスすると、同じウェブ アプリケーションがデプロイされ、正常に実行されていることがわかります。

84e1cbf29cbaeedc.png

Google Cloud コンソールから Cloud Run にアクセスすると、Cloud Run のサービスが一覧表示されます。yogaposes サービスが、そこに表示されるサービスの 1 つ(唯一のサービスではない)である必要があります。

f2b34a8c9011be4c.png

特定のサービス名(この場合は yogaposes)をクリックすると、URL、構成、ログなどのサービスの詳細を表示できます。

faaa5e0c02fe0423.png

これで、Cloud Run でのヨガのポーズ レコメンデーター ウェブ アプリケーションの開発とデプロイが完了しました。

10.完了

これで、データセットを Firestore にアップロードし、エンベディングを生成し、ユーザーのクエリに基づいてベクトル類似度検索を行うアプリケーションを作成できました。

リファレンス ドキュメント