Codelab – Eine kontextbezogene Empfehlungs-App für Yoga-Posen mit Firestore, Vector Search, LangChain und Gemini erstellen (Node.js-Version)

1. Einführung

In diesem Codelab erstellen Sie eine Anwendung, die mithilfe der Vektorsuche Yoga-Posen empfiehlt.

In diesem Codelab gehen Sie schrittweise so vor:

  1. Verwenden Sie ein vorhandenes Hugging Face-Dataset mit Yoga-Posen (JSON-Format).
  2. Erweitern Sie den Datensatz mit einer zusätzlichen Feldbeschreibung, in der Gemini Beschreibungen für die einzelnen Posen generiert.
  3. Laden Sie die Daten zu Yoga-Posen als Sammlung von Dokumenten in eine Firestore-Sammlung mit generierten Einbettungen.
  4. Erstellen Sie einen zusammengesetzten Index in Firestore, um die Vektorsuche zu ermöglichen.
  5. Verwenden Sie die Vektorsuche in einer Node.js-Anwendung, die alles zusammenführt, wie unten dargestellt:

84e1cbf29cbaeedc.png

Aufgaben

  • Entwerfen, entwickeln und stellen Sie eine Webanwendung bereit, die Vektorsuche verwendet, um Yoga-Posen zu empfehlen.

Lerninhalte

  • Wie Sie mit Gemini Textinhalte generieren, und im Kontext dieses Codelabs Beschreibungen für Yoga-Posen
  • Datensätze aus einem erweiterten Dataset von Hugging Face zusammen mit Vektoreinbettungen in Firestore laden
  • Mit der Firestore-Vektorsuche nach Daten auf Grundlage einer Anfrage in natürlicher Sprache suchen
  • Audioinhalte mit der Google Cloud Text-to-Speech API generieren

Voraussetzungen

  • Chrome-Webbrowser
  • Ein Gmail-Konto
  • Ein Cloud-Projekt mit aktivierter Abrechnung

In diesem Codelab, das sich an Entwickler aller Erfahrungsstufen (auch Anfänger) richtet, werden in der Beispielanwendung JavaScript und Node.js verwendet. Kenntnisse in JavaScript und Node.js sind jedoch nicht erforderlich, um die vorgestellten Konzepte zu verstehen.

2. Hinweis

Projekt erstellen

  1. Wählen Sie in der Google Cloud Console auf der Seite zur Projektauswahl ein Google Cloud-Projekt aus oder erstellen Sie eines.
  2. Die Abrechnung für das Cloud-Projekt muss aktiviert sein. So prüfen Sie, ob die Abrechnung für ein Projekt aktiviert ist .
  3. Sie verwenden Cloud Shell, eine Befehlszeilenumgebung, die in Google Cloud ausgeführt wird und in der bq vorinstalliert ist. Klicken Sie oben in der Google Cloud Console auf „Cloud Shell aktivieren“.

Bild der Schaltfläche „Cloud Shell aktivieren“

  1. Sobald die Verbindung mit der Cloud Shell hergestellt ist, können Sie mit dem folgenden Befehl prüfen, ob Sie bereits authentifiziert sind und für das Projekt schon Ihre Projekt-ID eingestellt ist:
gcloud auth list
  1. Führen Sie den folgenden Befehl in Cloud Shell aus, um zu bestätigen, dass der gcloud-Befehl Ihr Projekt kennt.
gcloud config list project
  1. Wenn Ihr Projekt nicht festgelegt ist, verwenden Sie den folgenden Befehl, um es festzulegen:
gcloud config set project <YOUR_PROJECT_ID>
  1. Aktivieren Sie die erforderlichen APIs mit dem unten gezeigten Befehl. Das kann einige Minuten dauern.
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

Bei erfolgreicher Ausführung des Befehls sollte eine Meldung wie die unten gezeigte angezeigt werden:

Operation "operations/..." finished successfully.

Alternativ zum gcloud-Befehl können Sie in der Konsole nach den einzelnen Produkten suchen oder diesen Link verwenden.

Wenn eine API fehlt, können Sie sie jederzeit während der Implementierung aktivieren.

Informationen zu gcloud-Befehlen und deren Verwendung finden Sie in der Dokumentation.

Repository klonen und Umgebungseinstellungen einrichten

Als Nächstes klonen Sie das Beispiel-Repository, auf das wir im Rest des Codelabs verweisen. Wenn Sie sich in Cloud Shell befinden, geben Sie den folgenden Befehl in Ihrem Basisverzeichnis ein:

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

