Codelab: criar um app de recomendação de posições de ioga contextual com o Firestore, a pesquisa de vetor, o Langchain e o Gemini (versão Node.js)

1. Introdução

Neste codelab, você vai criar um aplicativo que usa a pesquisa de vetor para recomendar poses de ioga.

No codelab, você vai usar uma abordagem detalhada da seguinte forma:

  1. Use um conjunto de dados do Hugging Face de poses de ioga (formato JSON).
  2. Melhore o conjunto de dados com uma descrição de campo adicional que usa o Gemini para gerar descrições de cada pose.
  3. Carregue os dados das poses de ioga como uma coleção de documentos no Firestore com embeddings gerados.
  4. Crie um índice composto no Firestore para permitir a pesquisa vetorial.
  5. Use a pesquisa vetorial em um aplicativo Node.js que reúne tudo, conforme mostrado abaixo:

84e1cbf29cbaeedc.png

O que você aprenderá

  • Projete, crie e implante um aplicativo da Web que usa a Pesquisa de vetor para recomendar posições de ioga.

O que você vai aprender

  • Como usar o Gemini para gerar conteúdo de texto e, no contexto deste codelab, gerar descrições de poses de ioga
  • Como carregar registros de um conjunto de dados aprimorado do Hugging Face no Firestore com incorporações de vetor
  • Como usar a Pesquisa Vetorial do Firestore para pesquisar dados com base em uma consulta em linguagem natural
  • Como usar a API Text-to-Speech do Google Cloud para gerar conteúdo de áudio

O que é necessário

  • Navegador da Web Google Chrome
  • Uma conta do Gmail
  • Um projeto do Cloud com faturamento ativado

Este codelab, desenvolvido para desenvolvedores de todos os níveis (inclusive iniciantes), usa JavaScript e Node.js no aplicativo de exemplo. No entanto, o conhecimento de JavaScript e Node.js não é necessário para entender os conceitos apresentados.

2. Antes de começar

Criar um projeto

  1. No console do Google Cloud, na página de seletor de projetos, selecione ou crie um projeto do Google Cloud.
  2. Verifique se o faturamento está ativado para seu projeto do Cloud. Saiba como verificar se o faturamento está ativado em um projeto .
  3. Você vai usar o Cloud Shell, um ambiente de linha de comando executado no Google Cloud que vem pré-carregado com bq. Clique em "Ativar o Cloud Shell" na parte de cima do console do Google Cloud.

Imagem do botão "Ativar o Cloud Shell"

  1. Depois de se conectar ao Cloud Shell, verifique se você já está autenticado e se o projeto está definido como seu ID usando o seguinte comando:
gcloud auth list
  1. Execute o comando a seguir no Cloud Shell para confirmar se o comando gcloud sabe sobre seu projeto.
gcloud config list project
  1. Se o projeto não estiver definido, use este comando:
gcloud config set project <YOUR_PROJECT_ID>
  1. Ative as APIs necessárias usando o comando mostrado abaixo. Isso pode levar alguns minutos. Tenha paciência.
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

Após a execução do comando, uma mensagem semelhante à mostrada abaixo vai aparecer:

Operation "operations/..." finished successfully.

A alternativa ao comando gcloud é pelo console, pesquisando cada produto ou usando este link.

Se alguma API for perdida, você poderá ativá-la durante a implementação.

Consulte a documentação para ver o uso e os comandos gcloud.

Clonar o repositório e configurar o ambiente

A próxima etapa é clonar o repositório de exemplo que vamos usar como referência no restante do codelab. Supondo que você esteja no Cloud Shell, execute o seguinte comando no diretório principal:

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

Para iniciar o editor, clique em "Abrir editor" na barra de ferramentas da janela do Cloud Shell. Clique na barra de menu no canto superior esquerdo e selecione "File" → "Open Folder", conforme mostrado abaixo:

66221fd0d0e5202f.png

Selecione a pasta yoga-poses-recommender-nodejs. Ela será aberta com os seguintes arquivos, conforme mostrado abaixo:

7dbe126ee112266d.png

Agora precisamos configurar as variáveis de ambiente que vamos usar. Clique no arquivo env-template e o conteúdo vai aparecer conforme mostrado abaixo:

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

Atualize os valores de PROJECT_ID e LOCATION de acordo com o que você selecionou ao criar o projeto do Google Cloud e a região do banco de dados do Firestore. O ideal é que os valores de LOCATION sejam iguais para o projeto do Google Cloud e o banco de dados do Firestore, por exemplo, us-central1.

