Lớp học lập trình – Tạo ứng dụng đề xuất tư thế Yoga theo ngữ cảnh bằng Firestore, Vector Search, Langchain và Gemini (phiên bản Node.js)

1. Giới thiệu

Trong lớp học lập trình này, bạn sẽ tạo một ứng dụng sử dụng tính năng tìm kiếm vectơ để đề xuất các tư thế tập yoga.

Trong lớp học lập trình này, bạn sẽ sử dụng phương pháp từng bước như sau:

  1. Sử dụng một Tập dữ liệu hiện có của Hugging Face về các tư thế Yoga (định dạng JSON).
  2. Nâng cao tập dữ liệu bằng nội dung mô tả trường bổ sung sử dụng Gemini để tạo nội dung mô tả cho từng tư thế.
  3. Tải dữ liệu về các tư thế Yoga dưới dạng một tập hợp các Tài liệu trong bộ sưu tập Firestore có các embeddings được tạo.
  4. Tạo một chỉ mục kết hợp trong Firestore để cho phép tìm kiếm vectơ.
  5. Sử dụng tính năng Tìm kiếm vectơ trong một ứng dụng Node.js kết hợp mọi thứ như minh hoạ dưới đây:

84e1cbf29cbaeedc.png

Bạn sẽ thực hiện

  • Thiết kế, xây dựng và triển khai một ứng dụng web sử dụng tính năng Tìm kiếm vectơ để đề xuất các tư thế tập yoga.

Kiến thức bạn sẽ học được

  • Cách sử dụng Gemini để tạo nội dung văn bản và trong bối cảnh của lớp học lập trình này, hãy tạo nội dung mô tả cho các tư thế yoga
  • Cách tải các bản ghi từ một tập dữ liệu nâng cao từ Hugging Face vào Firestore cùng với vectơ nhúng
  • Cách sử dụng tính năng Tìm kiếm vectơ của Firestore để tìm kiếm dữ liệu dựa trên một cụm từ tìm kiếm bằng ngôn ngữ tự nhiên
  • Cách sử dụng Google Cloud Text to Speech API để tạo nội dung âm thanh

Bạn cần có

  • Trình duyệt web Chrome
  • Tài khoản Gmail
  • Một Dự án trên đám mây đã bật tính năng thanh toán

Lớp học lập trình này được thiết kế cho nhà phát triển ở mọi cấp độ (kể cả người mới bắt đầu), sử dụng JavaScript và Node.js trong ứng dụng mẫu. Tuy nhiên, bạn không cần có kiến thức về JavaScript và Node.js để hiểu các khái niệm được trình bày.

2. Trước khi bắt đầu

Tạo dự án

  1. Trong Google Cloud Console, trên trang chọn dự án, hãy chọn hoặc tạo một dự án trên Google Cloud.
  2. Đảm bảo rằng bạn đã bật tính năng thanh toán cho dự án trên Cloud. Tìm hiểu cách kiểm tra xem tính năng thanh toán có được bật trong một dự án hay không .
  3. Bạn sẽ sử dụng Cloud Shell, một môi trường dòng lệnh chạy trong Google Cloud và được tải sẵn bq. Nhấp vào Kích hoạt Cloud Shell ở đầu bảng điều khiển Cloud.

Hình ảnh nút Kích hoạt Cloud Shell

  1. Sau khi kết nối với Cloud Shell, bạn có thể kiểm tra để đảm bảo rằng bạn đã được xác thực và dự án được đặt thành mã dự án của bạn bằng lệnh sau:
gcloud auth list
  1. Chạy lệnh sau trong Cloud Shell để xác nhận rằng lệnh gcloud biết về dự án của bạn.
gcloud config list project
  1. Nếu bạn chưa đặt dự án, hãy dùng lệnh sau để đặt:
gcloud config set project <YOUR_PROJECT_ID>
  1. Bật các API bắt buộc thông qua lệnh bên dưới. Quá trình này có thể mất vài phút, vì vậy, vui lòng kiên nhẫn chờ đợi.
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

Khi thực thi lệnh thành công, bạn sẽ thấy một thông báo tương tự như thông báo dưới đây:

