Codelab — создайте контекстное рекомендательное приложение для поз йоги с помощью Firestore, Vector Search, Langchain и Gemini (версия Node.js)

1. Введение

В этой кодовой лаборатории вы создадите приложение, которое использует векторный поиск, чтобы рекомендовать позы йоги.

Используя кодовую лабораторию, вы будете применять пошаговый подход следующим образом:

  1. Используйте существующий набор данных обнимающих лиц с позами йоги (формат JSON).
  2. Дополните набор данных дополнительным описанием поля, которое использует Gemini для создания описаний для каждой из поз.
  3. Загрузите данные Yoga как коллекцию документов в коллекцию Firestore с сгенерированными вложениями.
  4. Создайте составной индекс в Firestore, чтобы разрешить векторный поиск.
  5. Используйте векторный поиск в приложении Node.js, который объединяет все, как показано ниже:

84e1cbf29cbaeedc.png

Что ты будешь делать

  • Спроектируйте, создайте и разверните веб-приложение, которое использует векторный поиск для рекомендации поз йоги.

Что вы узнаете

  • Как использовать Gemini для создания текстового контента и в контексте этой лаборатории создания описаний поз йоги.
  • Как загрузить записи из расширенного набора данных из Hugging Face в Firestore вместе с векторными встраиваниями
  • Как использовать векторный поиск Firestore для поиска данных на основе запроса на естественном языке
  • Как использовать Google Cloud Text to Speech API для создания аудиоконтента

Что вам понадобится

  • Веб-браузер Chrome
  • Учетная запись Gmail
  • Облачный проект с включенной оплатой

Эта лаборатория кода, предназначенная для разработчиков всех уровней (включая новичков), использует в своем примере приложения JavaScript и Node.js. Однако знание JavaScript и Node.js не требуется для понимания представленных концепций.

2. Прежде чем начать

Создать проект

  1. В Google Cloud Console на странице выбора проекта выберите или создайте проект Google Cloud.
  2. Убедитесь, что для вашего облачного проекта включена оплата. Узнайте, как проверить, включена ли оплата в проекте .
  3. Вы будете использовать Cloud Shell , среду командной строки, работающую в Google Cloud, в которую предварительно загружен bq. Нажмите «Активировать Cloud Shell» в верхней части консоли Google Cloud.

Изображение кнопки «Активировать Cloud Shell»

  1. После подключения к Cloud Shell вы проверяете, что вы уже прошли аутентификацию и что для проекта установлен идентификатор вашего проекта, с помощью следующей команды:
gcloud auth list
  1. Выполните следующую команду в Cloud Shell, чтобы убедиться, что команда gcloud знает о вашем проекте.
gcloud config list project
  1. Если ваш проект не установлен, используйте следующую команду, чтобы установить его:
gcloud config set project <YOUR_PROJECT_ID>
  1. Включите необходимые 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

Чтобы запустить редактор, нажмите «Открыть редактор» на панели инструментов окна Cloud Shell. Нажмите на строку меню в верхнем левом углу и выберите «Файл» → «Открыть папку», как показано ниже:

66221fd0d0e5202f.png

Выберите папку yoga-poses-recommender-nodejs , и вы увидите открытую папку со следующими файлами, как показано ниже:

7dbe126ee112266d.png

Теперь нам нужно настроить переменные среды, которые мы будем использовать. Нажмите на файл 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 Project и базы данных 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 в облачной консоли.

Если вы ранее не инициализировали базу данных Firestore в проекте, создайте базу данных default , нажав Create Database . При создании базы данных используйте следующие значения:

  • Режим Firestore: Native.
  • Местоположение: используйте настройки местоположения по умолчанию.
  • В разделе «Правила безопасности» выберите Test rules .
  • Создайте базу данных.

504cabdb99a222a5.png

В следующем разделе мы заложим основу для создания коллекции poses в нашей базе данных Firestore по умолчанию. Эта коллекция будет содержать образцы данных (документов) или информацию о позах йоги, которую мы затем будем использовать в нашем приложении.

На этом раздел настройки базы данных Firestore завершен.

4. Подготовьте набор данных по позам йоги.

Наша первая задача — подготовить набор данных Yoga Poses, который мы будем использовать для приложения. Мы начнем с существующего набора данных Hugging Face, а затем дополним его дополнительной информацией.

