Codelab - Firestore, Vector Search, Langchain ve Gemini ile bağlama dayalı yoga pozu önerici uygulaması oluşturma (Node.js sürümü)

1. Giriş

Bu codelab'de, yoga pozları önermek için Vector Search'ü kullanan bir uygulama oluşturacaksınız.

Bu codelab'de aşağıdaki gibi adım adım bir yaklaşım kullanacaksınız:

  1. Yoga pozlarının yer aldığı mevcut bir Hugging Face veri kümesini (JSON biçimi) kullanın.
  2. Gemini'ı kullanarak her poz için açıklama oluşturarak veri kümesini ek bir alan açıklamasıyla zenginleştirin.
  3. Yoga pozları verilerini, oluşturulan yerleştirmelerle birlikte Firestore koleksiyonunda Doküman koleksiyonu olarak yükleyin.
  4. Vektör aramasına izin vermek için Firestore'da birleşik dizin oluşturun.
  5. Aşağıda gösterildiği gibi her şeyi bir araya getiren bir Node.js uygulamasında Vector Search'ü kullanın:

84e1cbf29cbaeedc.png

Yapacaklarınız

  • Yoga pozları önermek için vektör araması kullanan bir web uygulaması tasarlama, oluşturma ve dağıtma

Neler öğreneceksiniz?

  • Gemini'ı kullanarak metin içeriği oluşturma ve bu codelab bağlamında yoga pozları için açıklamalar oluşturma
  • Hugging Face'teki gelişmiş bir veri kümesinden kayıtları vektör yerleştirmeleriyle birlikte Firestore'a yükleme
  • Doğal dil sorgusuna dayalı olarak veri aramak için Firestore Vector Search'ü kullanma
  • Ses içeriği oluşturmak için Google Cloud Text-to-Speech API'yi kullanma

Gerekenler

  • Chrome web tarayıcısı
  • Gmail hesabı
  • Faturalandırmanın etkin olduğu bir Cloud projesi

Her seviyeden geliştirici (yeni başlayanlar dahil) için tasarlanan bu codelab'de, örnek uygulamada JavaScript ve Node.js kullanılır. Ancak sunulan kavramları anlamak için JavaScript ve Node.js bilgisi gerekmez.

2. Başlamadan önce

Proje oluşturma

  1. Google Cloud Console'daki proje seçici sayfasında bir Google Cloud projesi seçin veya oluşturun.
  2. Cloud projeniz için faturalandırmanın etkinleştirildiğinden emin olun. Bir projede faturalandırmanın etkin olup olmadığını kontrol etmeyi öğrenin .
  3. bq'nun önceden yüklendiği, Google Cloud'da çalışan bir komut satırı ortamı olan Cloud Shell'i kullanacaksınız. Google Cloud Console'un üst kısmından Cloud Shell'i etkinleştir'i tıklayın.

Cloud Shell'i etkinleştir düğmesinin resmi

  1. Cloud Shell'e bağlandıktan sonra aşağıdaki komutu kullanarak kimliğinizin doğrulandığını ve projenin proje kimliğinize ayarlandığını kontrol edin:
gcloud auth list
  1. gcloud komutunun projeniz hakkında bilgi sahibi olduğunu onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın.
gcloud config list project
  1. Projeniz ayarlanmamışsa ayarlamak için aşağıdaki komutu kullanın:
gcloud config set project <YOUR_PROJECT_ID>
  1. Aşağıdaki komutu kullanarak gerekli API'leri etkinleştirin. Bu işlem birkaç dakika sürebilir. Lütfen bekleyin.
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

Komut başarıyla yürütüldüğünde aşağıda gösterilene benzer bir mesaj görürsünüz:

Operation "operations/..." finished successfully.

Gcloud komutuna alternatif olarak, her ürünü arayarak veya bu bağlantıyı kullanarak konsolu kullanabilirsiniz.

Herhangi bir API atlanırsa uygulama sırasında istediğiniz zaman etkinleştirebilirsiniz.

gcloud komutları ve kullanımı için belgelere bakın.

Depoyu klonlama ve ortam ayarlarını yapma

Sonraki adım, codelab'in geri kalanında referans vereceğimiz örnek depoyu klonlamaktır. Cloud Shell'de olduğunuzu varsayarak ana dizininizden aşağıdaki komutu verin:

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

