RAG in Delphi: ancorare un LLM ai tuoi documenti

· Componenti

Risposta rapida: la Retrieval-Augmented Generation (RAG) è l'unione di due idee. Prima generi una sola volta gli embedding dei tuoi documenti e archivi i vettori risultanti. Poi, a ogni domanda, generi l'embedding della domanda, recuperi i chunk più vicini per significato e li passi all'LLM come contesto, così la risposta proviene dai tuoi dati anziché dalla memoria di addestramento del modello. In sgcWebSockets l'intera pipeline si riduce a tre componenti: TsgcAIOpenAIEmbeddings per trasformare il testo in vettori, TsgcAIDatabaseVectorFile o TsgcAIDatabaseVectorPinecone per archiviarli e cercarli, e TsgcHTTP_API_OpenAI per scrivere la risposta finale ancorata.

Un modello di chat generico sa molto del mondo e nulla del tuo manuale di prodotto, dei tuoi ticket di assistenza o del report interno dell'ultimo trimestre. Chiediglielo e o si rifiuterà o, peggio, inventerà qualcosa di plausibile. La RAG risolve questo problema senza riaddestrare nulla: mantieni il modello così com'è e gli fornisci i passaggi giusti dal tuo corpus al momento della domanda. Di seguito trovi l'intero ciclo in Delphi, dall'inizio alla fine, con i nomi reali dei componenti.

Cosa fa realmente la RAG

Un embedding è un elenco di numeri che cattura il significato di un frammento di testo. Due passaggi sullo stesso argomento finiscono vicini in quello spazio numerico, anche quando non condividono alcuna parola chiave. Un database vettoriale archivia quei numeri e, data una query vettoriale, restituisce le voci più vicine ordinate per somiglianza. La RAG mette insieme tutto questo:

FaseCosa accadeComponente sgcWebSockets
1. Ingestione (una volta)Suddividi i documenti in chunk, genera l'embedding di ogni chunk, archivia i vettoriTsgcAIOpenAIEmbeddings.CreateEmbeddingsFromFile
2. ArchiviazioneConserva i vettori in un file locale o in un indice cloudTsgcAIDatabaseVectorFile · TsgcAIDatabaseVectorPinecone
3. Recupero (per ogni domanda)Genera l'embedding della domanda, trova i chunk più viciniGetEmbeddingQueryData
4. RispostaInserisci i chunk nel prompt, interroga il modelloTsgcHTTP_API_OpenAI._CreateChatCompletion

I passaggi 1 e 2 vengono eseguiti quando i tuoi dati cambiano. I passaggi 3 e 4 vengono eseguiti a ogni domanda dell'utente. Costruiamoli uno per uno.

Passaggio 1 — genera gli embedding dei tuoi documenti

Crea un TsgcAIOpenAIEmbeddings, fornisci una chiave OpenAI, punta la sua proprietà Database su uno store vettoriale e chiama CreateEmbeddingsFromFile. Quell'unica chiamata legge il file, lo suddivide in chunk (controllati da EmbeddingsOptions.ChunkSize), genera l'embedding di ogni chunk e scrive i vettori nello store tramite la sequenza BeginAddData / AddData / EndAddData al posto tuo.

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;

Questo è l'intero passaggio di ingestione. Eseguilo una volta, o ogni volta che i tuoi documenti cambiano. Il modello di embedding predefinito è text-embedding-3-small; cambialo tramite EmbeddingsOptions.Model se te ne serve uno diverso. Trovi maggiori dettagli nella pagina del componente Embeddings.

Passaggio 2 — scegli dove risiedono i vettori

Entrambi i backend discendono dallo stesso componente base, TsgcAIDatabaseVector, quindi sono intercambiabili: sostituisci l'uno con l'altro e il tuo codice di ingestione e di query non cambia. L'unica differenza è dove risiedono i dati.

Per un'app desktop, uno strumento offline o un corpus più piccolo, TsgcAIDatabaseVectorFile mantiene tutto in un file locale senza alcun servizio esterno. Quando l'indice è grande, deve essere condiviso tra processi o utenti, oppure deve scalare oltre una singola macchina, passa a TsgcAIDatabaseVectorPinecone, che fa l'upsert di ogni chunk tramite l'API REST gestita di 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;

Nota che la riga di ingestione è identica a quella del Passaggio 1. È proprio questo lo scopo della classe base condivisa. Consulta la pagina Vector Databases per il backend su file e la pagina Pinecone per quello cloud.

Passaggio 3 — recupera e rispondi

Ora il percorso per ogni domanda. Genera l'embedding della domanda dell'utente e trova i chunk archiviati più vicini in un'unica chiamata: GetEmbedding genera l'embedding del testo e lo esegue attraverso il QueryData del database, restituendo i passaggi più pertinenti dal tuo corpus. Quei passaggi sono il tuo contesto. Concatenali con la domanda e invia il tutto al modello di chat:

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;

Questa è la RAG. Il modello non ha mai visto i tuoi documenti durante l'addestramento, eppure risponde a partire da essi, perché gli metti davanti i passaggi pertinenti al momento della richiesta. Cambia il corpus e le risposte cambiano con esso, senza alcun fine-tuning. L'istruzione di rifiutare quando il contesto è vuoto è ciò che mantiene il modello onesto invece di farlo tirare a indovinare.

In locale o nel cloud, stesso codice

Un dettaglio che vale la pena ribadire: la scelta tra lo store su file e Pinecone è reversibile. Poiché TsgcAIDatabaseVectorFile e TsgcAIDatabaseVectorPinecone condividono la base TsgcAIDatabaseVector, puoi creare un prototipo sul file locale (zero infrastruttura, funziona offline) e passare a Pinecone in seguito sostituendo il componente che assegni a Embeddings.Database. Nulla nel tuo codice di ingestione o di query cambia. Lo stesso vale per l'LLM alla fine: _CreateChatCompletion su TsgcHTTP_API_OpenAI può essere sostituito con il componente Anthropic o Gemini se preferisci un modello diverso per scrivere la risposta finale.

Una nota su chunking e qualità

La qualità del recupero dipende da come vengono suddivisi i tuoi documenti. Chunk più piccoli rendono le corrispondenze più precise ma possono perdere contesto; chunk più grandi mantengono il contesto ma diluiscono la corrispondenza. EmbeddingsOptions.ChunkSize controlla questo per CreateEmbeddingsFromFile, quindi vale la pena regolarlo in base al tuo materiale. Per un controllo più fine puoi anche generare l'embedding di singole stringhe con CreateEmbeddings e modellare tu stesso i chunk prima dell'ingestione.

Come iniziare

Tutti e tre i componenti sono inclusi in sgcWebSockets. Scarica la prova gratuita, trascina i componenti di embedding e di store vettoriale su una form, puntali su un file di testo e avrai un ciclo RAG funzionante in ben meno di cento righe. Esplora l'intera gamma di blocchi costitutivi per l'AI nell'hub dei componenti AI & LLM.

Domande sull'applicazione di tutto questo al tuo corpus? Contattaci — riceverai una risposta dalle persone che hanno scritto il codice.

Correlati