Klicken Sie zum Starten des Editors in der Symbolleiste des Cloud Shell-Fensters auf „Editor öffnen“. Klicken Sie links oben auf die Menüleiste und wählen Sie „Datei“ → „Ordner öffnen“ aus, wie unten dargestellt:

66221fd0d0e5202f.png

Wählen Sie den Ordner yoga-poses-recommender-nodejs aus. Er sollte sich mit den folgenden Dateien öffnen:

7dbe126ee112266d.png

Wir müssen jetzt die Umgebungsvariablen einrichten, die wir verwenden werden. Klicken Sie auf die Datei env-template. Der Inhalt sollte wie unten dargestellt aussehen:

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

Aktualisieren Sie die Werte für PROJECT_ID und LOCATION entsprechend den Angaben, die Sie beim Erstellen des Google Cloud-Projekts und der Firestore-Datenbankregion ausgewählt haben. Im Idealfall sollten die Werte von LOCATION für das Google Cloud-Projekt und die Firestore-Datenbank gleich sein, z.B. us-central1.

In diesem Codelab verwenden wir die folgenden Werte (mit Ausnahme von PROJECT_ID und LOCATION, die Sie entsprechend Ihrer Konfiguration festlegen müssen).

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

Speichern Sie diese Datei als .env im selben Ordner wie die Datei env-template.

Klicken Sie in Cloud Shell IDE links oben auf das Hauptmenü und dann auf Terminal → New Terminal.

Wechseln Sie mit dem folgenden Befehl zum Stammordner des geklonten Repositorys:

cd yoga-poses-recommender-nodejs

Installieren Sie die Node.js-Abhängigkeiten mit dem folgenden Befehl:

npm install

Super! Jetzt können wir mit der Einrichtung der Firestore-Datenbank fortfahren.

3. Firestore einrichten

Cloud Firestore ist eine vollständig verwaltete serverlose Dokumentendatenbank, die wir als Backend für unsere Anwendungsdaten verwenden. Daten in Cloud Firestore sind in Sammlungen von Dokumenten strukturiert.

Firestore-Datenbank initialisieren

Rufen Sie in der Cloud Console die Seite Firestore auf.

Wenn Sie noch keine Firestore-Datenbank im Projekt initialisiert haben, erstellen Sie die default-Datenbank, indem Sie auf Create Database klicken. Verwenden Sie beim Erstellen der Datenbank die folgenden Werte:

  • Firestore-Modus: Native.
  • Wählen Sie als Standorttyp Region und als Standort für die Region us-central1 aus.
  • Verwenden Sie für die Sicherheitsregeln Test rules.
  • Erstellen Sie die Datenbank.

61d0277510803c8d.png

Im nächsten Abschnitt bereiten wir die Erstellung einer Sammlung mit dem Namen poses in unserer Standard-Firestore-Datenbank vor. Diese Sammlung enthält Beispieldaten (Dokumente) oder Informationen zu Yoga-Posen, die wir dann in unserer Anwendung verwenden.

Damit ist der Abschnitt zum Einrichten der Firestore-Datenbank abgeschlossen.

4. Yoga-Posen-Dataset vorbereiten

Als Erstes müssen wir das Dataset „Yoga Poses“ vorbereiten, das wir für die Anwendung verwenden werden. Wir beginnen mit einem vorhandenen Hugging Face-Dataset und erweitern es dann mit zusätzlichen Informationen.

Hugging Face-Dataset für Yoga-Posen In diesem Codelab wird zwar eines der Datasets verwendet, Sie können aber auch ein beliebiges anderes Dataset verwenden und die gleichen Techniken anwenden, um das Dataset zu optimieren.

298cfae7f23e4bef.png

Wenn wir zum Bereich Files and versions gehen, können wir die JSON-Datendatei für alle Posen abrufen.

3fe6e55abdc032ec.png

Wir haben die Datei yoga_poses.json heruntergeladen und stellen sie Ihnen zur Verfügung. Diese Datei heißt yoga_poses_alldata.json und befindet sich im Ordner /data.

Rufen Sie die Datei data/yoga_poses.json im Cloud Shell Editor auf und sehen Sie sich die Liste der JSON-Objekte an. Jedes JSON-Objekt stellt eine Yoga-Pose dar. Wir haben insgesamt drei Datensätze. Ein Beispieldatensatz ist unten zu sehen:

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

