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

Firebase Genkit, alma işlemiyle desteklenen üretme (RAG) akışları oluşturmanıza yardımcı olan soyutlamalar ve ilgili araçlarla entegrasyon sağlayan eklentiler sağlar.

RAG nedir?

Almayla artırılmış üretim, harici bilgi kaynaklarını LLM yanıtlarına dahil etmek için kullanılan bir tekniktir. LLM'ler genellikle geniş bir materyal grubuyla eğitilse de pratik kullanımları genellikle belirli alan bilgisi gerektirdiğinden bunu yapabilmeniz önemlidir (ör. müşterilerin şirketinizin ürünleriyle ilgili sorularını yanıtlamak için bir LLM kullanmak isteyebilirsiniz).

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

Buna karşılık RAG, modele aktarılan istemlere harici veri kaynaklarını dahil ederek çalışır. Örneğin, "Bart ile Lisa'nın ilişkisi nedir?" istemin önüne bazı alakalı bilgiler eklenerek genişletildiğini ("artırıldığını") ve bunun sonucunda "Homer ve Marge'ın çocukları Bart, Lisa ve Maggie. Bartu ile Lisa'nın ilişkisi nedir?"

Bu yaklaşımın birkaç avantajı vardır:

  • Modeli yeniden eğitmeniz gerekmediğinden daha uygun maliyetli olabilir.
  • Veri kaynağınızı sürekli olarak güncelleyebilirsiniz. LLM, güncellenen bilgileri hemen kullanabilir.
  • Artık LLM'nizin yanıtlarında referans gösterebilirsiniz.

Ö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 için maliyet dengelerini değerlendirmeniz gerekir.

RAG çok geniş bir alandır ve en yüksek kaliteli RAG'ye ulaşmak için birçok farklı teknik kullanılır. Temel Genkit çerçevesi, RAG'yi gerçekleştirmenize yardımcı olacak üç ana soyutlama sunar:

  • Dizine ekleyenler: "Dizin"e belge ekler.
  • Yerleştirenler: Dokümanları vektör temsiline dönüştürür.
  • Alıcı: Bir sorgu verildiğinde "dizin"den doküman alır.

Genkit, "dizin"in ne olduğu veya belgelerden tam olarak nasıl alındığı konusunda herhangi bir görüş belirtmediği için bu tanımlar kasıtlı olarak geniş tutulmuştur. Genkit yalnızca bir Document biçimi sağlar ve geri alma veya dizine ekleme uygulama sağlayıcısı tarafından diğer her şey tanımlanır.

Dizine ekleyenler

Dizin, belirli bir sorgu verildiğinde alakalı belgeleri hızlı bir şekilde alabileceğiniz şekilde belgelerinizi takip etmekten sorumludur. Bu işlem genellikle, belgeleriniz için dizin oluşturan ve yerleşik olarak adlandırılan çok boyutlu vektörleri kullanan bir vektör veritabanı kullanılarak gerçekleştirilir. Metin yerleştirme, bir metin pasajında ifade edilen kavramları (şeffaf olmayan bir şekilde) temsil eder. Bunlar, özel amaçlı makine öğrenimi modelleri kullanılarak oluşturulur. Bir vektör veritabanı, metni yerleştirmesini kullanarak dizine ekleyerek kavramsal olarak alakalı metinleri gruplandırabilir ve yeni bir metin dizesiyle (sorgu) ilgili belgeleri alabilir.

Oluşturma amacıyla doküman almadan önce bunları doküman dizine eklemeniz gerekir. Tipik bir besleme akışı aşağıdakileri yapar:

  1. İstemlerinizi geliştirmek için yalnızca alakalı bölümlerin kullanılması amacıyla büyük belgeleri daha küçük belgelere bölün (bu işleme "bölümlere ayırma" denir). Bu, birçok LLM'nin sınırlı bir bağlam penceresine sahip olması nedeniyle gereklidir. Bu da dokümanların tamamını bir istemle dahil etmeyi pratik hale getirmez.

    Genkit, yerleşik parçalara ayırma kitaplıkları sağlamaz ancak Genkit ile uyumlu açık kaynak kitaplıklar mevcuttur.

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

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

Veri kaynağınız kararlıysa 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 besleme akışını sürekli olarak çalıştırabilirsiniz (ör. bir Cloud Firestore tetikleyicisinde, bir doküman her güncellendiğinde).

Yerleştirenler

Yerleştirici, 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, dizine ekleme işleminin bir parçası olarak yerleştiricilerden yararlanılır. Ancak bunlar, dizine ekleme olmadan yerleşimler oluşturmak için bağımsız olarak da kullanılabilir.

Retriever

Retriever, her türlü belge alma işlemiyle ilgili mantığı kapsayan bir kavramdır. En popüler getirme örnekleri genellikle vektör depolarından getirmeyi içerir. Ancak Genkit'te, veri döndüren herhangi bir işlev bir getirme işlevi olabilir.

Getirici oluşturmak için sağlanan uygulamalardan birini kullanabilir veya kendi getiricinizi oluşturabilirsiniz.

Desteklenen dizine ekleyenler, veri toplayıcılar ve yerleştiriciler

Genkit, eklenti sistemi aracılığıyla dizine ekleme ve alma desteği sağlar. Aşağıdaki eklentiler resmi olarak desteklenir:

Ayrıca Genkit, veritabanı yapılandırmanıza ve şemanıza göre özelleştirebileceğiniz önceden tanımlanmış kod şablonları aracılığıyla aşağıdaki vektör depolarını destekler:

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