Para este codelab, vamos usar os seguintes valores, exceto PROJECT_ID e LOCATION, que você precisa definir de acordo com sua configuração.

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

Salve este arquivo como .env na mesma pasta que o arquivo env-template.

Acesse o menu principal no canto superior esquerdo do Cloud Shell IDE e selecione Terminal → New Terminal.

Navegue até a pasta raiz do repositório clonado usando o seguinte comando:

cd yoga-poses-recommender-nodejs

Instale as dependências do Node.js usando o comando:

npm install

Ótimo! Agora está tudo pronto para prosseguir com a tarefa de configurar o banco de dados do Firestore.

3. Configurar o Firestore

O Cloud Firestore é um banco de dados de documentos sem servidor totalmente gerenciado que vamos usar como back-end para os dados do aplicativo. Os dados no Cloud Firestore são estruturados em coleções de documentos.

Inicialização do banco de dados do Firestore

Acesse a página do Firestore no Console do Cloud.

Se você não tiver inicializado um banco de dados do Firestore antes no projeto, crie o banco de dados default clicando em Create Database. Durante a criação do banco de dados, use os seguintes valores:

  • Modo do Firestore: Native.
  • Localização: use as configurações de localização padrão.
  • Para as regras de segurança, use Test rules.
  • Crie o banco de dados.

504cabdb99a222a5.png

Na próxima seção, vamos preparar o terreno para criar uma coleção chamada poses no nosso banco de dados padrão do Firestore. Essa coleção vai conter dados de amostra (documentos) ou informações sobre as posições de ioga, que serão usadas no nosso aplicativo.

Isso conclui a seção de configuração do banco de dados do Firestore.

4. Preparar o conjunto de dados de poses de ioga

Nossa primeira tarefa é preparar o conjunto de dados de posturas de ioga que vamos usar no aplicativo. Vamos começar com um conjunto de dados do Hugging Face e depois aprimorar com mais informações.

Confira o conjunto de dados do Hugging Face para posturas de ioga. Embora este codelab use um dos conjuntos de dados, você pode usar qualquer outro conjunto de dados e seguir as mesmas técnicas demonstradas para aprimorar o conjunto de dados.

298cfae7f23e4bef.png

Se formos à seção Files and versions, vamos conseguir o arquivo de dados JSON de todas as poses.

3fe6e55abdc032ec.png

Fizemos o download do arquivo yoga_poses.json e o enviamos para você. Esse arquivo é chamado yoga_poses_alldata.json e está na pasta /data.

Acesse o arquivo data/yoga_poses.json no editor do Cloud Shell e confira a lista de objetos JSON, em que cada objeto JSON representa uma postura de ioga. Temos um total de 3 registros, e um exemplo é mostrado abaixo:

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

Esta é uma ótima oportunidade para apresentar o Gemini e como podemos usar o modelo padrão para gerar um campo description.

No editor do Cloud Shell, acesse o arquivo generate-descriptions.js. O conteúdo do arquivo é mostrado abaixo:

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

Esse aplicativo vai adicionar um novo campo description a cada registro JSON de pose de ioga. Ela vai receber a descrição por uma chamada ao modelo Gemini, onde vamos fornecer o comando necessário. O campo é adicionado ao arquivo JSON, e o novo arquivo é gravado no arquivo data/yoga_poses_with_descriptions.json.

Vamos conferir as principais etapas:

  1. Na função main(), ela invoca a função add_descriptions_to_json e fornece o arquivo de entrada e o arquivo de saída esperados.
  2. A função add_descriptions_to_json faz o seguinte para cada registro JSON, ou seja, informações sobre a postagem de ioga:
  3. Ele extrai pose_name, sanskrit_name, expertise_level e pose_types.
  4. Ele invoca a função callGemini, que constrói um comando e, em seguida, invoca a classe de modelo LangchainVertexAI para receber o texto de resposta.
  5. Esse texto de resposta é adicionado ao objeto JSON.
  6. A lista JSON atualizada de objetos é gravada no arquivo de destino.

Vamos executar este aplicativo. Inicie uma nova janela de terminal (Ctrl+Shift+C) e digite o seguinte comando:

npm run generate-descriptions

Se você receber uma solicitação de autorização, envie-a.