Operation "operations/..." finished successfully.

Bạn có thể thay thế lệnh gcloud bằng cách tìm kiếm từng sản phẩm trên bảng điều khiển hoặc sử dụng đường liên kết này.

Nếu bỏ lỡ API nào, bạn luôn có thể bật API đó trong quá trình triển khai.

Tham khảo tài liệu để biết các lệnh và cách sử dụng gcloud.

Sao chép kho lưu trữ và thiết lập chế độ cài đặt môi trường

Bước tiếp theo là sao chép kho lưu trữ mẫu mà chúng ta sẽ tham chiếu trong phần còn lại của lớp học lập trình. Giả sử bạn đang ở trong Cloud Shell, hãy đưa ra lệnh sau từ thư mục chính của bạn:

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

Để khởi chạy trình chỉnh sửa, hãy nhấp vào Open Editor (Mở trình chỉnh sửa) trên thanh công cụ của cửa sổ Cloud Shell. Nhấp vào thanh trình đơn ở góc trên cùng bên trái rồi chọn Tệp → Mở thư mục như minh hoạ dưới đây:

66221fd0d0e5202f.png

Chọn thư mục yoga-poses-recommender-nodejs. Bạn sẽ thấy thư mục này mở ra với các tệp sau đây như minh hoạ bên dưới:

7dbe126ee112266d.png

Bây giờ, chúng ta cần thiết lập các biến môi trường mà chúng ta sẽ sử dụng. Nhấp vào tệp env-template, bạn sẽ thấy nội dung như hình bên dưới:

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

Vui lòng cập nhật các giá trị cho PROJECT_IDLOCATION theo những gì bạn đã chọn khi tạo Dự án trên Google Cloud và khu vực Cơ sở dữ liệu Firestore. Lý tưởng nhất là các giá trị của LOCATION phải giống nhau đối với Dự án Google Cloud và Cơ sở dữ liệu Firestore, ví dụ: us-central1.

Để phục vụ mục đích của lớp học lập trình này, chúng ta sẽ sử dụng các giá trị sau (ngoại trừ PROJECT_IDLOCATION, bạn cần đặt theo cấu hình của mình.

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

Vui lòng lưu tệp này dưới dạng .env trong cùng thư mục với tệp env-template.

Chuyển đến trình đơn chính ở trên cùng bên trái trong Cloud Shell IDE, rồi nhấp vào Terminal → New Terminal.

Chuyển đến thư mục gốc của kho lưu trữ mà bạn đã sao chép bằng lệnh sau:

cd yoga-poses-recommender-nodejs

Cài đặt các phần phụ thuộc Node.js thông qua lệnh:

npm install

Tuyệt vời! Giờ đây, chúng ta đã sẵn sàng chuyển sang nhiệm vụ thiết lập cơ sở dữ liệu Firestore.

3. Thiết lập Firestore

Cloud Firestore là một cơ sở dữ liệu tài liệu không máy chủ, được quản lý toàn diện mà chúng ta sẽ dùng làm phần phụ trợ cho dữ liệu ứng dụng. Dữ liệu trong Cloud Firestore được cấu trúc thành các tập hợp gồm tài liệu.

Khởi chạy cơ sở dữ liệu Firestore

Truy cập vào trang Firestore trong bảng điều khiển Cloud.

Nếu trước đây bạn chưa khởi tạo cơ sở dữ liệu Firestore trong dự án, hãy tạo cơ sở dữ liệu default bằng cách nhấp vào Create Database. Trong quá trình tạo cơ sở dữ liệu, hãy sử dụng các giá trị sau:

  • Chế độ Firestore: Native.
  • Chọn Loại địa điểm là Region rồi chọn địa điểm us-central1 cho khu vực.
  • Đối với Quy tắc bảo mật, hãy chọn Test rules.
  • Tạo cơ sở dữ liệu.

61d0277510803c8d.png

Trong phần tiếp theo, chúng ta sẽ đặt nền tảng để tạo một bộ sưu tập có tên là poses trong cơ sở dữ liệu Firestore mặc định. Tập hợp này sẽ chứa dữ liệu mẫu (tài liệu) hoặc thông tin về các tư thế Yoga mà chúng ta sẽ sử dụng trong ứng dụng.

Đến đây là kết thúc phần thiết lập cơ sở dữ liệu Firestore.

4. Chuẩn bị tập dữ liệu tư thế Yoga

Nhiệm vụ đầu tiên của chúng ta là chuẩn bị tập dữ liệu Tư thế yoga mà chúng ta sẽ sử dụng cho ứng dụng. Chúng tôi sẽ bắt đầu với một tập dữ liệu hiện có của Hugging Face, sau đó cải thiện tập dữ liệu đó bằng thông tin bổ sung.

Hãy xem Tập dữ liệu Hugging Face cho các tư thế yoga. Xin lưu ý rằng mặc dù lớp học lập trình này sử dụng một trong các tập dữ liệu, nhưng bạn có thể sử dụng bất kỳ tập dữ liệu nào khác và làm theo các kỹ thuật tương tự như đã minh hoạ để cải thiện tập dữ liệu.

298cfae7f23e4bef.png

Nếu chuyển đến phần Files and versions, chúng ta có thể lấy tệp dữ liệu JSON cho tất cả các tư thế.

3fe6e55abdc032ec.png

Chúng tôi đã tải yoga_poses.json xuống và cung cấp tệp đó cho bạn. Tệp này có tên là yoga_poses_alldata.json và nằm trong thư mục /data.

Chuyển đến tệp data/yoga_poses.json trong Cloud Shell Editor rồi xem danh sách các đối tượng JSON, trong đó mỗi đối tượng JSON đại diện cho một tư thế Yoga. Chúng ta có tổng cộng 3 bản ghi và một bản ghi mẫu được trình bày bên dưới:

{
   "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"]
 }

