1. Introdução
Neste codelab, você vai criar um aplicativo que usa a pesquisa vetorial para recomendar posturas de ioga.
Durante o codelab, você vai usar uma abordagem gradual da seguinte forma:
- Use um conjunto de dados do Hugging Face com posturas de yoga (formato JSON).
- Melhore o conjunto de dados com uma descrição de campo adicional que usa o Gemini para gerar descrições de cada uma das poses.
- Carregue os dados de posturas de ioga como uma coleção de documentos na coleção do Firestore com incorporações geradas.
- Crie um índice composto no Firestore para permitir a pesquisa vetorial.
- Use a pesquisa vetorial em um aplicativo Node.js que reúne tudo, conforme mostrado abaixo:

O que você aprenderá
- Projetar, criar e implantar um aplicativo da Web que usa a pesquisa vetorial para recomendar posturas 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 para posturas de ioga
- Como carregar registros de um conjunto de dados avançado da Hugging Face no Firestore com embeddings de vetor
- Como usar a pesquisa vetorial do Firestore para pesquisar dados com base em uma consulta em linguagem natural
- Como usar a API Google Cloud Text-to-Speech 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, criado para desenvolvedores de todos os níveis (inclusive iniciantes), usa JavaScript e Node.js no aplicativo de exemplo. No entanto, não é necessário ter conhecimento de JavaScript e Node.js para entender os conceitos apresentados.
2. Antes de começar
Criar um projeto
- No console do Google Cloud, na página de seletor de projetos, selecione ou crie um projeto do Google Cloud.
- Verifique se o faturamento está ativado para seu projeto do Cloud. Saiba como verificar se o faturamento está ativado em um projeto .
- 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.

- Depois de se conectar ao Cloud Shell, verifique se sua conta já está autenticada e se o projeto está configurado com o ID do seu projeto usando o seguinte comando:
gcloud auth list
- Execute o comando a seguir no Cloud Shell para confirmar se o comando gcloud sabe sobre seu projeto.
gcloud config list project
- Se o projeto não estiver definido, use este comando:
gcloud config set project <YOUR_PROJECT_ID>
- Ative as APIs necessárias com o comando mostrado abaixo. Isso pode levar alguns minutos.
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, você vai ver uma mensagem semelhante à mostrada abaixo:
Operation "operations/..." finished successfully.
A alternativa ao comando gcloud é usar o console. Para isso, pesquise cada produto ou use este link.
Se alguma API for esquecida, você sempre 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 as definições de ambiente
A próxima etapa é clonar o repositório de amostra que vamos referenciar 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 Arquivo → Abrir pasta, conforme mostrado abaixo:

Selecione a pasta yoga-poses-recommender-nodejs. Ela vai abrir com os seguintes arquivos, conforme mostrado abaixo:

Agora, precisamos configurar as variáveis de ambiente que vamos usar. Clique no arquivo env-template. O conteúdo vai aparecer como 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 os mesmos para o projeto do Google Cloud e o banco de dados do Firestore, por exemplo, us-central1.
Para os fins deste 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 do arquivo env-template.
Acesse o menu principal na parte de cima à esquerda na Cloud Shell IDE e clique em 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 com o comando:
npm install
Ótimo! Agora está tudo pronto para passar para 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 usaremos 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ê ainda não tiver inicializado um banco de dados do Firestore 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. - Selecione o tipo de local como
Regione escolha o localus-central1para a região. - Para as regras de segurança, use
Test rules. - Crie o banco de dados.

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 posturas de ioga, que vamos usar no aplicativo.
Isso conclui a seção de configuração do banco de dados do Firestore.
4. Preparar o conjunto de dados de posturas de yoga
A 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 aprimorá-lo 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, é possível usar qualquer outro e seguir as mesmas técnicas demonstradas para melhorar o conjunto de dados.

Se acessarmos a seção Files and versions, podemos acessar o arquivo de dados JSON para todas as poses.

Fizemos o download do yoga_poses.json e enviamos o arquivo para você. Esse arquivo é chamado de 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 um representa uma postura de yoga. Temos um total de três 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"]
}
Agora é uma ótima oportunidade para apresentar o Gemini e como podemos usar o modelo padrão para gerar um campo description para ele.
No editor do Cloud Shell, acesse o arquivo generate-descriptions.js. O conteúdo desse 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 postura de ioga. Ele vai receber a descrição por uma chamada ao modelo do Gemini, em que vamos fornecer o comando necessário. O campo é adicionado ao arquivo JSON, e o novo arquivo é gravado em data/yoga_poses_with_descriptions.json.
Vamos analisar as etapas principais:
- Na função
main(), você vai encontrar a invocação da funçãoadd_descriptions_to_json, além do arquivo de entrada e do arquivo de saída esperados. - A função
add_descriptions_to_jsonfaz o seguinte para cada registro JSON, ou seja, informações da postagem de yoga: - Ele extrai
pose_name,sanskrit_name,expertise_levelepose_types. - Ele invoca a função
callGemini, que cria um comando e invoca a classe de modelo LangchainVertexAI para receber o texto da resposta. - Esse texto de resposta é adicionado ao objeto JSON.
- A lista atualizada de objetos JSON é gravada no arquivo de destino.
Vamos executar esse aplicativo. Abra uma nova janela de terminal (Ctrl+Shift+C) e execute o seguinte comando:
npm run generate-descriptions
Se for solicitada alguma autorização, forneça-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 que possam estar presentes em novas contas do Google Cloud. Portanto, tenha paciência.
Confira abaixo um exemplo de execução em andamento:

