Generowanie rozszerzone przez wyszukiwanie w zapisanych informacjach (RAG)

Firebase Genkit udostępnia abstrakcje, które ułatwiają tworzenie generacji rozszerzonego zakresu danych przepływy RAG oraz wtyczki umożliwiające integrację z powiązanymi narzędziami.

Co to jest RAG?

Generacja oparta na pobieraniu to technika wykorzystywana do uwzględniania źródeł informacji w odpowiedziach LLM. Ważne jest, by móc Chociaż duże modele językowe są zwykle trenowane na praktyczne zastosowania modeli LLM często wymagają specjalistycznej wiedzy z jego dziedziny (na Możesz na przykład użyć modelu LLM, aby odpowiadać na pytania klientów, Pytania dotyczące usług firmy).

Jednym z rozwiązań jest dostrojenie modelu przy użyciu bardziej szczegółowych danych. Jednak może być kosztowny zarówno pod względem kosztów mocy obliczeniowej, jak i nakładu pracy aby przygotować odpowiednie dane treningowe.

RAG działa natomiast, uwzględniając zewnętrzne źródła danych w prompcie czas przesyłania do modelu. Wyobraź sobie na przykład prompt, „Jaki jest związek Bartka z Lisą?” mogą zostać rozszerzone przez poprzedzają pewne istotne informacje i wyświetla się komunikat: „Homer Dzieci Magdy mają imiona Bart, Lisa i Maggie. Jaka jest relacja Bartka? do Lizy?”.

Takie podejście ma kilka zalet:

  • Może to być bardziej opłacalne, ponieważ nie trzeba ponownie trenować modelu.
  • Możesz stale aktualizować źródło danych, a LLM od razu korzystanie ze zaktualizowanych informacji.
  • Teraz masz możliwość powołania się na odniesienia w odpowiedziach LLM.

Z kolei używanie RAG oznacza w naturalny sposób dłuższe prompty i niektóre interfejsy LLM API. opłaty za każdy wysłany token wejściowy są naliczane. Ostatecznie należy ocenić obniżenia kosztów aplikacji.

RAG to bardzo szeroki obszar. Istnieje wiele różnych technik służących do najwyższej jakości. Podstawowa platforma Genkit opiera się na 2 głównych abstrakcjach może pomóc RAG-om:

  • Indeksy: dodawanie dokumentów do „indeksu”.
  • Umieszczanie: przekształca dokumenty do reprezentacji wektorowej
  • Moduły pobierania: pobieranie dokumentów z „indeksu” dla zapytania.

Te definicje są celowo szerokie, ponieważ firma Genkit nie ma o niej opinii co to znaczy „indeks” czyli jak dokładnie pobierane są z niego dokumenty. Tylko Genkit udostępnia format Document, a pozostałe są zdefiniowane przez retrievera lub dostawcy implementacji usługi indeksującej.

Indeksujący

Indeks odpowiada za śledzenie dokumentów w sposób można szybko pobrać odpowiednie dokumenty dla konkretnego zapytania. To najbardziej często można osiągnąć za pomocą wektorowej bazy danych, która indeksuje dokumenty za pomocą wielowymiarowych wektorów dystrybucyjnych. Osadzanie tekstu (nieprzezroczyste) reprezentuje koncepcje wyrażone przez fragment tekstu; są generowane za pomocą specjalnych modeli ML. Indeksowanie tekstu za pomocą jego wektora dystrybucyjnego baza danych jest w stanie grupować koncepcyjnie powiązany tekst i pobierać dokumenty lub powiązane z nowym ciągiem tekstowym (zapytaniem).

Aby móc pobrać dokumenty na potrzeby ich wygenerowania, musisz: ich pozyskanie do indeksu dokumentów. Typowy proces pozyskiwania danych :

  1. Podziel duże dokumenty na mniejsze, aby uwzględnić tylko te, które są istotne są wykorzystywane do rozszerzania promptów, Jest to konieczne ponieważ wiele LLM ma ograniczone okno kontekstu, przez co niepraktyczne jest aby uwzględnić całe dokumenty z promptem,

    Genkit nie udostępnia wbudowanych bibliotek do dzielenia na fragmenty; jednak istnieją otwarte i biblioteki źródłowe kompatybilne z Genkit.

  2. Wygeneruj wektory dystrybucyjne dla każdego fragmentu. W zależności od używanej bazy danych możesz to zrobić jawnie za pomocą modelu generowania wektora dystrybucyjnego lub może użyć generatora wektorów dystrybucyjnych udostępnianego przez bazę danych.

  3. Dodaj fragment tekstu i jego indeks do bazy danych.

