रीट्राइवल-एग्मेंटेड जनरेशन (आरएजी)

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

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

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

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

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

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

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

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

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

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

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

इंडेक्स करने वाले टूल

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

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

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

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

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

  3. डेटाबेस में टेक्स्ट का हिस्सा और उसका इंडेक्स जोड़ें.

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

एम्बेड करने वाले

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

रिट्रीवर

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

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

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

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

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

मॉडल को एम्बेड करने की सुविधा, इन प्लग इन की मदद से मिलती है:

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

आरएजी फ़्लो तय करना

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

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

npm install llm-chunk pdf-parse @genkit-ai/dev-local-vectorstore
npm i -D --save @types/pdf-parse

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

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

const ai = genkit({
  plugins: [
    // vertexAI provides the textEmbedding004 embedder
    vertexAI(),

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

इंडेक्सर तय करना

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

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

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

export const menuPdfIndexer = devLocalIndexerRef('menuQA');

चंक करने का कॉन्फ़िगरेशन बनाना

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

यहां दी गई परिभाषा, चंक करने वाले फ़ंक्शन को कॉन्फ़िगर करती है, ताकि दस्तावेज़ के सेगमेंट में 1,000 से 2,000 वर्ण हों. यह सेगमेंट, वाक्य के आखिर में बंट जाता है. साथ ही, 100 वर्णों के चंक के बीच ओवरलैप होता है.

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

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

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

import { Document } from 'genkit/retriever';
import { chunk } from 'llm-chunk';
import { readFile } from 'fs/promises';
import path from 'path';
import pdf from 'pdf-parse';

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

export const indexMenu = ai.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 ai.index({
      indexer: menuPdfIndexer,
      documents,
    });
  }
);

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

genkit flow:run indexMenu "'menu.pdf'"

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

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

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

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

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

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

    // generate a response
   const { text } = await ai.generate({
      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}`,
      docs,
    });

    return text;
  }
);

इंडेक्स करने और डेटा वापस लाने वाले अपने प्रोग्राम लिखना

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

Simple Retrievers

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

import { z } from "genkit";
import { searchEmails } from "./db";

ai.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,
} from 'genkit/retriever';
import { z } from 'genkit';

export const menuRetriever = devLocalRetrieverRef('menuQA');

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

const advancedMenuRetriever = ai.defineRetriever(
  {
    name: `custom/advancedMenuRetriever`,
    configSchema: advancedMenuRetrieverOptionsSchema,
  },
  async (input, options) => {
    const extendedPrompt = await extendPrompt(input);
    const docs = await ai.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 ai.retrieve({
  retriever: advancedRetriever,
  query: input,
  options: { preRerankK: 7, k: 3 },
});

रीरैंकर और दो चरणों में खोज नतीजे दिखाने की सुविधा

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

रीरैंकर का उदाहरण

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

const FAKE_DOCUMENT_CONTENT = [
  'pythagorean theorem',
  'e=mc^2',
  'pi',
  'dinosaurs',
  'quantum mechanics',
  'pizza',
  'harry potter',
];

export const rerankFlow = ai.defineFlow(
  {
    name: 'rerankFlow',
    inputSchema: z.object({ query: z.string() }),
    outputSchema: z.array(
      z.object({
        text: z.string(),
        score: z.number(),
      })
    ),
  },
  async ({ query }) => {
    const documents = FAKE_DOCUMENT_CONTENT.map((text) =>
       ({ content: text })
    );

    const rerankedDocuments = await ai.rerank({
      reranker: 'vertexai/semantic-ranker-512',
      query:  ({ content: query }),
      documents,
    });

    return rerankedDocuments.map((doc) => ({
      text: doc.content,
      score: doc.metadata.score,
    }));
  }
);

दस्तावेज़ों को फिर से रैंक करने वाला यह टूल, semantic-ranker-512 के साथ Vertex AI genkit प्लग इन का इस्तेमाल करता है, ताकि दस्तावेज़ों को स्कोर और रैंक किया जा सके. स्कोर जितना ज़्यादा होगा, दस्तावेज़ क्वेरी के लिए उतना ही काम का होगा.

कस्टम रीरैंकर

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

export const customReranker = ai.defineReranker(
  {
    name: 'custom/reranker',
    configSchema: z.object({
      k: z.number().optional(),
    }),
  },
  async (query, documents, options) => {
    // Your custom reranking logic here
    const rerankedDocs = documents.map((doc) => {
      const score = Math.random(); // Assign random scores for demonstration
      return {
        ...doc,
        metadata: { ...doc.metadata, score },
      };
    });

    return rerankedDocs.sort((a, b) => b.metadata.score - a.metadata.score).slice(0, options.k || 3);
  }
);

तय करने के बाद, इस कस्टम रीरैंकर का इस्तेमाल, आपके आरएजी फ़्लो में किसी भी दूसरे रीरैंकर की तरह किया जा सकता है. इससे, आपको रीरैंकिंग की बेहतर रणनीतियों को लागू करने में मदद मिलती है.