1. مقدمة
في هذا الدرس التطبيقي حول الترميز، ستنشئ تطبيقًا يستخدم البحث المتّجه لاقتراح أوضاع اليوغا.
خلال هذا الدرس العملي، ستتّبع نهجًا خطوة بخطوة على النحو التالي:
- استخدام مجموعة بيانات حالية من Hugging Face تتضمّن وضعيات اليوغا (بتنسيق JSON)
- حسِّن مجموعة البيانات بإضافة وصف حقل آخر يستخدم Gemini لإنشاء أوصاف لكل وضعية.
- حمِّل بيانات وضعيات اليوغا كمجموعة من المستندات في مجموعة Firestore مع تضمينات تم إنشاؤها.
- أنشئ فهرسًا مركّبًا في Firestore للسماح بالبحث المتّجه.
- استخدِم "البحث المتّجه" في تطبيق Node.js يجمع كل شيء معًا كما هو موضّح أدناه:

المهام التي ستنفذها
- تصميم تطبيق ويب وإنشاؤه ونشره يستخدم Vector Search لاقتراح أوضاع اليوغا
ما ستتعلمه
- كيفية استخدام Gemini لإنشاء محتوى نصي، وفي سياق هذا الدرس التطبيقي حول الترميز، لإنشاء أوصاف لوضعيات اليوغا
- كيفية تحميل السجلات من مجموعة بيانات محسّنة من Hugging Face إلى Firestore مع تضمينات المتّجهات
- كيفية استخدام ميزة "البحث عن المتّجهات" على Firestore للبحث عن البيانات استنادًا إلى طلب بحث بلغة طبيعية
- كيفية استخدام Google Cloud Text to Speech API لإنشاء محتوى صوتي
المتطلبات
- متصفّح الويب Chrome
- حساب Gmail
- مشروع على السحابة الإلكترونية تم تفعيل الفوترة فيه
يستخدم هذا الدرس التطبيقي حول الترميز، المصمّم للمطوّرين من جميع المستويات (بما في ذلك المبتدئين)، JavaScript وNode.js في التطبيق النموذجي. ومع ذلك، لا يُشترط معرفة JavaScript وNode.js لفهم المفاهيم المقدَّمة.
2. قبل البدء
إنشاء مشروع
- في Google Cloud Console، ضمن صفحة اختيار المشروع، اختَر مشروعًا على Google 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>
- فعِّل واجهات برمجة التطبيقات المطلوبة من خلال الأمر الموضّح أدناه. قد تستغرق هذه العملية بضع دقائق، لذا يُرجى الانتظار.
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، البحث عن كل منتج في وحدة التحكّم أو استخدام هذا الرابط.
في حال عدم توفّر أي واجهة برمجة تطبيقات، يمكنك تفعيلها في أي وقت أثناء عملية التنفيذ.
راجِع المستندات لمعرفة أوامر gcloud وطريقة استخدامها.
استنساخ المستودع وضبط إعدادات البيئة
الخطوة التالية هي استنساخ مستودع الرموز النموذجية الذي سنشير إليه في بقية الدرس العملي. بافتراض أنّك في Cloud Shell، أدخِل الأمر التالي من الدليل الرئيسي:
git clone https://github.com/rominirani/yoga-poses-recommender-nodejs
لتشغيل المحرِّر، انقر على "فتح المحرِّر" في شريط الأدوات في نافذة Cloud Shell. انقر على شريط القوائم في أعلى يمين الصفحة واختَر "ملف" (File) → "فتح مجلد" (Open Folder) كما هو موضّح أدناه:

اختَر مجلد 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 هي قاعدة بيانات مستنِدة إلى مستندات وبدون خادم تتم إدارتها بالكامل، وسنستخدمها كخادم خلفي لبيانات تطبيقنا. يتم تنظيم البيانات في Cloud Firestore في مجموعات من المستندات.
تهيئة قاعدة بيانات Firestore
انتقِل إلى صفحة Firestore في Cloud Console.
إذا لم يسبق لك إعداد قاعدة بيانات Firestore في المشروع، أنشئ قاعدة بيانات default من خلال النقر على Create Database. أثناء إنشاء قاعدة البيانات، استخدِم القيم التالية:
- وضع Firestore:
Native. - اختَر نوع الموقع الجغرافي
Regionواختَر الموقع الجغرافيus-central1للمنطقة. - بالنسبة إلى "قواعد الأمان"، اختَر
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 Editor وألقِ نظرة على قائمة عناصر 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();
سيضيف هذا التطبيق حقل 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 الجديدة، لذا يُرجى الانتظار.
في ما يلي مثال على عملية تنفيذ قيد التقدّم:

بعد تحسين جميع السجلات الثلاثة باستخدام طلب 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 Embeddings لإنشاء التضمين لحقل المحتوى. ستتم إضافة عملية التضمين هذه إلى كل سجلّ مستند، ثم سنستخدم 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 Console.

انقر على قاعدة البيانات (الافتراضية)، من المفترض أن يؤدي ذلك إلى عرض المجموعة test-poses ومستندات متعددة ضمن هذه المجموعة. كل مستند هو وضعية يوغا واحدة.

انقر على أيّ من المستندات للتحقّق من الحقول. بالإضافة إلى الحقول التي استوردناها، ستجد أيضًا الحقل embedding، وهو حقل متّجه، وقد أنشأنا قيمته باستخدام نموذج text-embedding-004 Vertex AI Embedding.

بعد تحميل السجلات إلى قاعدة بيانات 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 الذي يحتوي على معالج /search، والذي يتلقّى الطلب الذي تم تمريره من ملف index.html 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 APIs، وإقرارك بالموافقة على أذونات مختلفة، لذا يُرجى إجراء ذلك.
ستستغرق عملية النشر حوالي 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، وينشئ التضمينات، ويجري عملية "بحث عن التشابه بين المتجهات" استنادًا إلى طلبات المستخدمين.