रिक्वेस्टमेंटेड जनरेशन (आरएजी)

Firebase Genkit एब्सट्रैक्ट जानकारी देता है, जो वापस पाने वाले जनरेशन (RAG) फ़्लो को बनाने में आपकी मदद करती हैं. साथ ही, ऐसे प्लगिन भी देती हैं जो मिलते-जुलते टूल के साथ इंटिग्रेशन उपलब्ध कराते हैं.

आरएजी क्या है?

रीट्रिवल-एग्मेंटेड जनरेशन एक ऐसी तकनीक है जिसका इस्तेमाल, एलएलएम के जवाबों में जानकारी के बाहरी सोर्स को शामिल करने के लिए किया जाता है. ऐसा करना ज़रूरी है, क्योंकि एलएलएम को आम तौर पर सामग्री के बड़े हिस्से पर ट्रेनिंग दी जाती है. लेकिन व्यावहारिक इस्तेमाल के लिए एलएलएम के लिए, डोमेन की खास जानकारी की ज़रूरत होती है. जैसे, अपनी कंपनी के प्रॉडक्ट के बारे में ग्राहकों के सवालों के जवाब देने के लिए, एलएलएम का इस्तेमाल किया जा सकता है.

एक समाधान यह है कि मॉडल को बेहतर बनाने के लिए, ज़्यादा सटीक डेटा का इस्तेमाल किया जाए. हालांकि, यह कंप्यूट कॉस्ट और ट्रेनिंग का ज़रूरी डेटा तैयार करने में लगने वाली ज़रूरत के हिसाब से काफ़ी महंगा हो सकता है.

वहीं दूसरी ओर, मॉडल को पास किए जाने के दौरान, RAG बाहरी डेटा सोर्स को प्रॉम्प्ट में शामिल करता है. उदाहरण के लिए, एक सवाल सोचें कि "लीज़ा के साथ बार्ट का संबंध क्या है?" को कुछ काम की जानकारी देकर बड़ा (बढ़ावा) किया जा सकता है ("आगे बढ़ाया गया"). इस प्रॉम्प्ट में यह प्रॉम्प्ट मिला, "होमर और मार्ज के बच्चों का नाम बार्ट, लीसा, और मैगी है. लीसा से बार्ट का क्या संबंध है?"

इस तरीके के कई फ़ायदे हैं:

  • यह मॉडल ज़्यादा किफ़ायती हो सकता है, क्योंकि आपको इसे फिर से बनाने की ज़रूरत नहीं है.
  • डेटा सोर्स को लगातार अपडेट किया जा सकता है और एलएलएम, अपडेट की गई जानकारी का तुरंत इस्तेमाल कर सकता है.
  • अब एलएलएम के जवाबों में, रेफ़रंस के तौर पर उद्धरण दिए जा सकते हैं.

वहीं दूसरी ओर, आरएजी का इस्तेमाल सामान्य तौर पर लंबे प्रॉम्प्ट के लिए किया जाता है. साथ ही, आपके भेजे गए हर इनपुट टोकन के लिए एलएलएम एपीआई की कुछ सेवाओं का शुल्क लिया जाता है. आख़िर में, आपको अपने ऐप्लिकेशन के लिए कीमतों के ट्रेडऑफ़ का आकलन करना होगा.

आरएजी बहुत बड़ा इलाका है और बेहतरीन क्वालिटी वाला आरएजी हासिल करने के लिए कई अलग-अलग तकनीकों का इस्तेमाल किया जाता है. आरएजी करने में आपकी मदद करने के लिए, कोर जेनकिट फ़्रेमवर्क में दो मुख्य चीज़ें शामिल होती हैं:

  • इंडेक्स करने वाले: "इंडेक्स" में दस्तावेज़ जोड़ें.
  • एम्बेडर: दस्तावेज़ों को वेक्टर रिप्रज़ेंटेशन में बदलता है
  • रिट्रीवर: क्वेरी के हिसाब से "इंडेक्स" से दस्तावेज़ फ़ेच किए जाते हैं.