Ознакомьтесь с набором данных Hugging Face для поз йоги . Обратите внимание: хотя в этой кодовой лаборатории используется один из наборов данных, на самом деле вы можете использовать любой другой набор данных и следовать тем же методам, которые продемонстрированы для улучшения набора данных.

298cfae7f23e4bef.png

Если мы перейдем в раздел Files and versions , мы сможем получить файл данных JSON для всех поз.

3fe6e55abdc032ec.png

Мы скачали 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 перейдите к 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 .

Пройдемся по основным шагам:

  1. В функции main() вы обнаружите, что она вызывает функцию add_descriptions_to_json и предоставляет ожидаемый входной файл и ожидаемый выходной файл.
  2. Функция add_descriptions_to_json выполняет следующие действия для каждой записи JSON, т. е. информации о публикации Yoga:
  3. Он извлекает pose_name , sanskrit_name , expertise_level и pose_types .
  4. Он вызывает функцию callGemini , которая создает приглашение, а затем вызывает класс модели LangchainVertexAI для получения текста ответа.
  5. Этот текст ответа затем добавляется в объект JSON.
  6. Обновленный список объектов JSON затем записывается в целевой файл.

Давайте запустим это приложение. Запустите новое окно терминала (Ctrl+Shift+C) и введите следующую команду:

npm run generate-descriptions

Если вас попросят какое-либо разрешение, пожалуйста, предоставьте его.

Вы обнаружите, что приложение начинает выполняться. Мы добавили задержку в 30 секунд между записями, чтобы избежать ограничений скорости, которые могут возникнуть в новых учетных записях Google Cloud, поэтому наберитесь терпения.

Пример текущего запуска показан ниже:

469ede91ba007c1f.png

После того как все три записи будут улучшены с помощью вызова Gemini, будет создан файл data/yoga_poses_with_description.json . Вы можете взглянуть на это.

Теперь мы готовы с нашим файлом данных, и следующим шагом будет понимание того, как заполнить им базу данных Firestore, а также генерировать встраивания.

5. Импортируйте данные в Firestore и создавайте векторные вложения.

У нас есть файл data/yoga_poses_with_description.json , и теперь нам нужно заполнить им базу данных Firestore и, что немаловажно, сгенерировать векторные внедрения для каждой из записей. Векторные встраивания будут полезны позже, когда нам придется выполнять поиск по их сходству с пользовательским запросом, предоставленным на естественном языке.

Шаги для этого будут следующими:

  1. Мы преобразуем список объектов JSON в список объектов. Каждый документ будет иметь два атрибута: content и metadata . Объект метаданных будет содержать весь объект JSON с такими атрибутами, как name , description , sanskrit_name и т. д. content будет текстовая строка, представляющая собой объединение нескольких полей.
  2. Когда у нас будет список документов, мы будем использовать класс 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 в облачной консоли.

504cabdb99a222a5.png

Нажмите на базу данных (по умолчанию), она должна показать коллекцию test-poses и несколько документов в этой коллекции. Каждый документ — это одна поза йоги.

9f37aa199c4b547a.png

Нажмите на любой из документов, чтобы изучить поля. В дополнение к полям, которые мы импортировали, вы также найдете поле embedding , которое представляет собой векторное поле, значение которого мы сгенерировали с помощью модели text-embedding-004 Vertex AI Embedding.

f0ed92124519beaf.png

Теперь, когда у нас есть записи, загруженные в базу данных Firestore с встраиваниями, мы можем перейти к следующему шагу и посмотреть, как выполнить поиск по сходству векторов в Firestore.

6. Импортируйте полные позы йоги в коллекцию базы данных Firestore.

Теперь мы создадим коллекцию poses , которая представляет собой полный список из 160 поз йоги, для которых мы создали файл импорта базы данных, который вы можете импортировать напрямую. Это сделано для экономии времени в лаборатории. Процесс создания базы данных, содержащей описание и внедрения, аналогичен тому, что мы видели в предыдущем разделе.

Импортируйте базу данных, выполнив действия, указанные ниже:

  1. Создайте корзину в своем проекте с помощью команды gsutil приведенной ниже. Замените переменную <PROJECT_ID> в приведенной ниже команде идентификатором своего проекта Google Cloud.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
  1. Теперь, когда корзина создана, нам нужно скопировать подготовленный нами экспорт базы данных в эту корзину, прежде чем мы сможем импортировать ее в базу данных Firebase. Используйте команду, указанную ниже:
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615  gs://<PROJECT_ID>-my-bucket

