Alma ile artırılmış oluşturma (RAG)

Firebase Genkit, alma destekli oluşturma (RAG) akışları oluşturmanıza yardımcı olan soyutlamaların yanı sıra ilgili araçlarla entegrasyon sağlayan eklentiler sunar.

RAG nedir?

Erişimle zenginleştirilmiş üretim, harici bilgi kaynaklarını LLM'nin yanıtlarına dahil etmek için kullanılan bir tekniktir. Bunu yapabilmek önemlidir çünkü LLM'ler genellikle çok sayıda materyalle ilgili eğitim almış olsa da LLM'lerin pratik kullanımı çoğu zaman belirli alan bilgisi gerektirir (örneğin, müşterilerin şirketinizin ürünleriyle ilgili sorularını yanıtlamak için LLM kullanmak isteyebilirsiniz).

Çözümlerden biri, daha spesifik veriler kullanarak modelde ince ayar yapmaktır. Ancak bu, hem işlem maliyeti hem de yeterli eğitim verileri hazırlamak için gereken çaba açısından pahalı olabilir.

RAG ise modele iletildiği sırada harici veri kaynaklarını bir isteme dahil ederek çalışır. Örneğin, "Bart'ın Lisa ile ilişkisi nedir?" isteminin bazı alakalı bilgiler öne getirilerek genişletilip genişletilerek "Homer ve Marge'ın çocuklarının adları Bart, Lisa ve Maggie" olduğunu düşünebilirsiniz. Bart'ın Lisa ile ilişkisi nedir?"

Bu yaklaşımın çeşitli avantajları vardır:

  • Modelinizi yeniden eğitmeniz gerekmeyeceğinden, daha uygun maliyetli olabilir.
  • Veri kaynağınızı sürekli olarak güncelleyebilirsiniz ve LLM güncellenen bilgilerden hemen yararlanabilir.
  • Artık LLM'nizin yanıtlarında referanslara atıfta bulunabilirsiniz.

Öte yandan, RAG kullanmak doğal olarak daha uzun istemler anlamına gelir ve bazı LLM API hizmetleri gönderdiğiniz her giriş jetonu için ücret alır. Sonuç olarak, uygulamalarınızın maliyet dengelerini değerlendirmeniz gerekir.

RAG çok geniş bir alandır ve en iyi kalitede RAG’ye ulaşmak için kullanılan birçok farklı teknik bulunur. Temel Genkit çerçevesi, RAG yapmanıza yardımcı olacak iki ana soyutlama sunar:

  • Dizine ekleme araçları: "dizine" doküman ekleyin.
  • Gömmeler: Belgeleri vektörel temsile dönüştürür
  • Alıcılar: Sorgu verilen bir "dizinden" dokümanları alın.

Genkit, bir "dizinin" ne olduğu veya ondan tam olarak nasıl belge alındığı konusunda fikir sahibi olmadığından bu tanımlar kasıtlı olarak kapsamlıdır. Genkit yalnızca Document biçimi sağlar. Diğer her şey, retriever veya dizinleyici uygulama sağlayıcısı tarafından tanımlanır.

Dizine Ekleyiciler

Dizin, belirli bir sorgu için alakalı belgeleri hızlı bir şekilde alabileceğiniz şekilde dokümanlarınızın takibinden sorumludur. Bu çoğu zaman, belgelerinizi yerleştirme adı verilen çok boyutlu vektörleri kullanarak dizine ekleyen bir vektör veritabanı kullanılarak gerçekleştirilir. Metin yerleştirme (opak) bir metin parçasında ifade edilen kavramları temsil eder. Bunlar özel amaçlı ML modelleri kullanılarak oluşturulur. Bir vektör veritabanı, yerleştirme özelliğini kullanarak metinleri dizine ekleyerek kavramsal olarak alakalı metinleri kümeleyebilir ve yeni bir metin dizesiyle (sorgu) ilişkili dokümanları alabilir.

Oluşturma amacıyla belgeleri alabilmeniz için önce bunları belge dizininize almanız gerekir. Tipik bir besleme akışında şunlar gerçekleşir:

  1. Büyük belgeleri daha küçük belgelere bölebilirsiniz. Böylece, istemlerinizi genişletmek için yalnızca alakalı bölümlerin, yani "bölünme"nin kullanılmasını sağlayın. Bu, birçok LLM'nin sınırlı bir bağlam penceresine sahip olduğu ve belgelerin tamamının bir istemle dahil edilmesini pratik hale getirmediği için gereklidir.

    Genkit, dahili öbekleme kitaplıkları sağlamaz ancak Genkit ile uyumlu olan açık kaynak kitaplıklar vardır.

  2. Her parça için yerleştirmeler oluşturun. Kullandığınız veritabanına bağlı olarak, bunu açıkça bir yerleştirme oluşturma modeliyle yapabilir veya veritabanı tarafından sağlanan yerleştirme oluşturma aracını kullanabilirsiniz.

  3. Metin parçasını ve dizinini veritabanına ekleyin.

Sabit bir veri kaynağıyla çalışıyorsanız besleme akışınızı seyrek olarak veya yalnızca bir kez çalıştırabilirsiniz. Öte yandan, sık sık değişen verilerle çalışıyorsanız alma akışını sürekli olarak çalıştırabilirsiniz (örneğin, bir Cloud Firestore tetikleyicisinde belge güncellendiğinde).