Đây là cơ hội tuyệt vời để chúng ta giới thiệu Gemini và cách sử dụng chính mô hình mặc định để tạo trường description cho mô hình này.

Trong Trình chỉnh sửa Cloud Shell, hãy chuyển đến tệp generate-descriptions.js. Dưới đây là nội dung của tệp này:

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();

Ứng dụng này sẽ thêm một trường description mới vào mỗi bản ghi JSON về tư thế Yoga. Ứng dụng sẽ lấy nội dung mô tả thông qua một lệnh gọi đến mô hình Gemini, trong đó chúng tôi sẽ cung cấp cho mô hình này câu lệnh cần thiết. Trường này sẽ được thêm vào tệp JSON và tệp mới sẽ được ghi vào tệp data/yoga_poses_with_descriptions.json.

Hãy cùng tìm hiểu các bước chính:

  1. Trong hàm main(), bạn sẽ thấy rằng hàm này gọi hàm add_descriptions_to_json và cung cấp tệp đầu vào cũng như tệp đầu ra dự kiến.
  2. Hàm add_descriptions_to_json sẽ thực hiện những việc sau cho mỗi bản ghi JSON, tức là thông tin về bài đăng trên Yoga:
  3. Thao tác này sẽ trích xuất pose_name, sanskrit_name, expertise_levelpose_types.
  4. Thao tác này sẽ gọi hàm callGemini để tạo một câu lệnh, sau đó gọi lớp mô hình LangchainVertexAI để nhận văn bản phản hồi.
  5. Sau đó, văn bản phản hồi này sẽ được thêm vào đối tượng JSON.
  6. Sau đó, danh sách đối tượng JSON đã cập nhật sẽ được ghi vào tệp đích.

Hãy chạy ứng dụng này. Khởi chạy một cửa sổ dòng lệnh mới (Ctrl+Shift+C) và đưa ra lệnh sau:

npm run generate-descriptions

Nếu bạn được yêu cầu uỷ quyền, vui lòng cung cấp thông tin đó.

Bạn sẽ thấy ứng dụng bắt đầu thực thi. Chúng tôi đã thêm độ trễ 30 giây giữa các bản ghi để tránh mọi hạn mức giới hạn tốc độ có thể có trên tài khoản Google Cloud mới, vì vậy, vui lòng kiên nhẫn chờ đợi.

