1. Introduction
Dans cet atelier de programmation, vous allez créer une application qui utilise la recherche vectorielle pour recommander des postures de yoga.
Au cours de cet atelier de programmation, vous allez suivre une approche par étapes :
- Utilisez un ensemble de données Hugging Face existant de poses de yoga (format JSON).
- Améliorez l'ensemble de données en ajoutant une description de champ qui utilise Gemini pour générer des descriptions pour chacune des poses.
- Chargez les données de poses de yoga sous forme de collection de documents dans la collection Firestore avec les embeddings générés.
- Créez un index composite dans Firestore pour autoriser la recherche vectorielle.
- Utilisez la recherche vectorielle dans une application Node.js qui rassemble tout, comme indiqué ci-dessous :

Objectifs de l'atelier
- Concevez, créez et déployez une application Web qui utilise Vector Search pour recommander des postures de yoga.
Points abordés
- Utiliser Gemini pour générer du contenu textuel et, dans le contexte de cet atelier de programmation, générer des descriptions de postures de yoga
- Charger des enregistrements à partir d'un ensemble de données enrichi de Hugging Face dans Firestore avec des embeddings vectoriels
- Utiliser la recherche vectorielle Firestore pour rechercher des données en fonction d'une requête en langage naturel
- Utiliser l'API Google Cloud Text-to-Speech pour générer du contenu audio
Prérequis
- Navigateur Web Chrome
- Un compte Gmail
- Un projet Cloud pour lequel la facturation est activée
Cet atelier de programmation, conçu pour les développeurs de tous niveaux (y compris les débutants), utilise JavaScript et Node.js dans son exemple d'application. Toutefois, vous n'avez pas besoin de connaître JavaScript ni Node.js pour comprendre les concepts présentés.
2. Avant de commencer
Créer un projet
- Dans la console Google Cloud, sur la page du sélecteur de projet, sélectionnez ou créez un projet Google Cloud.
- Assurez-vous que la facturation est activée pour votre projet Cloud. Découvrez comment vérifier si la facturation est activée sur un projet .
- Vous allez utiliser Cloud Shell, un environnement de ligne de commande exécuté dans Google Cloud et fourni avec bq. Cliquez sur "Activer Cloud Shell" en haut de la console Google Cloud.

- Une fois connecté à Cloud Shell, vérifiez que vous êtes déjà authentifié et que le projet est défini sur votre ID de projet à l'aide de la commande suivante :
gcloud auth list
- Exécutez la commande suivante dans Cloud Shell pour vérifier que la commande gcloud connaît votre projet.
gcloud config list project
- Si votre projet n'est pas défini, utilisez la commande suivante pour le définir :
gcloud config set project <YOUR_PROJECT_ID>
- Activez les API requises à l'aide de la commande ci-dessous. Cette opération peut prendre quelques minutes. Merci de patienter.
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
Si la commande s'exécute correctement, un message semblable à celui ci-dessous s'affiche :
Operation "operations/..." finished successfully.
Vous pouvez également accéder à la console en recherchant chaque produit ou en utilisant ce lien.
Si vous oubliez d'activer une API, vous pourrez toujours le faire au cours de l'implémentation.
Consultez la documentation pour connaître les commandes gcloud ainsi que leur utilisation.
Cloner le dépôt et configurer les paramètres de l'environnement
L'étape suivante consiste à cloner le dépôt d'exemple auquel nous ferons référence dans le reste de l'atelier de programmation. En supposant que vous êtes dans Cloud Shell, exécutez la commande suivante à partir de votre répertoire d'accueil :
git clone https://github.com/rominirani/yoga-poses-recommender-nodejs
Pour lancer l'éditeur, cliquez sur "Ouvrir l'éditeur" dans la barre d'outils de la fenêtre Cloud Shell. Cliquez sur la barre de menu en haut à gauche, puis sélectionnez Fichier → Ouvrir le dossier, comme indiqué ci-dessous :

Sélectionnez le dossier yoga-poses-recommender-nodejs. Il devrait s'ouvrir et afficher les fichiers suivants :