Gömmeler

Yerleştirme, içerik (metin, resim, ses vb.) alan ve orijinal içeriğin anlamsal anlamını kodlayan sayısal bir vektör oluşturan bir işlevdir. Yukarıda belirtildiği gibi, yerleştirilmiş öğeler dizine ekleme işleminin bir parçası olarak kullanılır. Bununla birlikte, dizin olmadan yerleştirmeler oluşturmak için bağımsız olarak da kullanılabilirler.

Toplayıcılar

Retriever, her türlü belge alma işlemiyle ilgili mantığı kapsayan bir kavramdır. En popüler alma durumları tipik olarak vektör mağazalarından alma içerir ancak Genkit'te bir retriever veri döndüren herhangi bir işlev olabilir.

Retriever oluşturmak için sağlanan uygulamalardan birini kullanabilir veya kendiniz bir tane oluşturabilirsiniz.

Desteklenen dizinleyiciler, retrieverlar ve gömücüler

Genkit, eklenti sistemi üzerinden dizinleyici ve retriever desteği sağlar. Aşağıdaki eklentiler resmi olarak desteklenmektedir:

Buna ek olarak Genkit, veritabanı yapılandırmanız ve şemanız için özelleştirebileceğiniz önceden tanımlanmış kod şablonları aracılığıyla aşağıdaki vektör mağazalarını destekler:

Yerleştirme modeli desteği aşağıdaki eklentiler aracılığıyla sağlanır:

Eklenti Modeller
Google Üretken Yapay Zeka Geko metin yerleştirme
Google Vertex AI Geko metin yerleştirme

RAG Akışı Tanımlama

Aşağıdaki örneklerde, restoran menüsü PDF dokümanlarından oluşan bir koleksiyonu bir vektör veritabanına nasıl alabileceğiniz ve hangi gıda ürünlerinin mevcut olduğunu belirleyen bir akışta kullanılmak üzere nasıl alabileceğiniz gösterilmektedir.

PDF'leri işlemek için bağımlılıkları yükleme

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

Yapılandırmanıza yerel bir vektör deposu ekleyin

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,
      },
    ]),
  ],
});

Dizine Ekleme aracı tanımlayın

Aşağıdaki örnekte, PDF belgeleri koleksiyonunu almak ve bunları yerel bir vektör veritabanında depolamak için dizinleyicinin nasıl oluşturulacağı gösterilmektedir.

Genkit'in basit test ve prototip oluşturma işlemleri için kullanıma hazır olarak sunduğu yerel dosya tabanlı vektör benzerlik retrieverını kullanır (üretimde kullanmayın)

Dizin oluşturucuyu oluşturma

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

export const menuPdfIndexer = devLocalIndexerRef('menuQA');

Bölümleme yapılandırması oluştur

Bu örnekte, dokümanları vektörelleştirilebilecek segmentlere ayırmak için basit bir metin ayırıcı sağlayan llm-chunk kitaplığı kullanılmıştır.

Aşağıdaki tanım, öbekleme işlevini; 1000 ile 2000 karakter arasında, bir cümlenin sonunda oluşturulmuş ve 100 karakterlik parçalar arasında çakışma olacak şekilde bir doküman segmentini garanti edecek şekilde yapılandırır.

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

Bu kitaplık için diğer öbekleme seçeneklerini llm-chunk dokümanlarında bulabilirsiniz.

Dizin aracı akışınızı tanımlayın

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

Dizinleyici akışını çalıştırma

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

indexMenu akışı çalıştırıldıktan sonra, vektör veritabanı belgelerle başlatılır ve alma adımlarıyla Genkit akışlarında kullanılmaya hazır olur.

Alma ile bir akışı tanımlama

Aşağıdaki örnekte, RAG akışında bir retriever'ı nasıl kullanabileceğiniz gösterilmektedir. Dizin oluşturucu örneğinde olduğu gibi bu örnekte de Genkit'in dosya tabanlı vektör retriever öğesi kullanılmaktadır. Bu hizmeti üretimde kullanmamalısınız.

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

Kendi dizinleyici ve retrieverlarınızı yazın

Kendi retrieverınızı da oluşturabilirsiniz. Bu, belgeleriniz Genkit'te desteklenmeyen bir belge deposunda yönetiliyorsa (ör. MySQL, Google Drive vb.) yararlı olur. Genkit SDK'sı, belgeleri getirmek için özel kod sağlamanıza olanak tanıyan esnek yöntemler sunar. Ayrıca, Genkit'teki mevcut retriever'lar üzerine kurulu özel alıcılar tanımlayabilir ve bunların üzerine gelişmiş RAM teknikleri (yeniden sıralama veya istem uzantıları gibi) uygulayabilirsiniz.

Basit Alıcılar

Simple retriever'lar, mevcut kodu kolayca retriever'lara dönüştürmenizi sağlar:

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

Özel Alıcılar

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 ve rerank, çerçeve tarafından sağlanmaz, sizin uygulamanız gerekir)

Ardından, retrieverınızı değiştirebilirsiniz:

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