Düzenleyiciyi başlatmak için Cloud Shell penceresinin araç çubuğunda Open Editor'ı (Düzenleyiciyi Aç) tıklayın. Sol üst köşedeki menü çubuğunu tıklayın ve Dosya → Klasörü Aç'ı seçin.

66221fd0d0e5202f.png

yoga-poses-recommender-nodejs klasörünü seçtiğinizde klasörün aşağıdaki dosyalarla birlikte açıldığını görürsünüz:

7dbe126ee112266d.png

Şimdi kullanacağımız ortam değişkenlerini ayarlamamız gerekiyor. env-template dosyasını tıkladığınızda içeriği aşağıdaki gibi görürsünüz:

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

Lütfen Google Cloud projesi ve Firestore veritabanı bölgesi oluştururken seçtiğiniz değerlere göre PROJECT_ID ve LOCATION değerlerini güncelleyin. İdeal olarak, LOCATION değerlerinin Google Cloud projesi ve Firestore veritabanı için aynı olmasını isteriz. Örneğin, us-central1.

Bu codelab'in amacı doğrultusunda, aşağıdaki değerleri kullanacağız (PROJECT_ID ve LOCATION hariç. Bu değerleri yapılandırmanıza göre ayarlamanız gerekir).

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

Lütfen bu dosyayı .env olarak env-template dosyasıyla aynı klasöre kaydedin.

Cloud Shell IDE'de sol üstteki ana menüye gidin ve Terminal → New Terminal simgesini tıklayın.

Aşağıdaki komutla klonladığınız deponun kök klasörüne gidin:

cd yoga-poses-recommender-nodejs

Node.js bağımlılıklarını şu komutla yükleyin:

npm install

Harika! Artık Firestore veritabanını ayarlama görevine geçebiliriz.

3. Firestore'u ayarlama

Cloud Firestore, uygulama verilerimiz için arka uç olarak kullanacağımız, tümüyle yönetilen bir sunucusuz belge veritabanıdır. Cloud Firestore'daki veriler, doküman koleksiyonları halinde yapılandırılır.

Firestore veritabanı başlatma

Cloud Console'da Firestore sayfasını ziyaret edin.

Projede daha önce bir Firestore veritabanı başlatmadıysanız Create Database seçeneğini tıklayarak default veritabanını oluşturun. Veritabanını oluştururken aşağıdaki değerleri kullanın:

  • Firestore modu: Native.
  • Konum türü olarak Region'ı ve bölge için us-central1 konumunu seçin.
  • Güvenlik kuralları için Test rules seçeneğini kullanın.
  • Veritabanını oluşturun.

61d0277510803c8d.png

Sonraki bölümde, varsayılan Firestore veritabanımızda poses adlı bir koleksiyon oluşturmanın temelini atacağız. Bu koleksiyonda, uygulamamızda kullanacağımız örnek veriler (dokümanlar) veya yoga pozları bilgileri yer alır.

Bu işlemle Firestore veritabanı kurulumu bölümü tamamlanır.

4. Yoga pozları veri kümesini hazırlama

İlk görevimiz, uygulamada kullanacağımız Yoga Pozları veri kümesini hazırlamaktır. Mevcut bir Hugging Face veri kümesiyle başlayıp ek bilgilerle geliştireceğiz.

Hugging Face Dataset for Yoga Poses'i (Yoga Pozları İçin Hugging Face Veri Kümesi) inceleyin. Bu codelab'de veri kümelerinden biri kullanılsa da aslında diğer veri kümelerini kullanabilir ve veri kümesini geliştirmek için gösterilen teknikleri uygulayabilirsiniz.

298cfae7f23e4bef.png

Files and versions bölümüne gidersek tüm pozlar için JSON veri dosyasını alabiliriz.

3fe6e55abdc032ec.png

yoga_poses.json dosyasını indirip size gönderdik. Bu dosya yoga_poses_alldata.json olarak adlandırılmış ve /data klasöründe bulunuyor.

Cloud Shell Düzenleyici'de data/yoga_poses.json dosyasına gidin ve her bir JSON nesnesinin bir yoga pozunu temsil ettiği JSON nesneleri listesine göz atın. Toplam 3 kaydımız var ve örnek bir kayıt aşağıda gösteriliyor:

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

Şimdi Gemini'ı ve varsayılan modeli kullanarak nasıl description alanı oluşturabileceğimizi tanıtmak için harika bir fırsat.