Теперь, когда у нас есть данные для импорта, мы можем перейти к последнему этапу импорта данных в созданную нами базу данных Firebase ( default ).

  1. Используйте команду 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 , как показано ниже:

561f3cb840de23d8.png

На этом создание коллекции 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 , который принимает приглашение, переданное из внешнего HTML-файла index.html . Затем вызывается метод search, который выполняет поиск по сходству векторов, который мы рассматривали в предыдущем разделе.

Затем ответ отправляется обратно в index.html со списком рекомендаций. Затем index.html отображает рекомендации в виде разных карточек.

Запустите приложение локально

Запустите новое окно терминала (Ctrl+Shift+C) или любое существующее окно терминала и введите следующую команду:

npm run start

Пример выполнения показан ниже:

...
Server listening on port 8080

После запуска посетите домашний URL-адрес приложения, нажав кнопку «Просмотр в Интернете», показанную ниже:

de297d4cee10e0bf.png

Он должен показать вам файл index.html , как показано ниже:

20240a0e885ac17b.png

Введите образец запроса (Пример: Provide me some exercises for back pain relief ) и нажмите кнопку Search . Это должно получить некоторые рекомендации из базы данных. Вы также увидите кнопку Play Audio , которая сгенерирует аудиопоток на основе описания, которое вы сможете услышать напрямую.

789b4277dc40e2be.png

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 минут, поэтому наберитесь терпения.

3a6d86fd32e4a5e.png

После успешного развертывания в результатах развертывания будет указан URL-адрес службы Cloud Run. Это будет иметь вид:

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

Посетите этот общедоступный URL-адрес, и вы увидите, что то же самое веб-приложение развернуто и успешно работает.

84e1cbf29cbaeedc.png

Вы также можете посетить Cloud Run из консоли Google Cloud и увидеть список сервисов в Cloud Run. Служба yogaposes должна быть одной из служб (если не единственной), перечисленных там.

f2b34a8c9011be4c.png

Вы можете просмотреть подробную информацию о службе, такую ​​​​как URL-адрес, конфигурации, журналы и многое другое, щелкнув имя конкретной службы (в нашем случае yogaposes ).

faaa5e0c02fe0423.png

На этом разработка и развертывание нашего веб-приложения с рекомендациями по позам йоги завершены в Cloud Run.

10. Поздравления

Поздравляем, вы успешно создали приложение, которое загружает набор данных в Firestore, генерирует встраивания и выполняет поиск векторного сходства на основе запроса пользователя.

Справочная документация

,

1. Введение

В этой кодовой лаборатории вы создадите приложение, которое использует векторный поиск, чтобы рекомендовать позы йоги.

Используя кодовую лабораторию, вы будете применять пошаговый подход следующим образом:

  1. Используйте существующий набор данных обнимающих лиц с позами йоги (формат JSON).
  2. Дополните набор данных дополнительным описанием поля, которое использует Gemini для создания описаний для каждой из поз.
  3. Загрузите данные Yoga как коллекцию документов в коллекцию Firestore с сгенерированными вложениями.
  4. Создайте составной индекс в Firestore, чтобы разрешить векторный поиск.
  5. Используйте векторный поиск в приложении Node.js, который объединяет все, как показано ниже:

84e1cbf29cbaeedc.png

Что ты будешь делать

  • Спроектируйте, создайте и разверните веб-приложение, которое использует векторный поиск для рекомендации поз йоги.

Что вы узнаете

  • Как использовать Gemini для создания текстового контента и в контексте этой лаборатории создания описаний поз йоги.
  • Как загрузить записи из расширенного набора данных из Hugging Face в Firestore вместе с векторными встраиваниями
  • Как использовать векторный поиск Firestore для поиска данных на основе запроса на естественном языке
  • Как использовать Google Cloud Text to Speech API для создания аудиоконтента

Что вам понадобится

  • Веб-браузер Chrome
  • Учетная запись Gmail
  • Облачный проект с включенной оплатой

Эта лаборатория кода, предназначенная для разработчиков всех уровней (включая новичков), использует в своем примере приложения JavaScript и Node.js. Однако знание JavaScript и Node.js не требуется для понимания представленных концепций.

2. Прежде чем начать