ये परिभाषाएं बहुत बड़ी हैं, क्योंकि जेनकिट ने यह नहीं बताया है कि "इंडेक्स" क्या होता है या इससे दस्तावेज़ों को कैसे लिया जाता है. Genkit से सिर्फ़ Document फ़ॉर्मैट मिलता है. बाकी सब कुछ, रिट्रीवर या इंडेक्सर लागू करने वाली कंपनी तय करती है.

इंडेक्सर

यह इंडेक्स, आपके दस्तावेज़ों पर इस तरह से नज़र रखने के लिए ज़िम्मेदार है कि आप किसी क्वेरी के हिसाब से काम के दस्तावेज़ तुरंत वापस पा सकते हैं. अक्सर ऐसा करने के लिए, वेक्टर डेटाबेस का इस्तेमाल किया जाता है. यह कई डाइमेंशन वाले वेक्टर इस्तेमाल करके आपके दस्तावेज़ों को इंडेक्स करता है, जिन्हें एम्बेडिंग कहा जाता है. टेक्स्ट एम्बेड करने (ओपेकली) में, टेक्स्ट के किसी पैसेज के ज़रिए दिखाए गए कॉन्सेप्ट को दिखाया जाता है. इन्हें खास मकसद वाले एमएल मॉडल का इस्तेमाल करके जनरेट किया जाता है. टेक्स्ट को एम्बेड करके इंडेक्स करने पर, वेक्टर डेटाबेस सैद्धांतिक तौर पर मिलते-जुलते टेक्स्ट को क्लस्टर कर सकता है और टेक्स्ट की नई स्ट्रिंग (क्वेरी) से जुड़े दस्तावेज़ों को वापस ला सकता है.

जनरेट करने के लिए दस्तावेज़ वापस पाने से पहले, आपको उन्हें अपने दस्तावेज़ के इंडेक्स में डालना होगा. डेटा डालने के सामान्य फ़्लो में, ये काम किए जाते हैं:

  1. बड़े दस्तावेज़ों को छोटे-छोटे दस्तावेज़ों में बांटें, ताकि आपके प्रॉम्प्ट बेहतर बनाने के लिए, सिर्फ़ काम के हिस्सों का इस्तेमाल किया जा सके – "चंकिंग". ऐसा इसलिए ज़रूरी है, क्योंकि कई एलएलएम में कॉन्टेक्स्ट के लिए सीमित विंडो होती है. इस वजह से, प्रॉम्प्ट के साथ पूरे दस्तावेज़ों को शामिल करना संभव नहीं होता.

    Genkit में पहले से मौजूद कई हिस्सों में बांटी जाने वाली लाइब्रेरी उपलब्ध नहीं हैं. हालांकि, कुछ ऐसी ओपन सोर्स लाइब्रेरी भी उपलब्ध हैं जो Genkit के साथ काम करती हैं.

  2. हर हिस्से के लिए एम्बेडिंग जनरेट करें. इस्तेमाल किए जा रहे डेटाबेस के आधार पर, ऐसा किया जा सकता है. इसके लिए, एम्बेड करने वाले जनरेशन मॉडल का इस्तेमाल किया जा सकता है या डेटाबेस से मिले एम्बेडिंग जनरेटर का इस्तेमाल किया जा सकता है.

  3. डेटाबेस में, टेक्स्ट का डेटा ग्रुप और उसका इंडेक्स जोड़ें.

ऐसा हो सकता है कि डेटा डालने के फ़्लो को बार-बार या सिर्फ़ एक बार चलाया जा सके. ऐसा तब होता है, जब डेटा के सोर्स को बेहतर बनाने के लिए काम किया जा रहा हो. दूसरी ओर, अगर ऐसे डेटा के साथ काम किया जा रहा है जिसमें बार-बार बदलाव होता है, तो डेटा डालने का फ़्लो लगातार चल सकता है (उदाहरण के लिए, जैसे कि Cloud Firestore ट्रिगर में, जब भी दस्तावेज़ अपडेट होगा).

एम्बेडर