Cloud Shell Düzenleyici'de generate-descriptions.js dosyasına gidin. Bu dosyanın içeriği aşağıda gösterilmektedir:

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

Bu uygulama, her Yoga pozu JSON kaydına yeni bir description alanı ekler. Açıklama, Gemini modeline yapılan bir çağrı aracılığıyla alınır. Bu çağrıda, Gemini modeline gerekli istem sağlanır. Alan, JSON dosyasına eklenir ve yeni dosya data/yoga_poses_with_descriptions.json dosyasına yazılır.

Başlıca adımlara göz atalım:

  1. main() işlevinde, add_descriptions_to_json işlevini çağırdığını ve beklenen giriş dosyasını ve çıkış dosyasını sağladığını görürsünüz.
  2. add_descriptions_to_json işlevi, her JSON kaydı (ör. Yoga gönderisi bilgileri) için aşağıdakileri yapar:
  3. pose_name, sanskrit_name, expertise_level ve pose_types değerlerini ayıklar.
  4. İstem oluşturan callGemini işlevini çağırır ve ardından yanıt metnini almak için LangchainVertexAI model sınıfını çağırır.
  5. Bu yanıt metni daha sonra JSON nesnesine eklenir.
  6. Güncellenen nesne JSON listesi daha sonra hedef dosyaya yazılır.

Bu uygulamayı çalıştırmamıza izin verin. Yeni bir terminal penceresi (Ctrl+Üst Karakter+C) başlatın ve aşağıdaki komutu verin:

npm run generate-descriptions

Yetkilendirme istenirse lütfen yetkilendirme işlemini yapın.

Uygulamanın yürütülmeye başladığını görürsünüz. Yeni Google Cloud hesaplarında olabilecek hız sınırı kotalarını önlemek için kayıtlar arasına 30 saniyelik bir gecikme ekledik. Lütfen sabırlı olun.

Devam eden örnek bir çalıştırma aşağıda gösterilmektedir:

469ede91ba007c1f.png

3 kaydın tamamı Gemini çağrısıyla geliştirildikten sonra data/yoga_poses_with_description.json dosyası oluşturulur. Bu makaleye göz atabilirsiniz.

Veri dosyamız hazır. Bir sonraki adımda, yerleştirme oluşturmanın yanı sıra Firestore veritabanını bu dosya ile nasıl dolduracağımızı öğreneceğiz.

5. Firestore'a veri aktarma ve vektör yerleştirmeleri oluşturma

data/yoga_poses_with_description.json dosyamız var. Şimdi Firestore veritabanını bu dosya ile doldurmamız ve en önemlisi, her kayıt için Vector Embeddings oluşturmamız gerekiyor. Vektör yerleştirmeler, daha sonra doğal dilde sağlanan kullanıcı sorgusuyla benzerlik araması yapmamız gerektiğinde faydalı olacaktır.

Bunun için aşağıdaki adımları uygulayın:

  1. JSON nesneleri listesini nesne listesine dönüştürürüz. Her dokümanın iki özelliği vardır: content ve metadata. Meta veri nesnesi, name, description, sanskrit_name gibi özelliklere sahip tüm JSON nesnesini içerir. content, birkaç alanın birleştirilmesiyle oluşturulan bir dize metin olacaktır.
  2. Belgelerin listesini oluşturduktan sonra, içerik alanı için yerleştirme oluşturmak üzere Vertex AI Embeddings sınıfını kullanacağız. Bu yerleştirme her doküman kaydına eklenir. Ardından, bu doküman nesneleri listesini koleksiyona kaydetmek için Firestore API'yi kullanırız (test-poses değişkenini işaret eden TEST_COLLECTION değişkenini kullanıyoruz).

import-data.js kodunu aşağıda bulabilirsiniz (kodun bazı bölümleri kısa olması için kısaltılmıştır):

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

Bu uygulamayı çalıştırmamıza izin verin. Yeni bir terminal penceresi (Ctrl+Üst Karakter+C) başlatın ve aşağıdaki komutu verin:

npm run import-data

Her şey yolunda giderse aşağıdakine benzer bir mesaj görürsünüz:

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.

Kayıtların başarıyla eklenip eklenmediğini ve yerleştirmelerin oluşturulup oluşturulmadığını kontrol etmek için Cloud Console'daki Firestore sayfasını ziyaret edin.