Das ist eine gute Gelegenheit, Gemini vorzustellen und zu zeigen, wie wir das Standardmodell selbst verwenden können, um ein description-Feld dafür zu generieren.

Rufen Sie im Cloud Shell-Editor die Datei generate-descriptions.js auf. Der Inhalt dieser Datei wird unten angezeigt:

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

Mit dieser Anwendung wird jedem JSON-Datensatz für Yogaposen ein neues Feld description hinzugefügt. Die Beschreibung wird durch einen Aufruf des Gemini-Modells abgerufen, dem wir den erforderlichen Prompt zur Verfügung stellen. Das Feld wird der JSON-Datei hinzugefügt und die neue Datei wird in die Datei data/yoga_poses_with_descriptions.json geschrieben.

Sehen wir uns die wichtigsten Schritte an:

  1. In der Funktion main() wird die Funktion add_descriptions_to_json aufgerufen und die erwartete Eingabe- und Ausgabedatei angegeben.
  2. Die Funktion add_descriptions_to_json führt für jeden JSON-Datensatz (d.h. für jede Yoga-Beitragsinformation) Folgendes aus:
  3. Dabei werden pose_name, sanskrit_name, expertise_level und pose_types extrahiert.
  4. Dabei wird die Funktion callGemini aufgerufen, die einen Prompt erstellt und dann die LangchainVertexAI-Modellklasse aufruft, um den Antworttext abzurufen.
  5. Dieser Antworttext wird dann dem JSON-Objekt hinzugefügt.
  6. Die aktualisierte JSON-Liste von Objekten wird dann in die Zieldatei geschrieben.

Führen wir diese Anwendung aus. Öffnen Sie ein neues Terminalfenster (Strg + Umschalt + C) und geben Sie den folgenden Befehl ein:

npm run generate-descriptions

Wenn Sie zur Autorisierung aufgefordert werden, geben Sie die erforderlichen Informationen an.

Die Anwendung wird ausgeführt. Wir haben eine Verzögerung von 30 Sekunden zwischen den Datensätzen eingefügt, um Ratenbegrenzungskontingente zu vermeiden, die möglicherweise für neue Google Cloud-Konten gelten. Bitte haben Sie etwas Geduld.

Unten sehen Sie ein Beispiel für einen laufenden Testlauf:

469ede91ba007c1f.png

Sobald alle drei Datensätze mit dem Gemini-Aufruf optimiert wurden, wird die Datei data/yoga_poses_with_description.json generiert. Das können Sie sich ansehen.

Wir haben jetzt unsere Datendatei. Im nächsten Schritt geht es darum, wie wir eine Firestore-Datenbank damit füllen und Embeddings generieren.

5. Daten in Firestore importieren und Vektoreinbettungen generieren

Wir haben die Datei data/yoga_poses_with_description.json und müssen jetzt die Firestore-Datenbank damit füllen und vor allem die Vektoreinbettungen für jeden Datensatz generieren. Die Vektoreinbettungen sind später nützlich, wenn wir eine Ähnlichkeitssuche mit der in natürlicher Sprache angegebenen Nutzeranfrage durchführen müssen.

So gehen Sie vor:

  1. Wir konvertieren die Liste der JSON-Objekte in eine Liste von Objekten. Jedes Dokument hat zwei Attribute: content und metadata. Das Metadatenobjekt enthält das gesamte JSON-Objekt mit Attributen wie name, description und sanskrit_name. content ist ein String, der aus der Verkettung einiger Felder besteht.
  2. Sobald wir eine Liste von Dokumenten haben, verwenden wir die Vertex AI Embeddings-Klasse, um die Einbettung für das Feld „content“ zu generieren. Diese Einbettung wird jedem Dokumentdatensatz hinzugefügt. Anschließend wird die Firestore API verwendet, um diese Liste von Dokumentobjekten in der Sammlung zu speichern. Dazu wird die Variable TEST_COLLECTION verwendet, die auf test-poses verweist.

Der Code für import-data.js ist unten angegeben (Teile des Codes wurden aus Gründen der Übersichtlichkeit gekürzt):

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

Führen wir diese Anwendung aus. Öffnen Sie ein neues Terminalfenster (Strg + Umschalt + C) und geben Sie den folgenden Befehl ein:

npm run import-data

Wenn alles wie geplant ausgeführt wird, sehen Sie eine Meldung ähnlich der folgenden:

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.