Dưới đây là ví dụ về một lượt chạy đang diễn ra:

469ede91ba007c1f.png

Sau khi cả 3 bản ghi được tăng cường bằng lệnh gọi Gemini, một tệp data/yoga_poses_with_description.json sẽ được tạo. Bạn có thể xem thông tin đó.

Chúng ta đã chuẩn bị xong tệp dữ liệu và bước tiếp theo là tìm hiểu cách điền dữ liệu vào Cơ sở dữ liệu Firestore, cùng với việc tạo các mục nhúng.

5. Nhập dữ liệu vào Firestore và tạo các vectơ nhúng

Chúng ta có tệp data/yoga_poses_with_description.json và giờ cần điền dữ liệu vào Cơ sở dữ liệu Firestore, đồng thời tạo Vector Embeddings cho từng bản ghi. Vector Embeddings sẽ hữu ích sau này khi chúng ta phải thực hiện tìm kiếm tương tự trên các Vector Embeddings đó bằng truy vấn của người dùng được cung cấp bằng ngôn ngữ tự nhiên.

Các bước thực hiện như sau:

  1. Chúng ta sẽ chuyển đổi danh sách đối tượng JSON thành danh sách đối tượng. Mỗi tài liệu sẽ có hai thuộc tính: contentmetadata. Đối tượng siêu dữ liệu sẽ chứa toàn bộ đối tượng JSON có các thuộc tính như name, description, sanskrit_name, v.v. content sẽ là một văn bản dạng chuỗi, là phép nối của một số trường.
  2. Sau khi có danh sách tài liệu, chúng ta sẽ sử dụng lớp Vertex AI Embeddings để tạo vectơ nhúng cho trường nội dung. Thao tác nhúng này sẽ được thêm vào từng bản ghi tài liệu, sau đó chúng ta sẽ dùng API Firestore để lưu danh sách các đối tượng tài liệu này trong bộ sưu tập (chúng ta đang dùng biến TEST_COLLECTION trỏ đến test-poses).

Mã cho import-data.js được cung cấp bên dưới (một số phần của mã đã bị cắt bớt để ngắn gọn):

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();

Hãy chạy ứng dụng này. Khởi chạy một cửa sổ dòng lệnh mới (Ctrl+Shift+C) và đưa ra lệnh sau:

npm run import-data

Nếu mọi thứ diễn ra suôn sẻ, bạn sẽ thấy một thông báo tương tự như thông báo dưới đây:

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.

Để kiểm tra xem các bản ghi đã được chèn thành công và các embeddings đã được tạo hay chưa, hãy truy cập vào trang Firestore trong bảng điều khiển Cloud.

504cabdb99a222a5.png

Nhấp vào cơ sở dữ liệu (mặc định), thao tác này sẽ cho thấy bộ sưu tập test-poses và nhiều tài liệu trong bộ sưu tập đó. Mỗi tài liệu là một tư thế Yoga.

9f37aa199c4b547a.png

Nhấp vào một chứng từ bất kỳ để xem các trường. Ngoài các trường mà chúng ta đã nhập, bạn cũng sẽ thấy trường embedding. Đây là một trường Vector, có giá trị mà chúng ta đã tạo thông qua mô hình Nhúng Vertex AI text-embedding-004.

f0ed92124519beaf.png

Giờ đây, sau khi tải các bản ghi lên Cơ sở dữ liệu Firestore cùng với các mục nhúng, chúng ta có thể chuyển sang bước tiếp theo và xem cách thực hiện Tìm kiếm tương tự theo vectơ trong Firestore.

6. Nhập toàn bộ tư thế Yoga vào bộ sưu tập Cơ sở dữ liệu Firestore

Bây giờ, chúng ta sẽ tạo bộ sưu tập poses. Đây là danh sách đầy đủ gồm 160 tư thế Yoga. Chúng ta đã tạo một tệp nhập cơ sở dữ liệu mà bạn có thể nhập trực tiếp. Việc này giúp tiết kiệm thời gian trong phòng thí nghiệm. Quy trình tạo cơ sở dữ liệu chứa nội dung mô tả và các vectơ nhúng cũng giống như quy trình mà chúng ta đã thấy trong phần trước.

