Szybka odpowiedź: Retrieval-Augmented Generation (RAG) to dwa pomysły sklejone razem. Najpierw raz osadzasz swoje dokumenty i przechowujesz powstałe wektory. Następnie, przy każdym pytaniu, osadzasz pytanie, pobierasz najbliższe znaczeniowo fragmenty i przekazujesz je do LLM jako kontekst, tak aby odpowiedź pochodziła z Twoich danych, a nie z pamięci treningowej modelu. W sgcWebSockets cały potok to trzy komponenty: TsgcAIOpenAIEmbeddings do zamiany tekstu na wektory, TsgcAIDatabaseVectorFile lub TsgcAIDatabaseVectorPinecone do ich przechowywania i przeszukiwania oraz TsgcHTTP_API_OpenAI do napisania ostatecznej, ugruntowanej odpowiedzi.
Ogólnego przeznaczenia model czatu wie wiele o świecie i nic o instrukcji Twojego produktu, Twoich zgłoszeniach serwisowych czy wewnętrznym raporcie z zeszłego kwartału. Zapytaj go o nie, a albo odmówi, albo, co gorsza, wymyśli coś prawdopodobnie brzmiącego. RAG rozwiązuje ten problem bez ponownego trenowania czegokolwiek: zostawiasz model bez zmian i podajesz mu odpowiednie fragmenty z Twojego własnego korpusu w momencie zadania pytania. Poniżej cała pętla w Delphi, od początku do końca, z prawdziwymi nazwami komponentów.
Co RAG właściwie robi
Osadzenie (embedding) to lista liczb, która oddaje znaczenie fragmentu tekstu. Dwa fragmenty na ten sam temat lądują blisko siebie w tej liczbowej przestrzeni, nawet gdy nie mają wspólnych słów kluczowych. Baza wektorowa przechowuje te liczby i dla zadanego wektora zapytania zwraca najbliższe wpisy uszeregowane według podobieństwa. RAG łączy to w jeden ciąg:
| Etap | Co się dzieje | Element sgcWebSockets |
|---|---|---|
| 1. Wczytanie (raz) | Podziel dokumenty na fragmenty, osadź każdy fragment, zapisz wektory | TsgcAIOpenAIEmbeddings.CreateEmbeddingsFromFile |
| 2. Przechowywanie | Trzymaj wektory w lokalnym pliku lub w indeksie w chmurze | TsgcAIDatabaseVectorFile · TsgcAIDatabaseVectorPinecone |
| 3. Pobieranie (na pytanie) | Osadź pytanie, znajdź najbliższe fragmenty | GetEmbedding → QueryData |
| 4. Odpowiedź | Umieść fragmenty w prompcie, zapytaj model | TsgcHTTP_API_OpenAI._CreateChatCompletion |
Kroki 1 i 2 uruchamiają się, gdy zmieniają się Twoje dane. Kroki 3 i 4 uruchamiają się przy każdym pytaniu użytkownika. Zbudujmy każdy z nich.
Krok 1 — osadź swoje dokumenty
Utwórz TsgcAIOpenAIEmbeddings, podaj mu klucz OpenAI, ustaw jego właściwość Database na magazyn wektorów i wywołaj CreateEmbeddingsFromFile. To jedno wywołanie odczytuje plik, dzieli go na fragmenty (sterowane przez EmbeddingsOptions.ChunkSize), osadza każdy fragment i zapisuje wektory do magazynu za pomocą sekwencji BeginAddData / AddData / EndAddData za Ciebie.
uses
sgcAI, sgcAI_OpenAI_Embeddings,
sgcAI_DB_Vector, sgcAI_DB_Vector_File, sgcAI_DB_Vector_Pinecone;
var
Embeddings: TsgcAIOpenAIEmbeddings;
DBFile: TsgcAIDatabaseVectorFile;
begin
Embeddings := TsgcAIOpenAIEmbeddings.Create(nil);
Embeddings.OpenAIOptions.ApiKey := 'sk-...';
// local, file-based vector store
DBFile := TsgcAIDatabaseVectorFile.Create(nil);
DBFile.VectorFileOptions.InputFilename := 'corpus.sgcif';
DBFile.VectorFileOptions.VectorFilename := 'corpus.sgcvf';
Embeddings.Database := DBFile;
Embeddings.CreateEmbeddingsFromFile('docs.txt');
end;
To cały krok wczytywania. Uruchom go raz lub za każdym razem, gdy zmieniają się Twoje dokumenty. Domyślny model osadzania to text-embedding-3-small; zmień go przez EmbeddingsOptions.Model, jeśli potrzebujesz innego. Więcej szczegółów znajdziesz na stronie komponentu Embeddings.
Krok 2 — wybierz, gdzie mają być wektory
Oba zaplecza dziedziczą po tym samym komponencie bazowym, TsgcAIDatabaseVector, więc są wymienne: zamień jedno na drugie, a Twój kod wczytywania i odpytywania się nie zmieni. Jedyna różnica to miejsce, w którym znajdują się dane.
Dla aplikacji desktopowej, narzędzia działającego offline lub mniejszego korpusu TsgcAIDatabaseVectorFile trzyma wszystko w lokalnym pliku, bez żadnej usługi zewnętrznej. Gdy indeks jest duży, musi być współdzielony między procesami lub użytkownikami albo musi skalować się poza jedną maszynę, przełącz się na TsgcAIDatabaseVectorPinecone, które wysyła (upsert) każdy fragment przez zarządzane REST API Pinecone:
var
DBPinecone: TsgcAIDatabaseVectorPinecone;
begin
DBPinecone := TsgcAIDatabaseVectorPinecone.Create(nil);
DBPinecone.PineconeOptions.ApiKey := 'pc-...';
DBPinecone.PineconeIndexOptions.IndexName := 'sgc-embeddings';
Embeddings.Database := DBPinecone;
Embeddings.CreateEmbeddingsFromFile('docs.txt');
end;
Zauważ, że linia wczytywania jest identyczna jak w kroku 1. O to właśnie chodzi we wspólnej klasie bazowej. Zobacz stronę baz wektorowych dla zaplecza plikowego oraz stronę Pinecone dla wersji chmurowej.
Krok 3 — pobierz i odpowiedz
Teraz ścieżka dla pojedynczego pytania. Osadź pytanie użytkownika i znajdź najbliższe przechowywane fragmenty w jednym wywołaniu: GetEmbedding osadza tekst i przepuszcza go przez metodę bazy QueryData, zwracając najbardziej trafne fragmenty z Twojego korpusu. Te fragmenty są Twoim kontekstem. Połącz je z pytaniem i wyślij całość do modelu czatu:
var
Question, Context, Prompt, Answer: string;
OpenAI: TsgcHTTP_API_OpenAI;
begin
Question := 'How do I enable the WatchDog reconnect?';
// retrieve the closest chunks from your own data
Context := Embeddings.GetEmbedding(Question, '');
// build a grounded prompt
Prompt :=
'Answer the question using only the context below.' + sLineBreak +
'If the context does not contain the answer, say you do not know.' +
sLineBreak + sLineBreak +
'Context:' + sLineBreak + Context + sLineBreak + sLineBreak +
'Question: ' + Question;
// ask the model
OpenAI := TsgcHTTP_API_OpenAI.Create(nil);
OpenAI.OpenAIOptions.ApiKey := 'sk-...';
Answer := OpenAI._CreateChatCompletion('gpt-4o-mini', Prompt);
Memo1.Lines.Text := Answer;
end;
To jest RAG. Model nigdy nie widział Twoich dokumentów podczas treningu, a mimo to odpowiada na ich podstawie, ponieważ to Ty postawiłeś przed nim odpowiednie fragmenty w momencie żądania. Zmień korpus, a odpowiedzi zmienią się wraz z nim, bez żadnego dostrajania. Instrukcja, by odmówić, gdy kontekst jest pusty, jest tym, co utrzymuje model w szczerości zamiast zgadywania.
Lokalnie lub w chmurze, ten sam kod
Jeden szczegół warto powtórzyć: wybór między magazynem plikowym a Pinecone jest odwracalny. Ponieważ TsgcAIDatabaseVectorFile i TsgcAIDatabaseVectorPinecone dzielą bazę TsgcAIDatabaseVector, możesz prototypować na lokalnym pliku (zerowa infrastruktura, działa offline) i przejść na Pinecone później, podmieniając komponent przypisany do Embeddings.Database. Nic w Twoim kodzie wczytywania ani odpytywania się nie zmienia. To samo dotyczy LLM na końcu: _CreateChatCompletion na TsgcHTTP_API_OpenAI można podmienić na komponent Anthropic lub Gemini, jeśli wolisz, by ostateczną odpowiedź napisał inny model.
Uwaga o podziale na fragmenty i jakości
Jakość pobierania zależy od tego, jak podzielone są Twoje dokumenty. Mniejsze fragmenty czynią dopasowania precyzyjniejszymi, ale mogą tracić kontekst; większe fragmenty zachowują kontekst, lecz rozmywają dopasowanie. EmbeddingsOptions.ChunkSize steruje tym dla CreateEmbeddingsFromFile, więc warto go dostroić do Twojego materiału. Dla większej kontroli możesz też osadzać pojedyncze łańcuchy za pomocą CreateEmbeddings i samodzielnie kształtować fragmenty przed wczytaniem.
Jak zacząć
Wszystkie trzy komponenty są dostarczane w sgcWebSockets. Pobierz bezpłatną wersję próbną, upuść komponenty osadzania i magazynu wektorów na formularzu, wskaż im plik tekstowy, a będziesz mieć działającą pętlę RAG w znacznie mniej niż stu liniach. Przejrzyj pełen zestaw klocków AI w centrum komponentów AI i LLM.
Pytania o zastosowanie tego do własnego korpusu? Skontaktuj się z nami — odpowiedź otrzymasz od ludzi, którzy napisali ten kod.
