Kurze Antwort: Retrieval-Augmented Generation (RAG) sind zwei zusammengefügte Ideen. Zuerst betten Sie Ihre Dokumente einmal ein und speichern die resultierenden Vektoren. Anschließend betten Sie bei jeder Frage die Frage ein, rufen die nach Bedeutung ähnlichsten Chunks ab und übergeben sie dem LLM als Kontext, sodass die Antwort aus Ihren Daten kommt statt aus dem Trainingsgedächtnis des Modells. In sgcWebSockets besteht die gesamte Pipeline aus drei Komponenten: TsgcAIOpenAIEmbeddings, um Text in Vektoren umzuwandeln, TsgcAIDatabaseVectorFile oder TsgcAIDatabaseVectorPinecone, um sie zu speichern und zu durchsuchen, und TsgcHTTP_API_OpenAI, um die endgültige verankerte Antwort zu schreiben.
Ein allgemeines Chat-Modell weiß viel über die Welt und nichts über Ihr Produkthandbuch, Ihre Support-Tickets oder den internen Bericht des letzten Quartals. Fragen Sie es danach, wird es entweder verweigern oder, schlimmer noch, etwas Plausibles erfinden. RAG behebt das, ohne irgendetwas neu zu trainieren: Sie behalten das Modell unverändert und füttern es zur Fragezeit mit den passenden Passagen aus Ihrem eigenen Korpus. Nachfolgend sehen Sie die gesamte Schleife in Delphi, von Anfang bis Ende, mit echten Komponentennamen.
Was RAG tatsächlich tut
Ein Embedding ist eine Liste von Zahlen, die die Bedeutung eines Textstücks erfasst. Zwei Passagen zum selben Thema landen in diesem numerischen Raum nahe beieinander, selbst wenn sie keine Schlüsselwörter teilen. Eine Vektordatenbank speichert diese Zahlen und gibt zu einem Abfragevektor die nächstgelegenen Einträge nach Ähnlichkeit geordnet zurück. RAG verkettet diese Schritte:
| Phase | Was geschieht | sgcWebSockets-Baustein |
|---|---|---|
| 1. Einlesen (einmalig) | Dokumente in Chunks aufteilen, jeden Chunk einbetten, die Vektoren speichern | TsgcAIOpenAIEmbeddings.CreateEmbeddingsFromFile |
| 2. Speichern | Vektoren in einer lokalen Datei oder einem Cloud-Index halten | TsgcAIDatabaseVectorFile · TsgcAIDatabaseVectorPinecone |
| 3. Abrufen (pro Frage) | Die Frage einbetten, die ähnlichsten Chunks finden | GetEmbedding → QueryData |
| 4. Antworten | Die Chunks in den Prompt setzen, das Modell fragen | TsgcHTTP_API_OpenAI._CreateChatCompletion |
Die Schritte 1 und 2 laufen, wenn sich Ihre Daten ändern. Die Schritte 3 und 4 laufen bei jeder Benutzerfrage. Bauen wir jeden einzelnen auf.
Schritt 1 — betten Sie Ihre Dokumente ein
Erstellen Sie ein TsgcAIOpenAIEmbeddings, geben Sie ihm einen OpenAI-Schlüssel, richten Sie seine Database-Eigenschaft auf einen Vektorspeicher und rufen Sie CreateEmbeddingsFromFile auf. Dieser eine Aufruf liest die Datei, teilt sie in Chunks auf (gesteuert durch EmbeddingsOptions.ChunkSize), bettet jeden Chunk ein und schreibt die Vektoren über die Sequenz BeginAddData / AddData / EndAddData für Sie in den Speicher.
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;
Das ist der gesamte Einlese-Schritt. Führen Sie ihn einmal aus oder immer dann, wenn sich Ihre Dokumente ändern. Das Standard-Embedding-Modell ist text-embedding-3-small; ändern Sie es über EmbeddingsOptions.Model, falls Sie ein anderes benötigen. Mehr Details finden Sie auf der Seite zur Embeddings-Komponente.
Schritt 2 — wählen Sie, wo die Vektoren liegen
Beide Backends stammen von derselben Basiskomponente TsgcAIDatabaseVector ab, sind also austauschbar: Tauschen Sie eines gegen das andere, und Ihr Einlese- und Abfragecode ändert sich nicht. Der einzige Unterschied liegt darin, wo die Daten liegen.
Für eine Desktop-Anwendung, ein Offline-Tool oder einen kleineren Korpus hält TsgcAIDatabaseVectorFile alles in einer lokalen Datei ohne externen Dienst. Wenn der Index groß ist, über Prozesse oder Benutzer hinweg geteilt werden muss oder über eine Maschine hinaus skalieren soll, wechseln Sie zu TsgcAIDatabaseVectorPinecone, das jeden Chunk über die verwaltete Pinecone-REST-API einfügt:
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;
Beachten Sie, dass die Einlese-Zeile mit der aus Schritt 1 identisch ist. Genau das ist der Sinn der gemeinsamen Basisklasse. Siehe die Seite zu Vektordatenbanken für das Datei-Backend und die Pinecone-Seite für das Cloud-Backend.
Schritt 3 — abrufen und antworten
Nun der Pfad pro Frage. Betten Sie die Frage des Benutzers ein und finden Sie die ähnlichsten gespeicherten Chunks in einem Aufruf: GetEmbedding bettet den Text ein und führt ihn durch QueryData der Datenbank, wodurch die relevantesten Passagen aus Ihrem Korpus zurückgegeben werden. Diese Passagen sind Ihr Kontext. Verketten Sie sie mit der Frage und senden Sie das Ganze an das Chat-Modell:
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;
Das ist RAG. Das Modell hat Ihre Dokumente während des Trainings nie gesehen, antwortet aber dennoch aus ihnen, weil Sie ihm die relevanten Passagen zur Anfragezeit vorlegen. Ändern Sie den Korpus, und die Antworten ändern sich mit ihm, ganz ohne Fine-Tuning. Die Anweisung, bei leerem Kontext zu verweigern, hält das Modell ehrlich, statt es raten zu lassen.
Lokal oder Cloud, derselbe Code
Ein Detail ist eine Wiederholung wert: Die Wahl zwischen dem Dateispeicher und Pinecone ist reversibel. Da TsgcAIDatabaseVectorFile und TsgcAIDatabaseVectorPinecone die Basis TsgcAIDatabaseVector teilen, können Sie gegen die lokale Datei prototypisieren (keine Infrastruktur, läuft offline) und später zu Pinecone wechseln, indem Sie die Komponente austauschen, die Sie Embeddings.Database zuweisen. Nichts in Ihrem Einlese- oder Abfragecode ändert sich. Dasselbe gilt für das LLM am Ende: _CreateChatCompletion auf TsgcHTTP_API_OpenAI kann gegen die Anthropic- oder Gemini-Komponente getauscht werden, falls Sie ein anderes Modell für die endgültige Antwort bevorzugen.
Eine Anmerkung zu Chunking und Qualität
Die Abrufqualität hängt davon ab, wie Ihre Dokumente aufgeteilt werden. Kleinere Chunks machen Treffer präziser, können aber Kontext verlieren; größere Chunks bewahren Kontext, verwässern aber den Treffer. EmbeddingsOptions.ChunkSize steuert dies für CreateEmbeddingsFromFile, daher lohnt es sich, ihn auf Ihr Material abzustimmen. Für feinere Kontrolle können Sie auch einzelne Strings mit CreateEmbeddings einbetten und die Chunks selbst formen, bevor Sie sie einlesen.
Erste Schritte
Alle drei Komponenten sind in sgcWebSockets enthalten. Holen Sie sich die kostenlose Testversion, legen Sie die Embeddings- und Vektorspeicher-Komponenten auf ein Formular, richten Sie sie auf eine Textdatei, und Sie haben eine funktionierende RAG-Schleife mit deutlich unter hundert Zeilen. Durchsuchen Sie den vollständigen Satz an KI-Bausteinen im Hub für KI- & LLM-Komponenten.
Fragen zur Anwendung auf Ihren eigenen Korpus? Kontaktieren Sie uns — Sie erhalten eine Antwort von den Leuten, die den Code geschrieben haben.