O aplicativo vai começar a ser executado. Adicionamos um atraso de 30 segundos entre os registros para evitar cotas de limite de taxa nas novas contas do Google Cloud. Tenha paciência.

Confira abaixo um exemplo de execução em andamento:

469ede91ba007c1f.png

Depois que os três registros forem aprimorados com a chamada do Gemini, um arquivo data/yoga_poses_with_description.json será gerado. Confira.

Agora que temos o arquivo de dados, a próxima etapa é entender como preencher um banco de dados do Firestore com ele, além da geração de incorporações.

5. Importar dados para o Firestore e gerar embeddings vetoriais

Temos o arquivo data/yoga_poses_with_description.json e agora precisamos preencher o banco de dados do Firestore com ele e, principalmente, gerar as inclusões de vetor para cada um dos registros. As incorporações vetoriais serão úteis mais tarde, quando precisarmos fazer uma pesquisa de similaridade com elas usando a consulta do usuário fornecida em linguagem natural.

Para isso, siga estas etapas:

  1. Vamos converter a lista de objetos JSON em uma lista de objetos. Cada documento terá dois atributos: content e metadata. O objeto de metadados vai conter todo o objeto JSON com atributos como name, description, sanskrit_name etc. O content será um texto de string que será uma concatenação de alguns campos.
  2. Depois de ter uma lista de documentos, vamos usar a classe Embeddings da Vertex AI para gerar o embedding do campo de conteúdo. Essa incorporação será adicionada a cada registro de documento, e depois vamos usar a API Firestore para salvar essa lista de objetos de documento na coleção. Estamos usando a variável TEST_COLLECTION que aponta para test-poses.

Confira abaixo o código de import-data.js (partes do código foram truncadas para encurtar):

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

Vamos executar este aplicativo. Inicie uma nova janela de terminal (Ctrl+Shift+C) e digite o seguinte comando:

npm run import-data

Se tudo correr bem, você vai receber uma mensagem semelhante a esta:

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.

Para verificar se os registros foram inseridos e as incorporações foram geradas, acesse a página do Firestore no Console do Cloud.

504cabdb99a222a5.png

Clique no banco de dados (padrão), que vai mostrar a coleção test-poses e vários documentos nessa coleção. Cada documento é uma pose de ioga.

9f37aa199c4b547a.png

Clique em qualquer um dos documentos para investigar os campos. Além dos campos importados, você também vai encontrar o campo embedding, que é um campo de vetor, cujo valor foi gerado pelo modelo de embedding text-embedding-004 da Vertex AI.

f0ed92124519beaf.png

Agora que temos os registros enviados para o banco de dados do Firestore com os embeddings, podemos passar para a próxima etapa e conferir como fazer a pesquisa de similaridade vetorial no Firestore.

6. Importar poses de yoga completas para a coleção do banco de dados do Firestore

Agora vamos criar a coleção poses, que é uma lista completa de 160 posturas de ioga, para as quais geramos um arquivo de importação de banco de dados que você pode importar diretamente. Isso é feito para economizar tempo no laboratório. O processo de geração do banco de dados que contém a descrição e as embeddings é o mesmo que vimos na seção anterior.

Siga as etapas abaixo para importar o banco de dados:

  1. Crie um bucket no seu projeto com o comando gsutil abaixo. Substitua a variável <PROJECT_ID> no comando abaixo pelo ID do projeto do Google Cloud.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
  1. Agora que o bucket foi criado, precisamos copiar a exportação de banco de dados que preparamos para ele antes de importar para o banco de dados do Firebase. Use o comando abaixo:
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615  gs://<PROJECT_ID>-my-bucket

Agora que temos os dados para importar, podemos passar para a etapa final de importação dos dados para o banco de dados do Firebase (default) que criamos.

  1. Use o comando gcloud abaixo:
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615

A importação vai levar alguns segundos. Quando estiver pronta, você poderá validar seu banco de dados do Firestore e a coleção acessando https://console.cloud.google.com/firestore/databases, selecionando o banco de dados default e a coleção poses, conforme mostrado abaixo:

561f3cb840de23d8.png

Isso conclui a criação da coleção do Firestore que vamos usar no nosso app.

7. Realizar pesquisa de similaridade vetorial no Firestore

Para realizar a pesquisa de similaridade vetorial, vamos usar a consulta do usuário. Um exemplo dessa consulta pode ser "Suggest me some exercises to relieve back pain".