Создать проект

  1. В Google Cloud Console на странице выбора проекта выберите или создайте проект Google Cloud.
  2. Убедитесь, что для вашего облачного проекта включена оплата. Узнайте, как проверить, включена ли оплата в проекте .
  3. Вы будете использовать Cloud Shell , среду командной строки, работающую в Google Cloud, в которую предварительно загружен bq. Нажмите «Активировать Cloud Shell» в верхней части консоли Google Cloud.

Изображение кнопки «Активировать Cloud Shell»

  1. После подключения к Cloud Shell вы проверяете, что вы уже прошли аутентификацию и что для проекта установлен идентификатор вашего проекта, с помощью следующей команды:
gcloud auth list
  1. Выполните следующую команду в Cloud Shell, чтобы убедиться, что команда gcloud знает о вашем проекте.
gcloud config list project
  1. Если ваш проект не установлен, используйте следующую команду, чтобы установить его:
gcloud config set project <YOUR_PROJECT_ID>
  1. Включите необходимые 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

Чтобы запустить редактор, нажмите «Открыть редактор» на панели инструментов окна Cloud Shell. Нажмите на строку меню в верхнем левом углу и выберите «Файл» → «Открыть папку», как показано ниже:

66221fd0d0e5202f.png

Выберите папку yoga-poses-recommender-nodejs , и вы увидите открытую папку со следующими файлами, как показано ниже:

7dbe126ee112266d.png

Теперь нам нужно настроить переменные среды, которые мы будем использовать. Нажмите на файл 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 Project и базы данных 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 в облачной консоли.

Если вы ранее не инициализировали базу данных Firestore в проекте, создайте базу данных default , нажав Create Database . При создании базы данных используйте следующие значения:

  • Режим Firestore: Native.
  • Местоположение: используйте настройки местоположения по умолчанию.
  • В разделе «Правила безопасности» выберите Test rules .
  • Создайте базу данных.

504cabdb99a222a5.png

В следующем разделе мы заложим основу для создания коллекции poses в нашей базе данных Firestore по умолчанию. Эта коллекция будет содержать образцы данных (документов) или информацию о позах йоги, которую мы затем будем использовать в нашем приложении.

На этом раздел настройки базы данных Firestore завершен.

4. Подготовьте набор данных по позам йоги.

Наша первая задача — подготовить набор данных Yoga Poses, который мы будем использовать для приложения. Мы начнем с существующего набора данных Hugging Face, а затем дополним его дополнительной информацией.

Ознакомьтесь с набором данных Hugging Face для поз йоги . Обратите внимание: хотя в этой кодовой лаборатории используется один из наборов данных, на самом деле вы можете использовать любой другой набор данных и следовать тем же методам, которые продемонстрированы для улучшения набора данных.

298cfae7f23e4bef.png

Если мы перейдем в раздел Files and versions , мы сможем получить файл данных JSON для всех поз.

3fe6e55abdc032ec.png

Мы скачали 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 перейдите к 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 .

Пройдемся по основным шагам:

  1. В функции main() вы обнаружите, что она вызывает функцию add_descriptions_to_json и предоставляет ожидаемый входной файл и ожидаемый выходной файл.
  2. Функция add_descriptions_to_json выполняет следующие действия для каждой записи JSON, т. е. информации о публикации Yoga:
  3. Он извлекает pose_name , sanskrit_name , expertise_level и pose_types .
  4. Он вызывает функцию callGemini , которая создает приглашение, а затем вызывает класс модели LangchainVertexAI для получения текста ответа.
  5. Этот текст ответа затем добавляется в объект JSON.
  6. Обновленный список объектов JSON затем записывается в целевой файл.

Давайте запустим это приложение. Запустите новое окно терминала (Ctrl+Shift+C) и введите следующую команду:

npm run generate-descriptions

Если вас попросят какое-либо разрешение, пожалуйста, предоставьте его.

Вы обнаружите, что приложение начинает выполняться. Мы добавили задержку в 30 секунд между записями, чтобы избежать ограничений скорости, которые могут возникнуть в новых учетных записях Google Cloud, поэтому наберитесь терпения.

Пример текущего запуска показан ниже:

469ede91ba007c1f.png

После того как все три записи будут улучшены с помощью вызова Gemini, будет создан файл data/yoga_poses_with_description.json . Вы можете взглянуть на это.

Теперь мы готовы к нашему файлу данных, и следующим шагом будет понимание того, как заполнить им базу данных Firestore, а также генерировать встраивания.

5. Импортируйте данные в Firestore и создавайте векторные вложения.

