1. מבוא
ב-Codelab הזה תבנו אפליקציה שמשתמשת בחיפוש וקטורים כדי להמליץ על תנוחות יוגה.
במהלך ה-codelab, תשתמשו בגישה שלב אחר שלב באופן הבא:
- שימוש במערך נתונים קיים של תנוחות יוגה מ-Hugging Face (בפורמט JSON).
- משפרים את מערך הנתונים באמצעות תיאור שדה נוסף שנוצר על ידי Gemini לכל אחת מהתנוחות.
- טוענים את נתוני תנוחות היוגה כאוסף של מסמכים באוסף Firestore עם הטמעות שנוצרו.
- יוצרים אינדקס מורכב ב-Firestore כדי לאפשר חיפוש וקטורי.
- משתמשים בחיפוש וקטורי באפליקציית Node.js שמאגדת את כל הנתונים, כמו שמוצג בהמשך:

מה תעשו
- תכנון, בנייה ופריסה של אפליקציית אינטרנט שמשתמשת בחיפוש וקטורי כדי להמליץ על תנוחות יוגה.
מה תלמדו
- איך משתמשים ב-Gemini כדי ליצור תוכן טקסטואלי, ובמסגרת ה-Codelab הזה, ליצור תיאורים של תנוחות יוגה
- איך טוענים רשומות ממערך נתונים משופר מ-Hugging Face ל-Firestore יחד עם הטמעות וקטוריות
- איך משתמשים בחיפוש וקטורי ב-Firestore כדי לחפש נתונים על סמך שאילתה בשפה טבעית
- איך משתמשים ב-Google Cloud Text to Speech API כדי ליצור תוכן אודיו
מה תצטרכו
- דפדפן האינטרנט Chrome
- חשבון Gmail
- פרויקט ב-Cloud עם חיוב מופעל
ב-Codelab הזה, שמיועד למפתחים בכל הרמות (כולל מתחילים), נעשה שימוש ב-JavaScript וב-Node.js באפליקציית הדוגמה. עם זאת, לא נדרש ידע ב-JavaScript וב-Node.js כדי להבין את המושגים שמוצגים.
2. לפני שמתחילים
יצירת פרויקט
- ב-מסוף Google Cloud, בדף לבחירת הפרויקט, בוחרים או יוצרים פרויקט ב-Google Cloud.
- הקפידו לוודא שהחיוב מופעל בפרויקט שלכם ב-Cloud. כך בודקים אם החיוב מופעל בפרויקט
- תשתמשו ב-Cloud Shell, סביבת שורת פקודה שפועלת ב-Google Cloud ומגיעה עם bq שנטען מראש. לוחצים על 'הפעלת Cloud Shell' בחלק העליון של מסוף Google Cloud.

- אחרי שמתחברים ל-Cloud Shell, בודקים שכבר בוצע אימות ושהפרויקט מוגדר למזהה הפרויקט באמצעות הפקודה הבאה:
gcloud auth list
- מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שפקודת gcloud מכירה את הפרויקט.
gcloud config list project
- אם הפרויקט לא מוגדר, משתמשים בפקודה הבאה כדי להגדיר אותו:
gcloud config set project <YOUR_PROJECT_ID>
- מפעילים את ממשקי ה-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
כדי לפתוח את העורך, לוחצים על Open Editor בסרגל הכלים שבחלון של Cloud Shell. לוחצים על סרגל התפריטים בפינה הימנית העליונה ובוחרים באפשרות 'קובץ' ← 'פתיחת תיקייה' כמו שמוצג בהמשך:

בוחרים בתיקייה yoga-poses-recommender-nodejs והיא אמורה להיפתח עם הקבצים הבאים, כמו שמוצג בהמשך:

