Codelab – פיתוח אפליקציה להמלצות על תנוחות יוגה לפי הקשר בעזרת Firestore, Vector Search, Langchain ו-Gemini (גרסת Node.js)
מידע על Codelab זה
1. מבוא
בסדנת הקוד הזו תלמדו ליצור אפליקציה שמשתמשת בחיפוש וקטורים כדי להמליץ על תנוחות יוגה.
במהלך הקודלאב, נשתמש בגישה הדרגתית לפי השלבים הבאים:
- שימוש במערך נתונים קיים של Hugging Face עם תנוחות יוגה (בפורמט JSON).
- משפרים את מערך הנתונים באמצעות תיאור שדה נוסף שמשתמש ב-Gemini כדי ליצור תיאורים לכל אחת מהתנוחות.
- טעינת הנתונים של תנוחות היוגה כאוסף של מסמכים באוסף של Firestore עם הטמעות שנוצרו.
- יצירת אינדקס מורכב ב-Firestore כדי לאפשר חיפוש וקטור.
- אפשר להשתמש בחיפוש וקטורים באפליקציית Node.js שמרכזת את כל הרכיבים, כפי שמתואר בהמשך:
מה עליכם לעשות
- תכנון, פיתוח ופריסה של אפליקציית אינטרנט שמשתמשת בחיפוש וקטורים כדי להמליץ על תנוחות יוגה.
מה תלמדו
- איך משתמשים ב-Gemini כדי ליצור תוכן טקסטואלי, ובהקשר של סדנת הקוד הזו, ליצור תיאורים של תנוחות יוגה
- איך טוענים רשומות ממערך נתונים משופר מ-Hugging Face ל-Firestore יחד עם Vector Embeddings
- איך משתמשים בחיפוש וקטורים ב-Firestore כדי לחפש נתונים על סמך שאילתה בשפה טבעית
- איך משתמשים ב-Google Cloud Text to Speech API כדי ליצור תוכן אודיו
מה צריך להכין
- דפדפן האינטרנט Chrome
- חשבון Gmail
- פרויקט ב-Cloud שבו החיוב מופעל
סדנת הקוד הזו מיועדת למפתחים מכל הרמות (כולל למתחילים), והיא כוללת שימוש ב-JavaScript וב-Node.js באפליקציה לדוגמה. עם זאת, לא נדרש ידע ב-JavaScript וב-Node.js כדי להבין את המושגים המוצגים.
2. לפני שמתחילים
יצירת פרויקט
- בדף לבחירת הפרויקט במסוף Google Cloud, בוחרים או יוצרים פרויקט ב-Google Cloud.
- הקפידו לוודא שהחיוב מופעל בפרויקט שלכם ב-Cloud. כך בודקים אם החיוב מופעל בפרויקט
- נשתמש ב-Cloud Shell, סביבת שורת פקודה שפועלת ב-Google Cloud ומגיעה עם bq טעון מראש. לוחצים על Activate 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 והשימוש בהן.
שכפול המאגר והגדרת הגדרות הסביבה
בשלב הבא נשתמש בקוד לדוגמה שנעזרים בו בשאר הקודלמעבדה. נניח שאתם נמצאים ב-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
.
לצורך הקודלאב הזה, נשתמש בערכים הבאים (חוץ מ-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
.
עוברים לתיקיית השורש של המאגר שהעתקתם באמצעות הפקודה הבאה:
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.
- מיקום: משתמשים בהגדרות ברירת המחדל של המיקום.
- עבור כללי האבטחה, בוחרים באפשרות
Test rules
. - יוצרים את מסד הנתונים.
בקטע הבא נגדיר את הבסיס ליצירת אוסף בשם poses
במסד הנתונים שמוגדר כברירת מחדל ב-Firestore. האוסף הזה יכיל נתונים לדוגמה (מסמכים) או מידע על תנוחות יוגה, שבהם נשתמש באפליקציה שלנו.
זהו סוף הקטע של הגדרת מסד הנתונים של Firestore.
4. הכנת מערך הנתונים של תנוחות יוגה
המשימה הראשונה שלנו היא להכין את מערך הנתונים של תנוחות היוגה שנעשה בו שימוש באפליקציה. נתחיל ממערך נתונים קיים של Hugging Face, ולאחר מכן נוסיף לו מידע נוסף.
כדאי לעיין במערך הנתונים של Hugging Face לתנוחות יוגה. חשוב לזכור שבקודלאב הזה נעשה שימוש באחד ממערכי הנתונים, אבל אפשר להשתמש בכל מערך נתונים אחר וליישם את אותן שיטות שמוצגות כדי לשפר את מערך הנתונים.
אם נעבור לקטע Files and versions
, נוכל לקבל את קובץ הנתונים בפורמט JSON של כל תנוחות הישיבה.
הורדנו את yoga_poses.json
ושלחנו לך את הקובץ הזה. שם הקובץ הוא yoga_poses_alldata.json
והוא נמצא בתיקייה /data
.
עוברים לקובץ data/yoga_poses.json
בעורך של Cloud Shell ומעיינים ברשימת אובייקטי ה-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 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, כלומר פרטי הפוסט בנושא יוגה: - הוא מחלץ את
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, יחד עם יצירת הטמעות (embeddings).
5. ייבוא נתונים ל-Firestore ויצירת הטמעות של וקטורים
יש לנו את הקובץ data/yoga_poses_with_description.json
ועכשיו אנחנו צריכים לאכלס את מסד הנתונים של Firestore באמצעותו, וחשוב גם ליצור את הטמעות הווקטורים לכל אחד מהרשומות. הטמעות הווקטורים יהיו שימושיות בשלב מאוחר יותר, כשנצטרך לבצע חיפוש של דמיון ביניהם לבין שאילתת המשתמש שסופקה בשפה טבעית.
כדי לעשות זאת, צריך לפעול לפי השלבים הבאים:
- נעביר את רשימת אובייקטי ה-JSON לרשימת אובייקטים. לכל מסמך יהיו שני מאפיינים:
content
ו-metadata
. אובייקט המטא-נתונים יכיל את אובייקט ה-JSON כולו, עם מאפיינים כמוname
,description
,sanskrit_name
וכו'. השדהcontent
יהיה מחרוזת טקסט שתהיה שרשור של כמה שדות. - אחרי שנקבל רשימה של מסמכים, נשתמש בכיתה Vertex AI Embeddings כדי ליצור את הטמעת הנתונים (embedding) בשדה התוכן. ההטמעה הזו תתווסף לכל רשומת מסמך, ואז נשתמש ב-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.
כדי לבדוק אם הרשומות הוכנסו בהצלחה והטמעות ה-embedding נוצרו, אפשר להיכנס לדף Firestore במסוף Cloud.
לוחצים על מסד הנתונים (ברירת המחדל), שבו אמורה להופיע האוסף test-poses
ומספר מסמכים באוסף הזה. כל מסמך הוא תנוחת יוגה אחת.
לוחצים על אחד מהמסמכים כדי לבדוק את השדות. בנוסף לשדות שייבאנו, יופיע גם השדה embedding
, שהוא שדה וקטור. הערך שלו נוצר באמצעות מודל ההטמעה (Embedding) של Vertex AI text-embedding-004
.
עכשיו, אחרי שהרשומות הועלו למסד הנתונים של 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
שמוצגת בהמשך. באופן כללי, הקוד יוצר סוג הטמעה (embedding) שמשמש ליצירת הטמעה לשאילתת המשתמש. לאחר מכן הוא יוצר חיבור למסד הנתונים ולאוסף של 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
שמכיל את הטיפול /search
, שמקבל את ההנחיה שהועברה מקובץ ה-HTML 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>>
מריצים את הפקודה שלמעלה מהתיקייה ברמה הבסיסית (root) של האפליקציה. יכול להיות שתתבקשו גם להפעיל את ממשקי 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, יוצרת את הטמעות הנתונים ומבצעת חיפוש של דמיון בין וקטורים על סמך שאילתת המשתמש.