У нас есть файл data/yoga_poses_with_description.json , и теперь нам нужно заполнить им базу данных Firestore и, что немаловажно, сгенерировать векторные внедрения для каждой из записей. Векторные встраивания будут полезны позже, когда нам придется выполнять поиск по их сходству с пользовательским запросом, предоставленным на естественном языке.

Шаги для этого будут следующими:

  1. Мы преобразуем список объектов JSON в список объектов. Каждый документ будет иметь два атрибута: content и metadata . Объект метаданных будет содержать весь объект JSON с такими атрибутами, как name , description , sanskrit_name и т. д. content будет текстовая строка, представляющая собой объединение нескольких полей.
  2. Когда у нас будет список документов, мы будем использовать класс 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 в облачной консоли.

504cabdb99a222a5.png

Нажмите на базу данных (по умолчанию), она должна показать коллекцию test-poses и несколько документов в этой коллекции. Каждый документ — это одна поза йоги.

9f37aa199c4b547a.png

Нажмите на любой из документов, чтобы изучить поля. В дополнение к полям, которые мы импортировали, вы также найдете поле embedding , которое представляет собой векторное поле, значение которого мы сгенерировали с помощью модели text-embedding-004 Vertex AI Embedding.

f0ed92124519beaf.png

Теперь, когда у нас есть записи, загруженные в базу данных Firestore с встраиваниями, мы можем перейти к следующему шагу и посмотреть, как выполнить поиск по сходству векторов в Firestore.

6. Импортируйте полные позы йоги в коллекцию базы данных Firestore.

Теперь мы создадим коллекцию poses , которая представляет собой полный список из 160 поз йоги, для которых мы создали файл импорта базы данных, который вы можете импортировать напрямую. Это сделано для экономии времени в лаборатории. Процесс создания базы данных, содержащей описание и внедрения, аналогичен тому, что мы видели в предыдущем разделе.

Импортируйте базу данных, выполнив действия, указанные ниже:

  1. Создайте корзину в своем проекте с помощью команды gsutil приведенной ниже. Замените переменную <PROJECT_ID> в приведенной ниже команде идентификатором своего проекта Google Cloud.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
  1. Теперь, когда корзина создана, нам нужно скопировать подготовленный нами экспорт базы данных в эту корзину, прежде чем мы сможем импортировать ее в базу данных Firebase. Используйте команду, указанную ниже:
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615  gs://<PROJECT_ID>-my-bucket

Теперь, когда у нас есть данные для импорта, мы можем перейти к последнему этапу импорта данных в созданную нами базу данных Firebase ( default ).

  1. Используйте команду 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 , как показано ниже:

561f3cb840de23d8.png

На этом создание коллекции 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 , а в Front-End 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 -адрес приложения, нажав кнопку предварительного просмотра веб -сайта, показанная ниже:

de297d4cee10e0bf.png

Он должен показать вам файл index.html , который обслуживается, как показано ниже:

20240a0e885ac17b.png

Предоставьте образец запроса (пример: Provide me some exercises for back pain relief ) и нажмите кнопку Search . Это должно получить некоторые рекомендации из базы данных. Вы также увидите кнопку Play Audio , которая будет генерировать аудио -поток на основе описания, который вы можете услышать напрямую.

789b4277dc40e2be.png

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 минут, поэтому, пожалуйста, будьте терпеливы.

3A6D86FD32E4A5E.PNG

После успешного развертывания вывод развертывания предоставит URL Service Service Cloud Run. Это будет формы:

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

Посетите этот публичный URL, и вы должны увидеть то же веб -приложение, которое развернуто и успешно работает.

84E1CBF29CBAEEDC.PNG

Вы также можете посетить Cloud Run из Cloud Console Google, и вы увидите список сервисов в Cloud Run. Служба yogaposes должна быть одной из служб (если не единственной), перечисленной там.

F2B34A8C9011BE4C.PNG

Вы можете просмотреть детали службы, такие как URL, конфигурации, журналы и многое другое, нажав на конкретное имя службы ( yogaposes в нашем случае).

FAAA5E0C02FE0423.png

Это завершает разработку и развертывание нашего йоги, представляет рекомендательное веб -приложение для Cloud Run.

10. Поздравляю

Поздравляю, вы успешно создали приложение, которое загружает набор данных в Firestore, генерирует встраивание и выполняет поиск сходства вектора на основе запроса пользователей.

Справочные документы