עכשיו צריך להגדיר את משתני הסביבה שבהם נשתמש. לוחצים על הקובץ 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
צריך לעדכן את הערכים של PROJECT_ID ושל LOCATION בהתאם למה שבחרתם כשיצרתם את פרויקט Google Cloud ואת האזור של מסד הנתונים של Firestore. הכי טוב שהערכים של LOCATION יהיו זהים בפרויקט Google Cloud ובמסד הנתונים של Firestore, למשל us-central1.
לצורך ה-codelab הזה, נשתמש בערכים הבאים (חוץ מ-PROJECT_ID ו-LOCATION, שצריך להגדיר בהתאם להגדרה שלכם).
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 באותה תיקייה שבה נמצא הקובץ env-template.
עוברים לתפריט הראשי בפינה הימנית העליונה ב-Cloud Shell IDE ואז לוחצים על Terminal → New Terminal.
עוברים לתיקיית הבסיס (root) של המאגר ששיבטתם באמצעות הפקודה הבאה:
cd yoga-poses-recommender-nodejs
מתקינים את יחסי התלות של Node.js באמצעות הפקודה:
npm install
מעולה! עכשיו אפשר להמשיך למשימה של הגדרת מסד הנתונים של Firestore.
3. הגדרת Firestore
Cloud Firestore הוא מסד נתונים מנוהל למסמכים, שניתן להתאמה ללא שרת (serverless), שישמש כקצה עורפי לנתוני האפליקציה. הנתונים ב-Cloud Firestore מובְנים באוספים של מסמכים.
הפעלה של מסד נתונים ב-Firestore
נכנסים לדף Firestore במסוף Cloud.
אם לא הפעלתם מסד נתונים של Firestore בפרויקט, צריך ליצור את מסד הנתונים default על ידי לחיצה על Create Database. במהלך יצירת מסד הנתונים, משתמשים בערכים הבאים:
- מצב Firestore:
Native. - בוחרים באפשרות
Regionבתור Location Type ובוחרים את המיקוםus-central1לאזור. - בקטע 'כללי אבטחה', בוחרים באפשרות
Test rules. - יוצרים את מסד הנתונים.

בקטע הבא נניח את היסודות ליצירת אוסף בשם poses במסד הנתונים של Firestore שמוגדר כברירת מחדל. האוסף הזה יכיל נתוני דוגמה (מסמכים) או מידע על תנוחות יוגה, שבהם נשתמש באפליקציה שלנו.
כך משלימים את ההגדרה של מסד הנתונים של Firestore.
4. הכנת מערך הנתונים של תנוחות יוגה
המשימה הראשונה שלנו היא להכין את מערך הנתונים של תנוחות היוגה שבו נשתמש באפליקציה. נתחיל עם מערך נתונים קיים של Hugging Face ואז נשפר אותו עם מידע נוסף.
אפשר לעיין במערך הנתונים של Hugging Face לתנוחות יוגה. שימו לב: ב-Codelab הזה נעשה שימוש באחת מקבוצות הנתונים, אבל למעשה אפשר להשתמש בכל קבוצת נתונים אחרת וליישם את אותן טכניקות שמוצגות כאן כדי לשפר את קבוצת הנתונים.

אם עוברים לקטע Files and versions, אפשר לקבל את קובץ נתוני ה-JSON של כל התנוחות.

הורדנו את yoga_poses.json ושלחנו לך את הקובץ הזה. הקובץ הזה נקרא yoga_poses_alldata.json והוא נמצא בתיקייה /data.
עוברים לקובץ data/yoga_poses.json בעורך Cloud Shell ומעיינים ברשימת אובייקטי ה-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 Editor, עוברים לקובץ 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();
האפליקציה הזו תוסיף שדה חדש description לכל רשומה של תנוחת יוגה ב-JSON. התיאור יתקבל באמצעות קריאה למודל Gemini, שבו נספק לו את ההנחיה הנדרשת. השדה מתווסף לקובץ ה-JSON והקובץ החדש נכתב לקובץ data/yoga_poses_with_descriptions.json.
בואו נסביר את השלבים העיקריים:
- בפונקציה
main(), תראו שהיא מפעילה את הפונקציהadd_descriptions_to_jsonומספקת את קובץ הקלט ואת קובץ הפלט הצפוי. - הפונקציה
add_descriptions_to_jsonמבצעת את הפעולות הבאות לכל רשומת JSON, כלומר לכל פרט בפוסט של Yoga: - הוא מחלץ את
pose_name,sanskrit_name,expertise_levelו-pose_types. - היא מפעילה את הפונקציה
callGeminiשיוצרת הנחיה ואז מפעילה את מחלקת המודלים LangchainVertexAI כדי לקבל את טקסט התשובה. - טקסט התגובה הזה מתווסף לאובייקט JSON.
- לאחר מכן, רשימת האובייקטים המעודכנת בפורמט JSON נכתבת לקובץ היעד.
אנחנו רוצים להריץ את האפליקציה הזו. מפעילים חלון טרמינל חדש (Ctrl+Shift+C) ומזינים את הפקודה הבאה:
npm run generate-descriptions
אם תתבקש לתת הרשאה, עליך לעשות זאת.
האפליקציה תתחיל לפעול. הוספנו עיכוב של 30 שניות בין הרשומות כדי למנוע חריגה ממכסות של הגבלת קצב, שיכולות להיות בחשבונות חדשים ב-Google Cloud. לכן, צריך להתאזר בסבלנות.
דוגמה להרצה בתהליך:

