Snel antwoord: Retrieval-Augmented Generation (RAG) is een combinatie van twee ideeën. Eerst embed je je documenten één keer en sla je de resulterende vectoren op. Daarna embed je bij elke vraag de vraag, haal je de dichtstbijzijnde fragmenten op basis van betekenis op, en geef je ze als context aan de LLM zodat het antwoord uit je data komt in plaats van uit het trainingsgeheugen van het model. In sgcWebSockets bestaat de hele pijplijn uit drie componenten: TsgcAIOpenAIEmbeddings om tekst in vectoren om te zetten, TsgcAIDatabaseVectorFile of TsgcAIDatabaseVectorPinecone om ze op te slaan en te doorzoeken, en TsgcHTTP_API_OpenAI om het uiteindelijke, verankerde antwoord te schrijven.
Een algemeen chatmodel weet veel over de wereld en niets over jouw producthandleiding, jouw supporttickets of het interne rapport van afgelopen kwartaal. Vraag het daarnaar en het weigert ofwel, of erger nog, verzint iets plausibels. RAG lost dat op zonder iets opnieuw te trainen: je houdt het model zoals het is en geeft het op het moment van de vraag de juiste passages uit je eigen corpus. Hieronder staat de volledige lus in Delphi, van begin tot eind, met echte componentnamen.
Wat RAG eigenlijk doet
Een embedding is een lijst getallen die de betekenis van een stuk tekst vastlegt. Twee passages over hetzelfde onderwerp komen dicht bij elkaar te liggen in die numerieke ruimte, zelfs als ze geen trefwoorden delen. Een vectordatabase slaat die getallen op en geeft, gegeven een queryvector, de dichtstbijzijnde items terug, gerangschikt op gelijkenis. RAG rijgt deze aan elkaar:
| Fase | Wat er gebeurt | sgcWebSockets-onderdeel |
|---|---|---|
| 1. Inlezen (eenmalig) | Splits documenten in fragmenten, embed elk fragment, sla de vectoren op | TsgcAIOpenAIEmbeddings.CreateEmbeddingsFromFile |
| 2. Opslaan | Bewaar vectoren in een lokaal bestand of een cloudindex | TsgcAIDatabaseVectorFile · TsgcAIDatabaseVectorPinecone |
| 3. Ophalen (per vraag) | Embed de vraag, vind de dichtstbijzijnde fragmenten | GetEmbedding → QueryData |
| 4. Antwoorden | Zet de fragmenten in de prompt, vraag het model | TsgcHTTP_API_OpenAI._CreateChatCompletion |
Stap 1 en 2 draaien wanneer je data verandert. Stap 3 en 4 draaien bij elke gebruikersvraag. Laten we ze stuk voor stuk bouwen.
Stap 1 — embed je documenten
Maak een TsgcAIOpenAIEmbeddings aan, geef het een OpenAI-sleutel, wijs zijn Database-eigenschap aan een vectoropslag toe, en roep CreateEmbeddingsFromFile aan. Die ene aanroep leest het bestand, splitst het in fragmenten (geregeld door EmbeddingsOptions.ChunkSize), embed elk fragment, en schrijft de vectoren in de opslag via de BeginAddData / AddData / EndAddData-reeks voor je.
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;
Dat is de hele inleesstap. Voer hem één keer uit, of telkens wanneer je documenten veranderen. Het standaard embeddingmodel is text-embedding-3-small; wijzig het via EmbeddingsOptions.Model als je een ander nodig hebt. Meer details vind je op de pagina van het Embeddings-component.
Stap 2 — kies waar de vectoren wonen
Beide backends stammen af van hetzelfde basiscomponent, TsgcAIDatabaseVector, dus ze zijn uitwisselbaar: vervang de een door de ander en je inlees- en querycode verandert niet. Het enige verschil is waar de data zich bevindt.
Voor een desktopapp, een offline tool of een kleiner corpus houdt TsgcAIDatabaseVectorFile alles in een lokaal bestand zonder externe service. Wanneer de index groot is, gedeeld moet worden over processen of gebruikers, of verder moet schalen dan één machine, stap dan over op TsgcAIDatabaseVectorPinecone, die elk fragment upsert via de beheerde Pinecone REST API:
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;
Merk op dat de inleesregel identiek is aan die van stap 1. Dat is precies het doel van de gedeelde basisklasse. Zie de pagina Vector Databases voor de bestandsbackend en de Pinecone-pagina voor de cloudvariant.
Stap 3 — ophalen en antwoorden
Nu het pad per vraag. Embed de vraag van de gebruiker en vind de dichtstbijzijnde opgeslagen fragmenten in één aanroep: GetEmbedding embed de tekst en voert deze door de QueryData van de database, waarbij de meest relevante passages uit je corpus worden teruggegeven. Die passages zijn je context. Voeg ze samen met de vraag en stuur het geheel naar het chatmodel:
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;
Dat is RAG. Het model heeft je documenten nooit tijdens de training gezien, en toch antwoordt het ervanuit, omdat je de relevante passages voor zijn neus legt op het moment van de aanvraag. Verander het corpus en de antwoorden veranderen mee, zonder fine-tuning. De instructie om te weigeren wanneer de context leeg is, is wat het model eerlijk houdt in plaats van het te laten gokken.
Lokaal of cloud, dezelfde code
Eén detail dat herhaling waard is: de keuze tussen de bestandsopslag en Pinecone is omkeerbaar. Omdat TsgcAIDatabaseVectorFile en TsgcAIDatabaseVectorPinecone de basis TsgcAIDatabaseVector delen, kun je prototypen met het lokale bestand (geen infrastructuur, werkt offline) en later overstappen op Pinecone door het component dat je aan Embeddings.Database toewijst te verwisselen. Niets in je inlees- of querycode verandert. Hetzelfde geldt voor de LLM aan het eind: _CreateChatCompletion op TsgcHTTP_API_OpenAI kan worden vervangen door het Anthropic- of Gemini-component als je liever een ander model het uiteindelijke antwoord laat schrijven.
Een opmerking over chunking en kwaliteit
De kwaliteit van het ophalen hangt af van hoe je documenten worden gesplitst. Kleinere fragmenten maken matches preciezer maar kunnen context verliezen; grotere fragmenten behouden context maar verwateren de match. EmbeddingsOptions.ChunkSize regelt dit voor CreateEmbeddingsFromFile, dus het is de moeite waard om het op je materiaal af te stemmen. Voor fijnere controle kun je ook losse strings embedden met CreateEmbeddings en de fragmenten zelf vormgeven voordat je ze inleest.
Aan de slag
Alle drie de componenten zitten in sgcWebSockets. Pak de gratis proefversie, sleep de embeddings- en vectoropslagcomponenten op een formulier, wijs ze naar een tekstbestand, en je hebt een werkende RAG-lus in ruim onder de honderd regels. Bekijk de volledige set AI-bouwstenen op de hub met AI & LLM-componenten.
Vragen over het toepassen hiervan op je eigen corpus? Neem contact op — je krijgt antwoord van de mensen die de code geschreven hebben.