एम्बेडर एक ऐसा फ़ंक्शन है जो कॉन्टेंट (टेक्स्ट, इमेज, ऑडियो वगैरह) को लेता है और एक न्यूमेरिक वेक्टर बनाता है जो ओरिजनल कॉन्टेंट के सिमैंटिक मतलब को कोड में बदलता है. जैसा कि ऊपर बताया गया है, एम्बेड करने वाले लोगों का इस्तेमाल, इंडेक्स करने की प्रोसेस के तहत किया जाता है. हालांकि, बिना इंडेक्स के एम्बेड करने के लिए, उनका इस्तेमाल अलग से भी किया जा सकता है.

रिट्रीवर

रिट्रीवर एक ऐसा सिद्धांत है जिसमें किसी भी तरह का दस्तावेज़ वापस पाने से जुड़े लॉजिक को शामिल किया जाता है. उपयोगकर्ताओं को वापस पाने के सबसे लोकप्रिय मामलों में, आम तौर पर वेक्टर स्टोर से डेटा वापस पाने की सुविधा शामिल होती है. हालांकि, Genkit में रिट्रीवर ऐसा कोई भी फ़ंक्शन हो सकता है जो डेटा दिखाता हो.

रिट्रीवर बनाने के लिए, उसके दिए गए तरीकों में से किसी एक का इस्तेमाल किया जा सकता है या अपना खुद का बनाया जा सकता है.

साथ काम करने वाले इंडेक्सर, रिट्रीवर, और एम्बेडर

Genkit, अपने प्लगिन सिस्टम की मदद से इंडेक्सर और रिट्रीवर की सुविधा देता है. नीचे दिए गए प्लग इन आधिकारिक तौर पर काम करते हैं:

इसके अलावा, जेनकिट पहले से तय कोड टेंप्लेट की मदद से, इन वेक्टर स्टोर के साथ काम करता है. इन्हें अपने डेटाबेस कॉन्फ़िगरेशन और स्कीमा के हिसाब से कस्टमाइज़ किया जा सकता है:

एम्बेडिंग मॉडल से जुड़ी सहायता इन प्लगिन के ज़रिए दी जाती है:

प्लग इन मॉडल
Google का जनरेटिव एआई गेको टेक्स्ट एम्बेड करना
Google Vertex AI गेको टेक्स्ट एम्बेड करना

आरएजी फ़्लो की परिभाषा

नीचे दिए गए उदाहरणों में दिखाया गया है कि रेस्टोरेंट मेन्यू के PDF दस्तावेज़ों के कलेक्शन को वेक्टर डेटाबेस में कैसे डाला जा सकता है और उन्हें उस फ़्लो में इस्तेमाल करने के लिए कैसे हासिल किया जा सकता है जो यह तय करता है कि खाने की कौनसी चीज़ें उपलब्ध हैं.

PDF प्रोसेस करने के लिए डिपेंडेंसी इंस्टॉल करें

npm install llm-chunk pdf-parse
npm i -D --save @types/pdf-parse

अपने कॉन्फ़िगरेशन में लोकल वेक्टर स्टोर जोड़ें

import {
  devLocalIndexerRef,
  devLocalVectorstore,
} from '@genkit-ai/dev-local-vectorstore';
import { textEmbeddingGecko, vertexAI } from '@genkit-ai/vertexai';

configureGenkit({
  plugins: [
    // vertexAI provides the textEmbeddingGecko embedder
    vertexAI(),

    // the local vector store requires an embedder to translate from text to vector
    devLocalVectorstore([
      {
        indexName: 'menuQA',
        embedder: textEmbeddingGecko,
      },
    ]),
  ],
});

इंडेक्सर की परिभाषा तय करें

नीचे दिए गए उदाहरण में PDF दस्तावेज़ों का कलेक्शन डालने और लोकल वेक्टर डेटाबेस में स्टोर करने के लिए, इंडेक्सर बनाने का तरीका बताया गया है.