אחרי שכל 3 הרשומות ישופרו באמצעות השיחה עם Gemini, ייווצר קובץ data/yoga_poses_with_description.json. אתם יכולים לעיין בזה.
קובץ הנתונים מוכן, ועכשיו נסביר איך לאכלס איתו מסד נתונים של Firestore, ואיך ליצור הטמעות.
5. ייבוא נתונים ל-Firestore ויצירת הטמעות וקטוריות
יש לנו את הקובץ data/yoga_poses_with_description.json ועכשיו אנחנו צריכים לאכלס איתו את מסד הנתונים של Firestore, וחשוב מכך, ליצור את הטמעות הווקטורים לכל אחד מהרשומות. הטמעות וקטוריות יהיו שימושיות בהמשך כשנצטרך לבצע חיפוש דמיון על הטמעות וקטוריות עם שאילתת המשתמש שסופקה בשפה טבעית.
כדי לעשות את זה, צריך לפעול לפי השלבים הבאים:
- המערכת תמיר את רשימת אובייקטי ה-JSON לרשימת אובייקטים. לכל מסמך יהיו שני מאפיינים:
contentו-metadata. אובייקט המטא-נתונים יכיל את אובייקט ה-JSON כולו עם מאפיינים כמוname,description,sanskrit_nameוכו'. המאפייןcontentיהיה מחרוזת טקסט שתהיה שרשור של כמה שדות. - אחרי שנקבל רשימה של מסמכים, נשתמש במחלקת ההטמעות של Vertex AI כדי ליצור את ההטמעה של שדה התוכן. ההטמעה הזו תתווסף לכל רשומה של מסמך, ואז נשתמש ב-Firestore API כדי לשמור את הרשימה הזו של אובייקטים של מסמכים באוסף (אנחנו משתמשים במשתנה
TEST_COLLECTIONשמצביע עלtest-poses).
הקוד של 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.
כדי לבדוק אם הרשומות הוכנסו בהצלחה וההטמעות נוצרו, עוברים אל הדף של Firestore במסוף Cloud.

לוחצים על מסד הנתונים (ברירת המחדל). אמורה להופיע הקולקציה test-poses ומסמכים רבים מתחת לקולקציה הזו. כל מסמך הוא תנוחת יוגה אחת.

לוחצים על אחד המסמכים כדי לבדוק את השדות. בנוסף לשדות שייבאנו, תמצאו גם את השדה embedding, שהוא שדה וקטורי, שהערך שלו נוצר באמצעות מודל ההטמעה text-embedding-004 של Vertex AI.