504cabdb99a222a5.png

(Varsayılan) veritabanını tıklayın. Bu işlem, test-poses koleksiyonunu ve bu koleksiyonun altındaki birden fazla dokümanı gösterir. Her doküman bir yoga pozunu gösterir.

9f37aa199c4b547a.png

Alanları incelemek için dokümanlardan birini tıklayın. İçe aktardığımız alanlara ek olarak, embedding alanını da görürsünüz. Bu alan, text-embedding-004 Vertex AI Embedding modeli aracılığıyla değerini oluşturduğumuz bir vektör alanıdır.

f0ed92124519beaf.png

Kayıtlar yerleştirmelerle birlikte Firestore veritabanına yüklendiğine göre bir sonraki adıma geçebilir ve Firestore'da nasıl Vector Similarity Search yapılacağını görebiliriz.

6. Tam yoga pozlarını Firestore Database koleksiyonuna aktarma

Şimdi, doğrudan içe aktarabileceğiniz bir veritabanı içe aktarma dosyası oluşturduğumuz 160 yoga pozunun tam listesi olan poses koleksiyonunu oluşturacağız. Bu işlem, laboratuvarda zaman kazanmak için yapılır. Açıklama ve yerleştirmeleri içeren veritabanını oluşturma süreci, önceki bölümde gördüğümüzle aynıdır.

Aşağıdaki adımları uygulayarak veritabanını içe aktarın:

  1. Aşağıda verilen gsutil komutuyla projenizde bir paket oluşturun. Aşağıdaki komutta <PROJECT_ID> değişkenini Google Cloud proje kimliğinizle değiştirin.
gsutil mb -l us-central1 gs://<PROJECT_ID>-my-bucket
  1. Paket oluşturulduğuna göre, Firebase veritabanına aktarabilmemiz için hazırladığımız veritabanı dışa aktarma işlemini bu pakete kopyalamamız gerekir. Aşağıdaki komutu kullanın:
gsutil cp -r gs://yoga-database-firestore-export-bucket/2025-01-27T05:11:02_62615  gs://<PROJECT_ID>-my-bucket

İçe aktarılacak veriler olduğuna göre, oluşturduğumuz Firebase veritabanına (default) veri aktarma işleminin son adımına geçebiliriz.

  1. Aşağıdaki gcloud komutunu kullanın:
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2025-01-27T05:11:02_62615

İçe aktarma işlemi birkaç saniye sürer. İşlem tamamlandığında https://console.cloud.google.com/firestore/databases adresini ziyaret ederek Firestore veritabanınızı ve koleksiyonunuzu doğrulayabilirsiniz. Aşağıda gösterildiği gibi default veritabanını ve poses koleksiyonunu seçin:

561f3cb840de23d8.png

Böylece, uygulamamızda kullanacağımız Firestore koleksiyonunun oluşturulması tamamlanır.

7. Firestore'da vektör benzerliği araması yapma

Vektör benzerliği araması yapmak için kullanıcıdan gelen sorguyu alırız. Bu sorguya örnek olarak "Suggest me some exercises to relieve back pain" verilebilir.

search-data.js dosyasına göz atın. İncelenecek temel işlev, aşağıda gösterilen search işlevidir. Genel olarak, kullanıcı sorgusu için yerleştirme oluşturmak üzere kullanılacak bir yerleştirme sınıfı oluşturur. Ardından, Firestore veritabanı ve koleksiyonuyla bağlantı kurar. Ardından, koleksiyonda findNearest yöntemini çağırır. Bu yöntem, vektör benzerliği araması yapar.

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

Bu işlemi birkaç sorgu örneğiyle çalıştırmadan önce, arama sorgularınızın başarılı olması için gereken Firestore bileşik dizinini oluşturmanız gerekir. Uygulamayı dizini oluşturmadan çalıştırırsanız önce dizini oluşturmanız gerektiğini belirten bir hata mesajı ve dizini oluşturma komutu gösterilir.

Bileşik dizini oluşturmak için kullanılan gcloud komutu aşağıda gösterilmiştir:

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

Veritabanında 150'den fazla kayıt bulunduğundan dizinin tamamlanması birkaç dakika sürer. İşlem tamamlandığında dizini aşağıdaki komutla görüntüleyebilirsiniz:

gcloud firestore indexes composite list

Yeni oluşturduğunuz dizini listede görmeniz gerekir.