Wenn Sie prüfen möchten, ob die Datensätze erfolgreich eingefügt und die Einbettungen generiert wurden, rufen Sie in der Cloud Console die Firestore-Seite auf.

504cabdb99a222a5.png

Klicken Sie auf die Standarddatenbank. Daraufhin sollten die Sammlung test-poses und mehrere Dokumente unter dieser Sammlung angezeigt werden. Jedes Dokument entspricht einer Yoga-Pose.

9f37aa199c4b547a.png

Klicken Sie auf eines der Dokumente, um die Felder zu untersuchen. Zusätzlich zu den importierten Feldern finden Sie auch das Feld embedding, ein Vektorfeld, dessen Wert wir mit dem Vertex AI-Einbettungsmodell text-embedding-004 generiert haben.

f0ed92124519beaf.png

Nachdem die Datensätze mit den Einbettungen in die Firestore-Datenbank hochgeladen wurden, können wir mit dem nächsten Schritt fortfahren und sehen, wie eine Vektorähnlichkeitssuche in Firestore durchgeführt wird.

6. Vollständige Yoga-Posen in die Firestore-Datenbank-Sammlung importieren

Wir erstellen jetzt die Sammlung poses, die eine vollständige Liste mit 160 Yoga-Posen enthält. Für diese haben wir eine Datenbankimportdatei generiert, die Sie direkt importieren können. Das soll Zeit im Lab sparen. Der Prozess zum Generieren der Datenbank mit der Beschreibung und den Einbettungen ist derselbe wie im vorherigen Abschnitt.

So importieren Sie die Datenbank:

  1. Erstellen Sie mit dem unten angegebenen Befehl gsutil einen Bucket in Ihrem Projekt. Ersetzen Sie die Variable <PROJECT_ID> im folgenden Befehl durch Ihre Google Cloud-Projekt-ID.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
  1. Nachdem der Bucket erstellt wurde, müssen wir den vorbereiteten Datenbankexport in diesen Bucket kopieren, bevor wir ihn in die Firebase-Datenbank importieren können. Verwenden Sie den folgenden Befehl:
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615  gs://<PROJECT_ID>-my-bucket

Nachdem wir nun die zu importierenden Daten haben, können wir mit dem letzten Schritt fortfahren: dem Import der Daten in die von uns erstellte Firebase-Datenbank (default).

  1. Verwenden Sie den unten angegebenen gcloud-Befehl:
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615

Der Import dauert einige Sekunden. Danach können Sie Ihre Firestore-Datenbank und die Sammlung unter https://console.cloud.google.com/firestore/databases validieren. Wählen Sie dazu die Datenbank default und die Sammlung poses aus, wie unten dargestellt:

561f3cb840de23d8.png

Damit ist die Firestore-Sammlung, die wir in unserer Anwendung verwenden, erstellt.

7. Suche nach Vektorähnlichkeiten in Firestore durchführen

Für die Suche nach Vektorähnlichkeiten wird die Anfrage des Nutzers verwendet. Ein Beispiel für diese Abfrage ist "Suggest me some exercises to relieve back pain".

Sehen Sie sich die Datei search-data.js an. Die wichtigste Funktion ist search, die unten dargestellt ist. Im Wesentlichen wird eine Einbettungsklasse erstellt, die zum Generieren der Einbettung für die Nutzeranfrage verwendet werden soll. Anschließend wird eine Verbindung zur Firestore-Datenbank und -Sammlung hergestellt. Anschließend wird die Methode „findNearest“ für die Sammlung aufgerufen, um eine Suche nach Vektorähnlichkeit durchzuführen.

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

Bevor Sie diesen Code mit einigen Beispielanfragen ausführen, müssen Sie zuerst einen zusammengesetzten Firestore-Index generieren, der für erfolgreiche Suchanfragen erforderlich ist. Wenn Sie die Anwendung ausführen, ohne den Index zu erstellen, wird eine Fehlermeldung angezeigt, die darauf hinweist, dass Sie den Index zuerst erstellen müssen. Außerdem wird der Befehl zum Erstellen des Index angezeigt.

Der gcloud-Befehl zum Erstellen des zusammengesetzten Index ist unten dargestellt:

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

Die Erstellung des Index dauert einige Minuten, da die Datenbank mehr als 150 Einträge enthält. Anschließend können Sie den Index mit dem unten gezeigten Befehl aufrufen:

gcloud firestore indexes composite list

Der gerade erstellte Index sollte in der Liste angezeigt werden.