אחרי שהרשומות הועלו למסד הנתונים של Firestore עם ההטמעות, אפשר לעבור לשלב הבא ולראות איך מבצעים חיפוש דמיון וקטורי ב-Firestore.
6. ייבוא תנוחות יוגה מלאות לאוסף של מסד נתונים ב-Firestore
עכשיו ניצור את אוסף poses, שהוא רשימה מלאה של 160 תנוחות יוגה, שעבורן יצרנו קובץ לייבוא למסד נתונים שאפשר לייבא ישירות. הפעולה הזו נעשית כדי לחסוך זמן במעבדה. התהליך ליצירת מסד הנתונים שמכיל את התיאור וההטמעות זהה לתהליך שראינו בקטע הקודם.
כדי לייבא את מסד הנתונים, פועלים לפי השלבים הבאים:
- יוצרים קטגוריה בפרויקט באמצעות הפקודה
gsutilשמופיעה בהמשך. מחליפים את המשתנה<PROJECT_ID>בפקודה שלמטה במזהה הפרויקט ב-Google Cloud.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
- אחרי שיוצרים את הדלי, צריך להעתיק אליו את קובץ הייצוא של מסד הנתונים שהכנו, כדי שנוכל לייבא אותו למסד הנתונים של Firebase. משתמשים בפקודה שמופיעה בהמשך:
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615 gs://<PROJECT_ID>-my-bucket
עכשיו שיש לנו את הנתונים לייבוא, אפשר לעבור לשלב האחרון של ייבוא הנתונים למסד הנתונים של Firebase (default) שיצרנו.
- משתמשים בפקודה הבאה של gcloud:
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615
הייבוא יימשך כמה שניות. כשהוא יסתיים, תוכלו לאמת את מסד הנתונים של Firestore ואת האוסף. לשם כך, עוברים אל https://console.cloud.google.com/firestore/databases, בוחרים את מסד הנתונים default ואת האוסף poses כמו שמוצג בהמשך:

כך מסיימים ליצור את האוסף של 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 שמכיל את ה-handler /search, שמקבל את ההנחיה שהועברה מקובץ ה-HTML של ה-front-end index.html. לאחר מכן מופעלת שיטת החיפוש, שמבצעת את החיפוש של דמיון וקטורי שראינו בקטע הקודם.
התשובה נשלחת בחזרה אל index.html עם רשימת ההמלצות. אחרי מכן, ההמלצות מוצגות בכרטיסים שונים בindex.html.
הרצת האפליקציה באופן מקומי
מפעילים חלון טרמינל חדש (Ctrl+Shift+C) או חלון טרמינל קיים ומזינים את הפקודה הבאה:
npm run start
דוגמה להרצה:
...
Server listening on port 8080
אחרי שהאפליקציה תפעל, לוחצים על לחצן התצוגה המקדימה באינטרנט שמופיע למטה כדי להיכנס לכתובת ה-URL של דף הבית של האפליקציה:

הוא אמור להציג את הקובץ index.html שמוצג כמו שמופיע בהמשך:

מזינים שאילתה לדוגמה (למשל : Provide me some exercises for back pain relief) ולוחצים על הכפתור Search. אמורות להתקבל כמה המלצות ממסד הנתונים. יופיע גם לחצן Play Audio, שבאמצעותו אפשר ליצור זרם אודיו על סמך התיאור ולהאזין לו ישירות.

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 API ולתת אישור להרשאות שונות.
תהליך הפריסה יימשך כ-5-7 דקות, אז כדאי לחכות בסבלנות.

אחרי הפריסה, כתובת ה-URL של שירות Cloud Run תופיע בפלט הפריסה. הפורמט יהיה:
Service URL: https://yogaposes-<UNIQUEID>.us-central1.run.app
אם תיכנסו לכתובת ה-URL הציבורית הזו, תוכלו לראות את אפליקציית האינטרנט שפרסתם, והיא תפעל בצורה תקינה.

אפשר גם להיכנס אל Cloud Run דרך מסוף Google Cloud, ושם תופיע רשימת השירותים ב-Cloud Run. השירות yogaposes צריך להיות אחד מהשירותים (אם לא היחיד) שמופיעים שם.

כדי לראות את פרטי השירות כמו כתובת URL, הגדרות, יומנים ועוד, לוחצים על שם השירות הספציפי (yogaposes במקרה שלנו).

כך מסיימים את הפיתוח והפריסה של אפליקציית האינטרנט להמלצות על תנוחות יוגה ב-Cloud Run.
10. מזל טוב
מזל טוב, הצלחתם ליצור אפליקציה שמעלה מערך נתונים ל-Firestore, יוצרת את ההטמעות ומבצעת חיפוש דמיון וקטורי על סמך השאילתה של המשתמש.