यह टूल, फ़ाइल पर आधारित लोकल वेक्टर समानता रिट्रीवर का इस्तेमाल करता है. Genkit इसे सामान्य जांच और प्रोटोटाइप करने के लिए उपलब्ध कराता है. प्रोडक्शन में इसका इस्तेमाल न करें

इंडेक्सर बनाना

import { devLocalIndexerRef } from '@genkit-ai/dev-local-vectorstore';

export const menuPdfIndexer = devLocalIndexerRef('menuQA');

कई हिस्सों में बांटने का कॉन्फ़िगरेशन बनाएं

इस उदाहरण में llm-chunk लाइब्रेरी का इस्तेमाल किया गया है. इसमें दस्तावेज़ों को ऐसे सेगमेंट में बांटने के लिए आसान टेक्स्ट स्प्लिटर दिया गया है जिन्हें वेक्टर किया जा सकता है.

यहां दी गई परिभाषा में, छोटे-छोटे हिस्सों में भेजने वाले फ़ंक्शन को कॉन्फ़िगर किया गया है, ताकि दस्तावेज़ के 1000 से 2000 वर्णों के सेगमेंट का सेगमेंट तय किया जा सके. यह वाक्य के आखिर में बांटा गया है. साथ ही, इसमें 100 से ज़्यादा वर्ण हो सकते हैं.

const chunkingConfig = {
  minLength: 1000,
  maxLength: 2000,
  splitter: 'sentence',
  overlap: 100,
  delimiters: '',
} as any;

इस लाइब्रेरी के लिए छोटे-छोटे हिस्सों में भेजने के ज़्यादा विकल्प llm-chunk दस्तावेज़ में देखे जा सकते हैं.

अपना इंडेक्सर फ़्लो तय करें

import { index } from '@genkit-ai/ai';
import { Document } from '@genkit-ai/ai/retriever';
import { defineFlow, run } from '@genkit-ai/flow';
import { readFile } from 'fs/promises';
import { chunk } from 'llm-chunk';
import path from 'path';
import pdf from 'pdf-parse';
import * as z from 'zod';

export const indexMenu = defineFlow(
  {
    name: 'indexMenu',
    inputSchema: z.string().describe('PDF file path'),
    outputSchema: z.void(),
  },
  async (filePath: string) => {
    filePath = path.resolve(filePath);

    // Read the pdf.
    const pdfTxt = await run('extract-text', () =>
      extractTextFromPdf(filePath)
    );

    // Divide the pdf text into segments.
    const chunks = await run('chunk-it', async () =>
      chunk(pdfTxt, chunkingConfig)
    );

    // Convert chunks of text into documents to store in the index.
    const documents = chunks.map((text) => {
      return Document.fromText(text, { filePath });
    });

    // Add documents to the index.
    await index({
      indexer: menuPdfIndexer,
      documents,
    });
  }
);

async function extractTextFromPdf(filePath: string) {
  const pdfFile = path.resolve(filePath);
  const dataBuffer = await readFile(pdfFile);
  const data = await pdf(dataBuffer);
  return data.text;
}

इंडेक्सर फ़्लो चलाना

genkit flow:run indexMenu "'../pdfs'"

indexMenu फ़्लो को चलाने के बाद, वेक्टर डेटाबेस में दस्तावेज़ों को शामिल किया जाएगा. साथ ही, यह जेनकिट फ़्लो में वापस पाने के चरणों के साथ इस्तेमाल होने के लिए तैयार होगा.

डेटा पाने के लिए फ़्लो तय करना

यहां दिए गए उदाहरण में बताया गया है कि आरएजी फ़्लो में, रिट्रीवर का इस्तेमाल कैसे किया जा सकता है. इंडेक्सर वाले उदाहरण की तरह, इस उदाहरण में भी Genkit के फ़ाइल-आधारित वेक्टर रिट्रीवर का इस्तेमाल किया गया है. आपको प्रोडक्शन में इसका इस्तेमाल नहीं करना चाहिए.

import { generate } from '@genkit-ai/ai';
import { retrieve } from '@genkit-ai/ai/retriever';
import { devLocalRetrieverRef } from '@genkit-ai/dev-local-vectorstore';
import { defineFlow } from '@genkit-ai/flow';
import { geminiPro } from '@genkit-ai/vertexai';
import * as z from 'zod';