Quando todos os três registros forem aprimorados com a chamada do Gemini, um arquivo data/yoga_poses_with_description.json será gerado. Você pode dar uma olhada nisso.
Agora estamos prontos com nosso 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 de vetor
Temos o arquivo data/yoga_poses_with_description.json e agora precisamos preencher o banco de dados do Firestore com ele e, principalmente, gerar os embeddings de vetor para cada um dos registros. Os embeddings de vetores serão úteis mais tarde, quando precisarmos fazer uma pesquisa de similaridade neles com a consulta do usuário fornecida em linguagem natural.
Para isso, siga estas etapas:
- Vamos converter a lista de objetos JSON em uma lista de objetos. Cada documento terá dois atributos:
contentemetadata. O objeto de metadados vai conter todo o objeto JSON com atributos comoname,description,sanskrit_nameetc. Ocontentserá um texto de string que vai ser uma concatenação de alguns campos. - Depois de ter uma lista de documentos, vamos usar a classe Embeddings da Vertex AI para gerar o embedding do campo de conteúdo. Esse encadeamento será adicionado a cada registro de documento. Em seguida, usaremos a API Firestore para salvar essa lista de objetos de documento na coleção (estamos usando a variável
TEST_COLLECTION, que aponta paratest-poses).
O código de import-data.js é mostrado abaixo. Algumas partes foram truncadas para facilitar a leitura:
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 esse aplicativo. Abra uma nova janela de terminal (Ctrl+Shift+C) e execute o seguinte comando:
npm run import-data
Se tudo correr bem, você vai receber uma mensagem parecida com 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 os embeddings gerados, acesse a página do Firestore no console do Cloud.

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

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 geramos usando o modelo de embedding da Vertex AI text-embedding-004.

Agora que os registros foram enviados para o banco de dados do Firestore com os embeddings, podemos passar para a próxima etapa e ver como fazer a pesquisa de similaridade vetorial no Firestore.
6. Importar posturas 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. 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 para gerar o banco de dados que contém a descrição e as incorporações é o mesmo que vimos na seção anterior.
Para importar o banco de dados, siga estas etapas:
- Crie um bucket no seu projeto com o comando
gsutilabaixo. Substitua a variável<PROJECT_ID>no comando abaixo pelo ID do seu projeto do Google Cloud.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
- Agora que o bucket foi criado, precisamos copiar a exportação do 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 para o banco de dados do Firebase (default) que criamos.
- Use o comando gcloud abaixo:
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615
A importação leva alguns segundos. Depois que ela estiver pronta, você poderá validar seu banco de dados e a coleção do Firestore acessando https://console.cloud.google.com/firestore/databases, selecionando o banco de dados default e a coleção poses, conforme mostrado abaixo:

Isso conclui a criação da coleção do Firestore que vamos usar no aplicativo.
7. Realizar pesquisa de similaridade vetorial no Firestore
Para realizar uma 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 principal função a ser analisada é search, mostrada abaixo. Em geral, ele cria uma classe de embedding que será usada para gerar o 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 consultas, 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, uma mensagem de erro indicando que é necessário criar o índice primeiro será exibida com o comando para fazer isso.
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
O índice vai levar alguns minutos para ser concluído, já que há mais de 150 registros presentes no banco de dados. Depois de concluído, você pode conferir o índice com o comando mostrado abaixo:
gcloud firestore indexes composite list
Você vai encontrar o índice que acabou de criar na lista.
Teste o seguinte comando agora:
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']
Depois de fazer isso funcionar, entendemos como trabalhar com o banco de dados vetorial do Firestore para fazer upload de registros, gerar embeddings e realizar uma pesquisa de similaridade vetorial. Agora podemos criar um web app 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 de 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. Ele usa o comando transmitido do arquivo HTML index.html do front-end. Isso invoca o método de pesquisa, que faz a pesquisa de similaridade de vetores que vimos na seção anterior.
A resposta é enviada de volta ao index.html com a lista de recomendações. O index.html mostra as recomendações como cards diferentes.
Executar o aplicativo localmente
Abra uma nova janela de terminal (Ctrl+Shift+C) ou qualquer janela de terminal aberta e execute o seguinte comando:
npm run start
Confira um exemplo de execução abaixo:
...
Server listening on port 8080
Depois de tudo pronto, acesse o URL da página inicial do aplicativo clicando no botão "Visualização na Web" mostrado abaixo:

Ele vai mostrar o arquivo index.html veiculado, como mostrado abaixo:

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 gera um stream de áudio com base na descrição. É possível ouvir o áudio diretamente.

9. (Opcional) Como implantar no Google Cloud Run
A etapa final será implantar esse aplicativo no Google Cloud Run. O comando de implantação é mostrado abaixo. Antes de implantar, substitua os vários valores entre colchetes (<<>>) mostrados abaixo. Esses são os valores que você pode 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 seja necessário ativar as APIs do Cloud e confirmar várias permissões.
O processo de implantação leva de 5 a 7 minutos. Aguarde.

Depois de implantado, a saída da implantação vai fornecer o URL do serviço do Cloud Run. Ele vai estar no formato:
Service URL: https://yogaposes-<UNIQUEID>.us-central1.run.app
Acesse esse URL público para ver o mesmo aplicativo da Web implantado e em execução.

Também é possível acessar o Cloud Run no console do Google Cloud para ver a lista de serviços no Cloud Run. O serviço yogaposes precisa ser um dos serviços (se não o único) listados ali.

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

Isso conclui o desenvolvimento e a implantação do nosso aplicativo da Web de recomendação de posturas 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 faz uma pesquisa de similaridade vetorial com base na consulta dos usuários.