Probieren Sie jetzt den folgenden Befehl aus:

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

Sie sollten einige Empfehlungen erhalten. Ein Beispiel für einen Lauf sehen Sie unten:

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

Wenn das funktioniert, haben wir verstanden, wie wir die Firestore-Vektordatenbank verwenden, um Datensätze hochzuladen, Einbettungen zu generieren und eine Vektorähnlichkeitssuche durchzuführen. Wir können jetzt eine Webanwendung erstellen, in die die Vektorsuche in ein Web-Frontend eingebunden wird.

8. Die Webanwendung

Die Python Flask-Webanwendung ist in der Datei app.js und die HTML-Datei für das Frontend in der Datei views/index.html. verfügbar.

Wir empfehlen, sich beide Dateien anzusehen. Beginnen Sie mit der Datei app.js, die den Handler /search enthält. Dieser übernimmt den Prompt, der von der Frontend-HTML-Datei index.html übergeben wurde. Dadurch wird die Suchmethode aufgerufen, die die Vektorähnlichkeitssuche durchführt, die wir uns im vorherigen Abschnitt angesehen haben.

Die Antwort wird dann mit der Liste der Empfehlungen an das index.html zurückgesendet. Im index.html werden die Empfehlungen dann als verschiedene Karten angezeigt.

Anwendung lokal ausführen

Öffnen Sie ein neues Terminalfenster (Strg + Umschalt + C) oder ein beliebiges vorhandenes Terminalfenster und geben Sie den folgenden Befehl ein:

npm run start

Unten sehen Sie ein Beispiel für die Ausführung:

...
Server listening on port 8080

Sobald die Anwendung ausgeführt wird, können Sie die Start-URL der Anwendung aufrufen, indem Sie auf die unten gezeigte Schaltfläche „Webvorschau“ klicken:

de297d4cee10e0bf.png

Die index.html-Datei sollte wie unten dargestellt angezeigt werden:

20240a0e885ac17b.png

Geben Sie eine Beispielanfrage an (z. B. Provide me some exercises for back pain relief) und klicken Sie auf die Schaltfläche Search. Dadurch sollten einige Empfehlungen aus der Datenbank abgerufen werden. Außerdem wird die Schaltfläche Play Audio angezeigt, über die ein Audio-Stream auf Grundlage der Beschreibung generiert wird, den Sie sich direkt anhören können.

789b4277dc40e2be.png

9. (Optional) In Google Cloud Run bereitstellen

Im letzten Schritt stellen wir diese Anwendung in Google Cloud Run bereit. Das Bereitstellungs-Command wird unten angezeigt. Ersetzen Sie vor der Bereitstellung die verschiedenen Werte in Klammern (<<>>), die unten aufgeführt sind. Diese Werte können Sie aus der Datei .env abrufen.

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

Führen Sie den oben genannten Befehl im Stammordner der Anwendung aus. Möglicherweise werden Sie auch aufgefordert, Google Cloud APIs zu aktivieren und verschiedene Berechtigungen zu bestätigen.

Das Deployment dauert etwa 5 bis 7 Minuten.

3a6d86fd32e4a5e.png

Nach erfolgreicher Bereitstellung wird in der Bereitstellungsausgabe die Cloud Run-Dienst-URL angegeben. Sie hat das folgende Format:

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

Wenn Sie diese öffentliche URL aufrufen, sollten Sie dieselbe Webanwendung sehen, die bereitgestellt wurde und erfolgreich ausgeführt wird.

84e1cbf29cbaeedc.png

Sie können auch in der Google Cloud Console Cloud Run aufrufen. Dort wird die Liste der Dienste in Cloud Run angezeigt. Der Dienst yogaposes sollte einer der (oder der einzige) dort aufgeführten Dienste sein.

f2b34a8c9011be4c.png

Sie können die Details des Dienstes wie URL, Konfigurationen und Logs aufrufen, indem Sie auf den jeweiligen Dienstnamen klicken (in unserem Fall yogaposes).

faaa5e0c02fe0423.png

Damit ist die Entwicklung und Bereitstellung unserer Webanwendung zur Empfehlung von Yoga-Posen in Cloud Run abgeschlossen.

10. Glückwunsch

Sie haben eine Anwendung erstellt, die ein Dataset in Firestore hochlädt, die Einbettungen generiert und eine Suche nach Vektorähnlichkeit basierend auf der Nutzeranfrage durchführt.

Referenzdokumente