// Define the retriever reference
export const menuRetriever = devLocalRetrieverRef('menuQA');

export const menuQAFlow = defineFlow(
  { name: 'menuQA', inputSchema: z.string(), outputSchema: z.string() },
  async (input: string) => {
    // retrieve relevant documents
    const docs = await retrieve({
      retriever: menuRetriever,
      query: input,
      options: { k: 3 },
    });

    // generate a response
    const llmResponse = await generate({
      model: geminiPro,
      prompt: `
    You are acting as a helpful AI assistant that can answer 
    questions about the food available on the menu at Genkit Grub Pub.
    
    Use only the context provided to answer the question.
    If you don't know, do not make up an answer.
    Do not add or change items on the menu.

    Question: ${input}
    `,
      context: docs,
    });

    const output = llmResponse.text();
    return output;
  }
);

अपने इंडेक्सर और रिट्रीवर खुद लिखें

अपना खुद का रिट्रीवर बनाया भी जा सकता है. यह तब काम आता है, जब आपके दस्तावेज़ों को ऐसे दस्तावेज़ स्टोर में मैनेज किया जाता हो जो Genkit के साथ काम नहीं करता. जैसे, MySQL, Google Drive वगैरह. Genkit SDK टूल के कई तरीके हैं. इनकी मदद से, दस्तावेज़ फ़ेच करने के लिए अपनी पसंद के मुताबिक कोड दिए जा सकते हैं. आपके पास उन कस्टम रिट्रीवर को भी तय करने का विकल्प होता है जो Genkit में पहले से मौजूद रिट्रीवर को बेहतर बनाते हैं. साथ ही, सबसे ऊपर आरएजी टेक्नोलॉजी (जैसे कि रीरैंकिंग या प्रॉम्प्ट एक्सटेंशन) का इस्तेमाल करते हैं.

सिंपल रिट्रीवर

सिंपल रिट्रीवर की मदद से, मौजूदा कोड को आसानी से रिट्रीवर में बदला जा सकता है:

import {
  defineSimpleRetriever,
  retrieve
} from '@genkit-ai/ai/retriever';
import { searchEmails } from './db';
import { z } from 'zod';

defineSimpleRetriever({
  name: 'myDatabase',
  configSchema: z.object({
    limit: z.number().optional()
  }).optional(),
  // we'll extract "message" from the returned email item
  content: 'message',
  // and several keys to use as metadata
  metadata: ['from', 'to', 'subject'],
} async (query, config) => {
  const result = await searchEmails(query.text(), {limit: config.limit});
  return result.data.emails;
});

कस्टम रिट्रीवर

import {
  CommonRetrieverOptionsSchema,
  defineRetriever,
  retrieve,
} from '@genkit-ai/ai/retriever';
import * as z from 'zod';

export const menuRetriever = devLocalRetrieverRef('menuQA');

const advancedMenuRetrieverOptionsSchema = CommonRetrieverOptionsSchema.extend({
  preRerankK: z.number().max(1000),
});

const advancedMenuRetriever = defineRetriever(
  {
    name: `custom/advancedMenuRetriever`,
    configSchema: advancedMenuRetrieverOptionsSchema,
  },
  async (input, options) => {
    const extendedPrompt = await extendPrompt(input);
    const docs = await retrieve({
      retriever: menuRetriever,
      query: extendedPrompt,
      options: { k: options.preRerankK || 10 },
    });
    const rerankedDocs = await rerank(docs);
    return rerankedDocs.slice(0, options.k || 3);
  }
);

(extendPrompt और rerank ऐसा कुछ है जिसे आपको खुद लागू करना होगा, यह फ़्रेमवर्क में उपलब्ध नहीं होता)

इसके बाद, अपने रिट्रीवर को बदला जा सकता है:

const docs = await retrieve({
  retriever: advancedRetriever,
  query: input,
  options: { preRerankK: 7, k: 3 },
});