Confira o arquivo search-data.js. A função principal a ser analisada é a search, mostrada abaixo. De forma geral, ele cria uma classe de embedding que será usada para gerar a embedding da consulta do usuário. Em seguida, ele estabelece uma conexão com o banco de dados e a coleção do Firestore. Em seguida, na coleção, ele invoca o método findNearest, que faz uma pesquisa de similaridade vetorial.

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

Antes de executar isso com alguns exemplos de consulta, gere um índice composto do Firestore, que é necessário para que as consultas de pesquisa sejam bem-sucedidas. Se você executar o aplicativo sem criar o índice, um erro indicando que você precisa criar o índice primeiro será exibido com o comando para criar o índice primeiro.

O comando gcloud para criar o índice composto é mostrado abaixo:

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

A indexação vai levar alguns minutos, porque há mais de 150 registros no banco de dados. Quando ele for concluído, você poderá conferir o índice usando o comando mostrado abaixo:

gcloud firestore indexes composite list

Você vai encontrar o índice que acabou de criar na lista.

Teste o seguinte comando:

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

Você vai receber algumas recomendações. Confira um exemplo de execução abaixo:

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

Agora que você já sabe como usar o Banco de Dados Vetorial do Firestore para fazer upload de registros, gerar embeddings e realizar uma pesquisa de similaridade de vetores, vamos continuar. Agora podemos criar um aplicativo da Web que vai integrar a pesquisa vetorial a um front-end da Web.

8. O aplicativo da Web

O aplicativo da Web Python Flask está disponível no arquivo app.js, e o arquivo HTML do front-end está presente no views/index.html..

Recomendamos que você analise os dois arquivos. Comece com o arquivo app.js que contém o gerenciador /search, que recebe a solicitação transmitida do arquivo HTML index.html do front-end. Isso invoca o método de pesquisa, que faz a pesquisa de similaridade vetorial que analisamos na seção anterior.

A resposta é enviada de volta para o index.html com a lista de recomendações. O index.html mostra as recomendações como cards diferentes.

Executar o aplicativo localmente

Inicie uma nova janela de terminal (Ctrl+Shift+C) ou qualquer janela de terminal existente e execute o seguinte comando:

npm run start

Confira abaixo um exemplo de execução:

...
Server listening on port 8080

Quando estiver tudo pronto, acesse o URL da página inicial do aplicativo clicando no botão "Visualização da Web" mostrado abaixo:

de297d4cee10e0bf.png

O arquivo index.html vai ser mostrado como mostrado abaixo:

20240a0e885ac17b.png

Forneça uma consulta de exemplo (por exemplo, Provide me some exercises for back pain relief) e clique no botão Search. Isso vai recuperar algumas recomendações do banco de dados. Você também vai encontrar um botão Play Audio, que vai gerar um stream de áudio com base na descrição, que você pode ouvir diretamente.

789b4277dc40e2be.png

9. (Opcional) Implantação no Google Cloud Run

A etapa final será implantar este aplicativo no Google Cloud Run. O comando de implantação é mostrado abaixo. Antes de implantar, substitua os valores mostrados em negrito abaixo. Esses são valores que você poderá recuperar do arquivo .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>>

Execute o comando acima na pasta raiz do aplicativo. Talvez você também precise ativar as APIs do Google Cloud e confirmar várias permissões.

O processo de implantação leva de 5 a 7 minutos para ser concluído. Tenha paciência.

3a6d86fd32e4a5e.png

Depois de implantada, a saída da implantação vai fornecer o URL do serviço do Cloud Run. Ele vai estar neste formato:

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

Acesse esse URL público e o mesmo aplicativo da Web implantado e em execução vai aparecer.

84e1cbf29cbaeedc.png

Você também pode acessar o Cloud Run no console do Google Cloud e conferir a lista de serviços no Cloud Run. O serviço yogaposes precisa ser um dos serviços (ou o único) listados.

f2b34a8c9011be4c.png

Para conferir os detalhes do serviço, como URL, configurações, registros e muito mais, clique no nome do serviço específico (yogaposes no nosso caso).

faaa5e0c02fe0423.png

Isso conclui o desenvolvimento e a implantação do nosso aplicativo da Web de recomendação de poses de ioga no Cloud Run.

10. Parabéns

Parabéns! Você criou um aplicativo que faz upload de um conjunto de dados para o Firestore, gera os embeddings e realiza uma pesquisa de similaridade vetorial com base na consulta do usuário.

Documentos de referência