Jeśli pracujesz, proces pozyskiwania możesz uruchamiać nieregularnie lub tylko raz. ze stabilnym źródłem danych. Jeśli natomiast pracujesz z danymi, często się zmienia, możesz stale uruchamiać proces pozyskiwania (na przykład np. w aktywatorze Cloud Firestore za każdym razem, gdy dokument jest aktualizowany).

Umieszczanie

Umieszczanie na stronie to funkcja, która pobiera treść (tekst, obrazy, dźwięk itp.) i tworzy wektor numeryczny, który koduje semantyczne znaczenie oryginalnej treści. Jak wspomnieliśmy powyżej, w procesie indeksowania wykorzystywane są obiekty osadzone, ale można ich też używać niezależnie do tworzenia wektorów dystrybucyjnych bez indeksu.

retrievery

retriever to koncepcja uwzględniająca logikę związaną z dowolnym dokumentem. pobieranie danych. Najczęściej są to pobrania z ale w programie Genkit retriever może być dowolną funkcją zwracającą dane.

Aby utworzyć retrievera, możesz użyć jednej z podanych implementacji lub tworzyć własne.

Obsługiwane moduły indeksujące, pobrane oraz umieszczone

Genkit zapewnia obsługę indeksowania i pobierania za pomocą systemu wtyczek. następujące wtyczki są oficjalnie obsługiwane:

Dodatkowo Genkit obsługuje następujące magazyny wektorowe za pomocą wstępnie zdefiniowanych szablonów kodu, które można dostosowywać pod kątem konfiguracji bazy danych schemat:

Obsługa modelu umieszczania jest zapewniana przez te wtyczki:

Wtyczka Modele
Generatywna AI od Google Osadzanie tekstu gekon
Google Vertex AI Osadzanie tekstu gekon

Definiowanie przepływu RAG

Poniższe przykłady pokazują, jak można pozyskać kolekcję dokumentów PDF z menu restauracji do bazy danych wektorowych i pobierać je do wykorzystania w procesie, który określa, które produkty spożywcze są dostępne.

Instalowanie zależności do przetwarzania plików PDF

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

Dodaj lokalny magazyn wektorowy do konfiguracji

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

Definiowanie indeksującego

Poniższy przykład pokazuje, jak utworzyć mechanizm indeksujący do pozyskiwania kolekcji dokumentów PDF i przechowywanie ich w lokalnej bazie danych wektorowych.

Wykorzystuje narzędzie do pobierania podobieństw wektorowych na podstawie lokalnego pliku. które firma Genkit od razu udostępnia gotowe do testowania i tworzenia prototypów (nie wykorzystania w środowisku produkcyjnym)

Tworzenie indeksującego

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

export const menuPdfIndexer = devLocalIndexerRef('menuQA');

Utwórz konfigurację dzielenia na fragmenty

W tym przykładzie użyliśmy biblioteki llm-chunk, która udostępnia prosty podział tekstu do dzielenia dokumentów na segmenty, które można poddać wektoryzacji.

Poniższa definicja konfiguruje funkcję dzielenia na fragmenty, aby zapewnić gwarancję dla segmentu dokumentu zawierającego od 1000 do 2000 znaków, który jest dzielony na końcu zdania, w przypadku nakładania się na siebie fragmentów 100 znaków.

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

Więcej opcji dzielenia na fragmenty w tej bibliotece znajdziesz w dokumentacji funkcji llm-chunk.

Zdefiniuj przepływ robota indeksującego

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

Uruchamianie procesu indeksującego

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

Po uruchomieniu procesu indexMenu wektorowa baza danych zostanie zapełniona dokumentami i będzie gotowa do użycia w przepływach Genkit z krokami pobierania.

Definiowanie procesu z pobieraniem

Poniższy przykład pokazuje, jak można użyć retrievera w przepływie RAG. Polub z indeksem – w tym przykładzie użyto opartego na plikach narzędzia do pobierania wektorów wektorowych Genkit, których nie należy używać w środowisku produkcyjnym.

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

Stwórz własne roboty indeksujące i mechanizmy retrievery

Możesz też stworzyć własnego retrievera. Jest to przydatne, jeśli dokumenty są zarządzane w magazynie dokumentów, który nie jest obsługiwany w Genkit (np. MySQL, Dysku Google itp.). Genkit SDK udostępnia elastyczne metody, udostępniasz niestandardowy kod pobierania dokumentów. Możesz też określić niestandardowe retrievery bazujące na dotychczasowych retrieverach w Genkit i korzystające z zaawansowanych techniki RAG (np. ponowne pozycjonowanie lub rozszerzenia promptów).

Proste odbioru

Proste programy retrievery umożliwiają łatwe przekonwertowanie istniejącego kodu na retrievery:

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

Niestandardowe moduły odbioru

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 i rerank musisz wdrożyć samodzielnie, nie są dostępne w ramach zasad).

Potem możesz po prostu wymienić retrievera:

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