Nhập cơ sở dữ liệu bằng cách làm theo các bước dưới đây:

  1. Tạo một bộ chứa trong dự án bằng lệnh gsutil được cung cấp bên dưới. Thay thế biến <PROJECT_ID> trong lệnh bên dưới bằng mã dự án của bạn trên Google Cloud.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
  1. Sau khi tạo vùng chứa, chúng ta cần sao chép dữ liệu xuất của cơ sở dữ liệu mà chúng ta đã chuẩn bị vào vùng chứa này, trước khi có thể nhập dữ liệu đó vào cơ sở dữ liệu Firebase. Sử dụng lệnh bên dưới:
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615  gs://<PROJECT_ID>-my-bucket

Bây giờ, khi đã có dữ liệu để nhập, chúng ta có thể chuyển sang bước cuối cùng là nhập dữ liệu vào cơ sở dữ liệu Firebase (default) mà chúng ta đã tạo.

  1. Sử dụng lệnh gcloud bên dưới:
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615

Quá trình nhập sẽ mất vài giây và sau khi hoàn tất, bạn có thể xác thực cơ sở dữ liệu Firestore và tập hợp bằng cách truy cập vào https://console.cloud.google.com/firestore/databases, chọn cơ sở dữ liệu default và tập hợp poses như minh hoạ bên dưới:

561f3cb840de23d8.png

Thao tác này sẽ hoàn tất việc tạo bộ sưu tập Firestore mà chúng ta sẽ sử dụng trong ứng dụng.

7. Thực hiện tìm kiếm mức độ tương đồng của vectơ trong Firestore

Để thực hiện tìm kiếm Mức độ tương đồng của vectơ, chúng ta sẽ lấy truy vấn từ người dùng. Ví dụ về truy vấn này có thể là "Suggest me some exercises to relieve back pain".