Eklenti Modeller
Google Üretken Yapay Zeka Gecko metin yerleştirme
Google Vertex AI Gecko 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 besleyebileceğiniz ve hangi yiyeceklerin mevcut olduğunu belirleyen bir akışta kullanmak ü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 @genkit-ai/dev-local-vectorstore
npm i -D --save @types/pdf-parse

Yapılandırmanıza yerel bir vektör mağazası ekleme

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

Dizine Eklemeyi Tanımlama

Aşağıdaki örnekte, PDF dokümanlarından oluşan bir koleksiyonu besleyecek ve bunları yerel bir vektör veritabanında saklayacak bir dizine ekleme aracının nasıl oluşturulacağı gösterilmektedir.

Basit test ve prototipleme için Genkit'in hazır olarak sunduğu yerel dosya tabanlı vektör benzerliği alma aracını kullanır (üretimde kullanmayın)

Dizinleyiciyi oluşturma

export const menuPdfIndexer = devLocalIndexerRef('menuQA');

Bölme yapılandırması oluşturma

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

Aşağıdaki tanım, parçalara ayırma işlevini, 1000 ila 2000 karakter uzunluğunda bir belge segmenti elde edecek şekilde yapılandırır. Bu segment, bir cümlenin sonunda bölünür ve 100 karakterlik parçalar arasında çakışma olur.

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

Bu kitaplıkla ilgili daha fazla parçalara ayırma seçeneği llm-chunk dokümanlarında bulunabilir.

Dizine ekleme akışınızı tanımlama

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

Dizine ekleme akışını çalıştırma

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

indexMenu akışı çalıştırıldıktan sonra vektör veritabanı dokümanlarla doldurulur ve getirme adımlarına sahip Genkit akışlarında kullanılmaya hazır hale gelir.

Alma işlemi içeren bir akış tanımlama

Aşağıdaki örnekte, bir RAG akışında retriever'ı nasıl kullanabileceğiniz gösterilmektedir. Dizinleyici örneğinde olduğu gibi bu örnekte de Genkit'in dosya tabanlı vektör alıcısını kullanılır. Bu alıcıyı üretimde kullanmamanız gerekir.

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

Kendi dizine ekleme ve alma işlemlerinizi yazın

Kendi alıcınızı da oluşturabilirsiniz. Bu, dokümanlarınız Genkit'te desteklenmeyen bir doküman deposunda yönetiliyorsa (ör. MySQL, Google Drive vb.) kullanışlıdır. Genkit SDK'sı, doküman getirme için özel kod sağlamanıza olanak tanıyan esnek yöntemler sunar. Ayrıca, Genkit'teki mevcut alıcılara dayalı özel alıcıları tanımlayabilir ve bunların üzerine gelişmiş RAG teknikleri (yeniden sıralama veya istem uzantıları gibi) uygulayabilirsiniz.

Simple Retrievers

Basit alıcıları, mevcut kodu kolayca alıcıya dönüştürmenize olanak tanır:

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

Özel Alıcı

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 ve rerank, çerçeve tarafından sağlanmayan ve kendinizin uygulamanız gereken bir şeydir.)

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

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

Yeniden sıralama algoritmaları ve iki aşamalı getirme

Çapraz kodlayıcı olarak da bilinen yeniden sıralama modeli, bir sorgu ve doküman verildiğinde benzerlik puanı veren bir model türüdür. Bu puanı, dokümanları sorgumuzla alaka düzeyine göre yeniden sıralamak için kullanırız. Yeniden sıralama API'leri, bir doküman listesini (ör. bir alıcıdan alınan çıkış) alır ve dokümanları sorgu ile alaka düzeylerine göre yeniden sıralar. Bu adım, sonuçlarda ince ayar yapmak ve üretken bir modele sağlanan istemde en alakalı bilgilerin kullanılmasını sağlamak için yararlı olabilir.

Yeniden sıralama örneği

Genkit'teki yeniden sıralama aracı, alıcılara ve dizine ekleyenlere benzer bir söz diziminde tanımlanır. Genkit'te yeniden sıralama aracı kullanan bir örneği aşağıda bulabilirsiniz. Bu akış, önceden tanımlanmış bir Vertex AI yeniden sıralama aracı kullanarak bir belge grubunu sağlanan sorguyla alaka düzeyine göre yeniden sıralar.

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

Bu yeniden sıralama aracı, belgeleri puanlamak ve sıralamak için semantic-ranker-512 ile Vertex AI genkit eklentisini kullanır. Puan ne kadar yüksek olursa belgenin sorgu ile alaka düzeyi de o kadar yüksek olur.

Özel yeniden sıralama araçları

Ayrıca, özel yeniden sıralama araçları tanımlayarak belirli bir kullanım alanınıza uygun bir çözüm elde edebilirsiniz. Bu, kendi özel mantığınızı veya özel bir modeli kullanarak belgeleri yeniden sıralamanız gerektiğinde yararlıdır. Özel bir yeniden sıralama aracını tanımlamaya ilişkin basit bir örnek aşağıda verilmiştir:

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

Tanımlandıktan sonra bu özel yeniden sıralama aracı, RAG akışlarınızdaki diğer tüm yeniden sıralama araçları gibi kullanılabilir. Böylece, gelişmiş yeniden sıralama stratejileri uygulama esnekliği elde edersiniz.