Nous devons maintenant configurer les variables d'environnement que nous allons utiliser. Cliquez sur le fichier env-template. Son contenu devrait s'afficher comme suit :
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
Veuillez mettre à jour les valeurs de PROJECT_ID et LOCATION en fonction de ce que vous avez sélectionné lors de la création du projet Google Cloud et de la région de la base de données Firestore. Dans l'idéal, nous aimerions que les valeurs de LOCATION soient les mêmes pour le projet Google Cloud et la base de données Firestore (par exemple, us-central1).
Pour les besoins de cet atelier de programmation, nous allons utiliser les valeurs suivantes (à l'exception, bien sûr, de PROJECT_ID et LOCATION, que vous devez définir en fonction de votre configuration).
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
Veuillez enregistrer ce fichier sous le nom .env dans le même dossier que le fichier env-template.
Accédez au menu principal en haut à gauche de l'IDE Cloud Shell, puis cliquez sur Terminal → New Terminal.
Accédez au dossier racine du dépôt que vous avez cloné à l'aide de la commande suivante :
cd yoga-poses-recommender-nodejs
Installez les dépendances Node.js à l'aide de la commande suivante :
npm install
Super ! Nous sommes maintenant prêts à passer à la configuration de la base de données Firestore.
3. Configurer Firestore
Cloud Firestore est une base de données de documents sans serveur entièrement gérée que nous utiliserons comme backend pour les données de notre application. Dans Cloud Firestore, les données sont structurées en collections de documents.
Initialisation de la base de données Firestore
Accédez à la page Firestore de la console Cloud.
Si vous n'avez jamais initialisé de base de données Firestore dans le projet, créez la base de données default en cliquant sur Create Database. Lors de la création de la base de données, utilisez les valeurs suivantes :
- Mode Firestore :
Native. - Sélectionnez
Regioncomme type d'emplacement, puisus-central1comme emplacement pour la région. - Pour les règles de sécurité, choisissez
Test rules. - Créez la base de données.

Dans la section suivante, nous allons préparer le terrain pour créer une collection nommée poses dans notre base de données Firestore par défaut. Cette collection contiendra des exemples de données (documents) ou des informations sur les postures de yoga, que nous utiliserons ensuite dans notre application.
Cette étape marque la fin de la section consacrée à la configuration de la base de données Firestore.
4. Préparer l'ensemble de données des postures de yoga
Notre première tâche consiste à préparer l'ensemble de données sur les postures de yoga que nous utiliserons pour l'application. Nous allons commencer par un ensemble de données Hugging Face existant, puis l'enrichir avec des informations supplémentaires.
Consultez l'ensemble de données Hugging Face pour les postures de yoga. Notez que cet atelier de programmation utilise l'un des ensembles de données, mais vous pouvez en fait utiliser n'importe quel autre ensemble de données et suivre les mêmes techniques présentées pour l'améliorer.

Si nous accédons à la section Files and versions, nous pouvons obtenir le fichier de données JSON pour toutes les poses.

Nous avons téléchargé le yoga_poses.json et vous l'avons fourni. Ce fichier est nommé yoga_poses_alldata.json et se trouve dans le dossier /data.
Accédez au fichier data/yoga_poses.json dans l'éditeur Cloud Shell et examinez la liste des objets JSON, où chaque objet JSON représente une pose de yoga. Nous avons un total de trois enregistrements, dont un exemple est présenté ci-dessous :
{
"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"]
}
C'est l'occasion idéale de vous présenter Gemini et de vous montrer comment utiliser le modèle par défaut pour générer un champ description.
Dans l'éditeur Cloud Shell, accédez au fichier generate-descriptions.js. Le contenu de ce fichier est présenté ci-dessous :
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();
Cette application ajoutera un champ description à chaque enregistrement JSON de pose de yoga. Il obtiendra la description en appelant le modèle Gemini, auquel nous fournirons la requête nécessaire. Le champ est ajouté au fichier JSON, et le nouveau fichier est écrit dans le fichier data/yoga_poses_with_descriptions.json.
Voici les principales étapes :
- Dans la fonction
main(), vous constaterez qu'elle appelle la fonctionadd_descriptions_to_jsonet fournit le fichier d'entrée et le fichier de sortie attendus. - La fonction
add_descriptions_to_jsoneffectue les opérations suivantes pour chaque enregistrement JSON (c'est-à-dire les informations sur le post de yoga) : - Il extrait
pose_name,sanskrit_name,expertise_leveletpose_types. - Elle appelle la fonction
callGeminiqui construit une invite, puis appelle la classe de modèle LangchainVertexAI pour obtenir le texte de la réponse. - Ce texte de réponse est ensuite ajouté à l'objet JSON.
- La liste JSON mise à jour des objets est ensuite écrite dans le fichier de destination.
Exécutons cette application. Ouvrez une nouvelle fenêtre de terminal (Ctrl+Maj+C) et saisissez la commande suivante :
npm run generate-descriptions
Si une autorisation vous est demandée, veuillez l'accorder.
Vous constaterez que l'application commence à s'exécuter. Nous avons ajouté un délai de 30 secondes entre les enregistrements pour éviter les quotas de limites de débit qui pourraient exister sur les nouveaux comptes Google Cloud. Veuillez donc patienter.
Voici un exemple d'exécution en cours :

Une fois les trois enregistrements améliorés avec l'appel Gemini, un fichier data/yoga_poses_with_description.json est généré. Vous pouvez y jeter un coup d'œil.
Nous avons maintenant notre fichier de données. L'étape suivante consiste à comprendre comment remplir une base de données Firestore avec celui-ci, ainsi qu'à générer des embeddings.
5. Importer des données dans Firestore et générer des embeddings vectoriels
Nous avons le fichier data/yoga_poses_with_description.json et nous devons maintenant remplir la base de données Firestore avec celui-ci et, surtout, générer les embeddings vectoriels pour chacun des enregistrements. Les embeddings vectoriels seront utiles plus tard lorsque nous devrons effectuer une recherche de similarité sur eux avec la requête utilisateur fournie en langage naturel.
Pour ce faire, procédez comme suit :
- Nous allons convertir la liste d'objets JSON en liste d'objets. Chaque document comporte deux attributs :
contentetmetadata. L'objet de métadonnées contiendra l'intégralité de l'objet JSON comportant des attributs tels quename,description,sanskrit_name, etc.contentsera une chaîne de texte qui sera la concaténation de plusieurs champs. - Une fois que nous aurons une liste de documents, nous utiliserons la classe Vertex AI Embeddings pour générer l'embedding du champ de contenu. Cet embedding sera ajouté à chaque enregistrement de document. Nous utiliserons ensuite l'API Firestore pour enregistrer cette liste d'objets de document dans la collection (nous utilisons la variable
TEST_COLLECTIONqui pointe verstest-poses).
Le code de import-data.js est donné ci-dessous (certaines parties du code ont été tronquées par souci de concision) :
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();
Exécutons cette application. Ouvrez une nouvelle fenêtre de terminal (Ctrl+Maj+C) et saisissez la commande suivante :
npm run import-data
Si tout se passe bien, un message semblable à celui ci-dessous devrait s'afficher :
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.
Pour vérifier si les enregistrements ont bien été insérés et si les embeddings ont été générés, accédez à la page Firestore dans la console Cloud.

Cliquez sur la base de données (par défaut). La collection test-poses et plusieurs documents de cette collection devraient s'afficher. Chaque document correspond à une pose de yoga.

Cliquez sur l'un des documents pour examiner les champs. En plus des champs que nous avons importés, vous trouverez également le champ embedding, qui est un champ vectoriel dont la valeur a été générée à l'aide du modèle d'embedding Vertex AI text-embedding-004.

Maintenant que les enregistrements sont importés dans la base de données Firestore et que les embeddings sont en place, nous pouvons passer à l'étape suivante et découvrir comment effectuer une recherche de similarité vectorielle dans Firestore.
6. Importer des postures de yoga complètes dans une collection de base de données Firestore
Nous allons maintenant créer la collection poses, qui est une liste complète de 160 postures de yoga pour lesquelles nous avons généré un fichier d'importation de base de données que vous pouvez importer directement. Cela permet de gagner du temps dans l'atelier. Le processus de génération de la base de données contenant la description et les embeddings est le même que celui que nous avons vu dans la section précédente.
Importez la base de données en suivant les étapes ci-dessous :
- Créez un bucket dans votre projet à l'aide de la commande
gsutilci-dessous. Remplacez la variable<PROJECT_ID>dans la commande ci-dessous par l'ID de votre projet Google Cloud.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
- Maintenant que le bucket est créé, nous devons y copier l'exportation de la base de données que nous avons préparée, avant de pouvoir l'importer dans la base de données Firebase. Utilisez la commande ci-dessous :
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615 gs://<PROJECT_ID>-my-bucket
Maintenant que nous avons les données à importer, nous pouvons passer à la dernière étape, qui consiste à importer les données dans la base de données Firebase (default) que nous avons créée.
- Utilisez la commande gcloud ci-dessous :
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615
L'importation prendra quelques secondes. Une fois terminée, vous pourrez valider votre base de données Firestore et la collection en accédant à https://console.cloud.google.com/firestore/databases, puis en sélectionnant la base de données default et la collection poses, comme indiqué ci-dessous :

La création de la collection Firestore que nous utiliserons dans notre application est terminée.
7. Effectuer une recherche de similarité vectorielle dans Firestore
Pour effectuer une recherche de similarité vectorielle, nous allons récupérer la requête de l'utilisateur. Par exemple, la requête "Suggest me some exercises to relieve back pain".
Examinez le fichier search-data.js. La fonction clé à examiner est la fonction search, qui est illustrée ci-dessous. De manière générale, il crée une classe d'embedding qui sera utilisée pour générer l'embedding pour la requête utilisateur. Il établit ensuite une connexion à la base de données et à la collection Firestore. Il appelle ensuite la méthode findNearest sur la collection, qui effectue une recherche de similarité vectorielle.
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}`);
}
}
Avant d'exécuter cette requête avec quelques exemples, vous devez d'abord générer un index composite Firestore, qui est nécessaire pour que vos requêtes de recherche aboutissent. Si vous exécutez l'application sans créer l'index, une erreur indiquant que vous devez d'abord créer l'index s'affichera avec la commande permettant de créer l'index.
La commande gcloud permettant de créer l'index composite est indiquée ci-dessous :
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
L'index prendra quelques minutes, car la base de données contient plus de 150 enregistrements. Une fois l'index créé, vous pouvez l'afficher à l'aide de la commande ci-dessous :
gcloud firestore indexes composite list
L'index que vous venez de créer devrait apparaître dans la liste.
Essayez la commande suivante :
node search-data.js --prompt "Recommend me some exercises for back pain relief"
Vous devriez recevoir quelques recommandations. Voici un exemple d'exécution :
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']
Une fois que cela fonctionne, nous avons compris comment utiliser la base de données vectorielle Firestore pour importer des enregistrements, générer des embeddings et effectuer une recherche de similarité vectorielle. Nous pouvons maintenant créer une application Web qui intégrera la recherche vectorielle dans une interface Web.
8. L'application Web
L'application Web Python Flask est disponible dans le fichier app.js et le fichier HTML de l'interface utilisateur se trouve dans le fichier views/index.html..
Nous vous recommandons de consulter les deux fichiers. Commencez par le fichier app.js qui contient le gestionnaire /search, qui prend la requête transmise par le fichier index.html HTML de l'interface utilisateur. La méthode de recherche est ensuite appelée, ce qui effectue la recherche de similarité vectorielle que nous avons examinée dans la section précédente.
La réponse est ensuite renvoyée à index.html avec la liste des recommandations. L'index.html affiche ensuite les recommandations sous forme de fiches.
Exécuter l'application en local
Ouvrez une nouvelle fenêtre de terminal (Ctrl+Maj+C) ou une fenêtre de terminal existante, puis saisissez la commande suivante :
npm run start
Voici un exemple d'exécution :
...
Server listening on port 8080
Une fois l'application opérationnelle, accédez à l'URL de la page d'accueil en cliquant sur le bouton "Aperçu sur le Web" ci-dessous :

Le fichier index.html diffusé doit s'afficher comme suit :

Fournissez un exemple de requête (par exemple, Provide me some exercises for back pain relief), puis cliquez sur le bouton Search. Cela devrait permettre de récupérer certaines recommandations de la base de données. Vous verrez également un bouton Play Audio qui générera un flux audio basé sur la description, que vous pourrez écouter directement.

9. (Facultatif) Déployer sur Google Cloud Run
La dernière étape consiste à déployer cette application sur Google Cloud Run. La commande de déploiement est indiquée ci-dessous. Avant de la déployer, assurez-vous de remplacer les différentes valeurs entre crochets (<<>>) qui sont indiquées ci-dessous. Il s'agit de valeurs que vous pourrez récupérer à partir du fichier .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>>
Exécutez la commande ci-dessus à partir du dossier racine de l'application. Vous pouvez également être invité à activer les API Google Cloud et à confirmer diverses autorisations. Veuillez le faire.
Le processus de déploiement prend environ cinq à sept minutes. Merci de patienter.

Une fois le déploiement réussi, l'URL du service Cloud Run s'affiche dans le résultat du déploiement. Elle se présente sous la forme suivante :
Service URL: https://yogaposes-<UNIQUEID>.us-central1.run.app
Accédez à cette URL publique. L'application Web devrait s'afficher et s'exécuter correctement.

Vous pouvez également accéder à Cloud Run depuis la console Google Cloud pour afficher la liste des services dans Cloud Run. Le service yogaposes doit figurer dans la liste des services (voire être le seul).

Vous pouvez afficher les détails du service, tels que l'URL, les configurations, les journaux et plus encore, en cliquant sur le nom du service spécifique (yogaposes dans notre cas).

Le développement et le déploiement de notre application Web de recommandation de postures de yoga sur Cloud Run sont terminés.
10. Félicitations
Félicitations ! Vous avez réussi à créer une application qui importe un ensemble de données dans Firestore, génère les embeddings et effectue une recherche de similarité vectorielle en fonction de la requête des utilisateurs.