Hãy xem tệp search-data.js. Hàm chính cần xem xét là hàm search, được minh hoạ bên dưới. Nhìn chung, lớp này tạo ra một lớp nhúng sẽ được dùng để tạo lớp nhúng cho truy vấn của người dùng. Sau đó, nó thiết lập kết nối với cơ sở dữ liệu và bộ sưu tập Firestore. Sau đó, trên tập hợp, nó sẽ gọi phương thức findNearest, phương thức này thực hiện một thao tác Tìm kiếm tương tự theo vectơ.

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}`);
 }
}

Trước khi chạy thao tác này với một vài ví dụ về truy vấn, trước tiên, bạn phải tạo một chỉ mục kết hợp Firestore. Chỉ mục này là cần thiết để các truy vấn tìm kiếm của bạn thành công. Nếu chạy ứng dụng mà không tạo chỉ mục, bạn sẽ thấy một lỗi cho biết rằng trước tiên bạn cần tạo chỉ mục bằng lệnh tạo chỉ mục.

Lệnh gcloud để tạo chỉ mục kết hợp được trình bày dưới đây:

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

Quá trình lập chỉ mục sẽ mất vài phút để hoàn tất vì có hơn 150 bản ghi trong cơ sở dữ liệu. Sau khi hoàn tất, bạn có thể xem chỉ mục thông qua lệnh bên dưới:

gcloud firestore indexes composite list

Bạn sẽ thấy chỉ mục mà mình vừa tạo trong danh sách.

Hãy thử lệnh sau ngay bây giờ:

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

Bạn sẽ nhận được một số đề xuất. Sau đây là ví dụ về một lần chạy:

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']

Sau khi bạn hoàn tất bước này, bạn sẽ hiểu cách sử dụng Cơ sở dữ liệu vectơ Firestore để tải bản ghi lên, tạo mục nhúng và thực hiện Tìm kiếm độ tương đồng của vectơ. Giờ đây, chúng ta có thể tạo một ứng dụng web tích hợp tính năng tìm kiếm vectơ vào giao diện người dùng web.

8. Ứng dụng web

Ứng dụng web Python Flask có trong tệp app.js và tệp HTML giao diện người dùng có trong views/index.html.

Bạn nên xem cả hai tệp này. Bắt đầu bằng tệp app.js chứa trình xử lý /search. Trình xử lý này sẽ lấy câu lệnh đã được truyền từ tệp index.html HTML của giao diện người dùng. Sau đó, thao tác này sẽ gọi phương thức tìm kiếm, thực hiện tìm kiếm Tương đồng vectơ mà chúng ta đã xem xét ở phần trước.

Sau đó, phản hồi sẽ được gửi lại cho index.html cùng với danh sách đề xuất. Sau đó, index.html sẽ hiển thị các đề xuất dưới dạng các thẻ riêng biệt.

Chạy ứng dụng cục bộ

Khởi chạy một cửa sổ dòng lệnh mới (Ctrl+Shift+C) hoặc bất kỳ cửa sổ dòng lệnh hiện có nào và đưa ra lệnh sau:

npm run start

Dưới đây là một ví dụ về quá trình thực thi:

...
Server listening on port 8080

Sau khi ứng dụng chạy, hãy truy cập vào URL trang chủ của ứng dụng bằng cách nhấp vào nút Web Preview (Xem trước trên web) như minh hoạ bên dưới:

de297d4cee10e0bf.png

Thao tác này sẽ cho bạn thấy tệp index.html được phân phát như minh hoạ dưới đây:

20240a0e885ac17b.png

Cung cấp một cụm từ tìm kiếm mẫu (Ví dụ : Provide me some exercises for back pain relief) rồi nhấp vào nút Search. Thao tác này sẽ truy xuất một số đề xuất từ cơ sở dữ liệu. Bạn cũng sẽ thấy nút Play Audio. Nút này sẽ tạo một luồng âm thanh dựa trên nội dung mô tả mà bạn có thể nghe trực tiếp.

789b4277dc40e2be.png

9. (Không bắt buộc) Triển khai lên Google Cloud Run

Bước cuối cùng là triển khai ứng dụng này lên Google Cloud Run. Lệnh triển khai xuất hiện bên dưới. Hãy nhớ rằng trước khi triển khai, bạn phải thay thế các giá trị trong dấu ngoặc (<<>>) xuất hiện bên dưới. Đây là những giá trị mà bạn có thể truy xuất từ tệp .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>>

Thực thi lệnh trên từ thư mục gốc của ứng dụng. Bạn cũng có thể được yêu cầu bật Google Cloud API và xác nhận cấp nhiều quyền. Vui lòng thực hiện việc này.

Quá trình triển khai sẽ mất khoảng 5 đến 7 phút để hoàn tất, vì vậy, vui lòng kiên nhẫn chờ đợi.

3a6d86fd32e4a5e.png

Sau khi triển khai thành công, đầu ra triển khai sẽ cung cấp URL dịch vụ Cloud Run. Nội dung này sẽ có dạng:

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

Truy cập vào URL công khai đó và bạn sẽ thấy ứng dụng web tương tự được triển khai và chạy thành công.

84e1cbf29cbaeedc.png

Bạn cũng có thể truy cập vào Cloud Run từ bảng điều khiển Cloud và bạn sẽ thấy danh sách các dịch vụ trong Cloud Run. Dịch vụ yogaposes phải là một trong các dịch vụ (nếu không phải là dịch vụ duy nhất) được liệt kê ở đó.

f2b34a8c9011be4c.png

Bạn có thể xem thông tin chi tiết về dịch vụ như URL, cấu hình, nhật ký và nhiều thông tin khác bằng cách nhấp vào tên dịch vụ cụ thể (yogaposes trong trường hợp này).

faaa5e0c02fe0423.png

Đến đây là bạn đã hoàn tất việc phát triển và triển khai ứng dụng web đề xuất tư thế Yoga trên Cloud Run.

10. Xin chúc mừng

Xin chúc mừng! Bạn đã tạo thành công một ứng dụng tải một tập dữ liệu lên Firestore, tạo các mục nhúng và thực hiện một thao tác Tìm kiếm tương tự theo vectơ dựa trên truy vấn của người dùng.

Tài liệu tham khảo