Şimdi aşağıdaki komutu deneyin:

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

Size birkaç öneri sunulur. Örnek bir çalıştırma aşağıda gösterilmektedir:

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

Bu işlem tamamlandıktan sonra, kayıtları yüklemek, yerleştirmeler oluşturmak ve vektör benzerliği araması yapmak için Firestore vektör veritabanının nasıl kullanılacağını öğrenmiş olursunuz. Artık vektör arama işlevini bir web ön ucuna entegre edecek bir web uygulaması oluşturabiliriz.

8. Web Uygulaması

Python Flask web uygulaması app.js dosyasında, ön uç HTML dosyası ise views/index.html. dosyasında bulunur.

Her iki dosyaya da göz atmanız önerilir. Öncelikle, ön uç HTML index.html dosyasından iletilen istemi alan /search işleyicisini içeren app.js dosyasıyla başlayın. Bu işlem, önceki bölümde incelediğimiz vektör benzerliği aramasını yapan arama yöntemini çağırır.

Yanıt, öneri listesiyle birlikte index.html'ye geri gönderilir. index.html, önerileri farklı kartlar olarak gösterir.

Uygulamayı yerel olarak çalıştırma

Yeni bir terminal penceresi (Ctrl+Üst Karakter+C) veya mevcut bir terminal penceresi başlatıp aşağıdaki komutu verin:

npm run start

Aşağıda örnek bir yürütme gösterilmektedir:

...
Server listening on port 8080

Uygulama çalışmaya başladıktan sonra aşağıdaki Web Önizlemesi düğmesini tıklayarak uygulamanın ana sayfa URL'sini ziyaret edin:

de297d4cee10e0bf.png

Aşağıda gösterildiği gibi, sunulan index.html dosyası gösterilmelidir:

20240a0e885ac17b.png

Örnek sorgu girin (örnek : Provide me some exercises for back pain relief) ve Search düğmesini tıklayın. Bu işlem, veritabanından bazı önerileri alır. Ayrıca, açıklamaya dayalı bir ses akışı oluşturacak olan Play Audio düğmesini de görürsünüz. Bu akışı doğrudan dinleyebilirsiniz.

789b4277dc40e2be.png

9. (İsteğe bağlı) Google Cloud Run'a dağıtma

Son adımımız bu uygulamayı Google Cloud Run'a dağıtmak olacak. Dağıtım komutu aşağıda gösterilmektedir. Dağıtmadan önce, aşağıda gösterilen parantez içindeki (<<>>) çeşitli değerleri değiştirdiğinizden emin olun. Bu değerleri .env dosyasından alabilirsiniz.

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

Yukarıdaki komutu uygulamanın kök klasöründen çalıştırın. Ayrıca Google Cloud API'lerini etkinleştirmeniz ve çeşitli izinler için onay vermeniz de istenebilir. Lütfen bu işlemleri yapın.

Dağıtım işleminin tamamlanması yaklaşık 5-7 dakika sürer. Lütfen bekleyin.

3a6d86fd32e4a5e.png

Başarıyla dağıtıldıktan sonra dağıtım çıktısında Cloud Run hizmeti URL'si sağlanır. Şu biçimde olacaktır:

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

Herkese açık URL'yi ziyaret ettiğinizde aynı web uygulamasının başarıyla dağıtıldığını ve çalıştığını görmeniz gerekir.

84e1cbf29cbaeedc.png

Google Cloud Console'dan Cloud Run'ı da ziyaret edebilirsiniz. Bu durumda, Cloud Run'daki hizmetlerin listesini görürsünüz. yogaposes hizmeti, orada listelenen hizmetlerden biri (tek hizmet değilse) olmalıdır.

f2b34a8c9011be4c.png

Belirli bir hizmet adını (bizim örneğimizde yogaposes) tıklayarak URL, yapılandırmalar, günlükler gibi hizmet ayrıntılarını görüntüleyebilirsiniz.

faaa5e0c02fe0423.png

Bu işlem, Cloud Run'da yoga pozları öneren web uygulamamızın geliştirme ve dağıtım sürecini tamamlar.

10. Tebrikler

Tebrikler. Bir veri kümesini Firestore'a yükleyen, yerleştirmeleri oluşturan ve kullanıcının sorgusuna göre vektör benzerliği araması yapan bir uygulamayı başarıyla oluşturdunuz.

Referans belgeler