RAG en Delphi : ancrez un LLM dans vos propres documents

· Composants

Réponse rapide : la génération augmentée par récupération (RAG) réunit deux idées. D'abord, vous intégrez vos documents une seule fois et stockez les vecteurs obtenus. Ensuite, à chaque question, vous intégrez la question, récupérez les segments les plus proches par le sens, et les transmettez au LLM comme contexte afin que la réponse provienne de vos données plutôt que de la mémoire d'entraînement du modèle. Dans sgcWebSockets, tout le pipeline tient en trois composants : TsgcAIOpenAIEmbeddings pour transformer du texte en vecteurs, TsgcAIDatabaseVectorFile ou TsgcAIDatabaseVectorPinecone pour les stocker et les rechercher, et TsgcHTTP_API_OpenAI pour rédiger la réponse finale ancrée.

Un modèle de discussion généraliste connaît beaucoup de choses sur le monde et rien sur votre manuel produit, vos tickets de support ou le rapport interne du dernier trimestre. Posez-lui des questions à ce sujet et il refusera, ou pire, inventera quelque chose de plausible. La RAG corrige cela sans réentraîner quoi que ce soit : vous conservez le modèle tel quel et lui fournissez les bons passages de votre propre corpus au moment de la question. Voici la boucle complète en Delphi, de bout en bout, avec les vrais noms de composants.

Ce que fait réellement la RAG

Une intégration (embedding) est une liste de nombres qui capture le sens d'un fragment de texte. Deux passages portant sur le même sujet se retrouvent proches l'un de l'autre dans cet espace numérique, même lorsqu'ils ne partagent aucun mot-clé. Une base de données vectorielle stocke ces nombres et, à partir d'un vecteur de requête, renvoie les entrées les plus proches classées par similarité. La RAG enchaîne ces étapes :

ÉtapeCe qui se passeÉlément sgcWebSockets
1. Ingestion (une fois)Découper les documents en segments, intégrer chaque segment, stocker les vecteursTsgcAIOpenAIEmbeddings.CreateEmbeddingsFromFile
2. StockageConserver les vecteurs dans un fichier local ou un index cloudTsgcAIDatabaseVectorFile · TsgcAIDatabaseVectorPinecone
3. Récupération (par question)Intégrer la question, trouver les segments les plus prochesGetEmbeddingQueryData
4. RéponsePlacer les segments dans le prompt, interroger le modèleTsgcHTTP_API_OpenAI._CreateChatCompletion

Les étapes 1 et 2 s'exécutent lorsque vos données changent. Les étapes 3 et 4 s'exécutent à chaque question de l'utilisateur. Construisons chacune d'elles.

Étape 1 — intégrez vos documents

Créez un TsgcAIOpenAIEmbeddings, fournissez-lui une clé OpenAI, pointez sa propriété Database vers un magasin de vecteurs, et appelez CreateEmbeddingsFromFile. Ce seul appel lit le fichier, le découpe en segments (contrôlés par EmbeddingsOptions.ChunkSize), intègre chaque segment, et écrit les vecteurs dans le magasin via la séquence BeginAddData / AddData / EndAddData à votre place.

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;

C'est toute l'étape d'ingestion. Exécutez-la une fois, ou chaque fois que vos documents changent. Le modèle d'intégration par défaut est text-embedding-3-small ; changez-le via EmbeddingsOptions.Model si vous en avez besoin d'un autre. Vous trouverez plus de détails sur la page du composant Embeddings.

Étape 2 — choisissez où résident les vecteurs

Les deux backends descendent du même composant de base, TsgcAIDatabaseVector, ils sont donc interchangeables : remplacez l'un par l'autre et votre code d'ingestion et de requête ne change pas. La seule différence est l'endroit où résident les données.

Pour une application de bureau, un outil hors ligne ou un corpus plus modeste, TsgcAIDatabaseVectorFile conserve tout dans un fichier local sans service externe. Lorsque l'index est volumineux, qu'il doit être partagé entre plusieurs processus ou utilisateurs, ou qu'il doit dépasser l'échelle d'une seule machine, passez à TsgcAIDatabaseVectorPinecone, qui insère chaque segment via l'API REST managée de 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;

Remarquez que la ligne d'ingestion est identique à celle de l'étape 1. C'est tout l'intérêt de la classe de base partagée. Consultez la page Bases de données vectorielles pour le backend fichier et la page Pinecone pour le backend cloud.

Étape 3 — récupérez et répondez

Voici maintenant le parcours par question. Intégrez la question de l'utilisateur et trouvez les segments stockés les plus proches en un seul appel : GetEmbedding intègre le texte et l'exécute à travers le QueryData de la base de données, renvoyant les passages les plus pertinents de votre corpus. Ces passages constituent votre contexte. Concaténez-les avec la question et envoyez l'ensemble au modèle de discussion :

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;

Voilà la RAG. Le modèle n'a jamais vu vos documents pendant l'entraînement, et pourtant il y répond, parce que vous lui placez les passages pertinents sous les yeux au moment de la requête. Modifiez le corpus et les réponses changent avec lui, sans aucun ajustement fin. L'instruction de refuser lorsque le contexte est vide est ce qui maintient le modèle honnête au lieu de le laisser deviner.

Local ou cloud, le même code

Un détail mérite d'être répété : le choix entre le magasin fichier et Pinecone est réversible. Comme TsgcAIDatabaseVectorFile et TsgcAIDatabaseVectorPinecone partagent la base TsgcAIDatabaseVector, vous pouvez prototyper avec le fichier local (zéro infrastructure, fonctionne hors ligne) et passer à Pinecone plus tard en remplaçant le composant que vous affectez à Embeddings.Database. Rien dans votre code d'ingestion ou de requête ne change. Il en va de même pour le LLM à la fin : _CreateChatCompletion sur TsgcHTTP_API_OpenAI peut être remplacé par le composant Anthropic ou Gemini si vous préférez un autre modèle pour rédiger la réponse finale.

Une note sur le découpage et la qualité

La qualité de la récupération dépend de la façon dont vos documents sont découpés. Des segments plus petits rendent les correspondances plus précises mais peuvent perdre du contexte ; des segments plus grands conservent le contexte mais diluent la correspondance. EmbeddingsOptions.ChunkSize contrôle cela pour CreateEmbeddingsFromFile, il vaut donc la peine de l'ajuster à votre matériel. Pour un contrôle plus fin, vous pouvez aussi intégrer des chaînes individuelles avec CreateEmbeddings et façonner les segments vous-même avant l'ingestion.

Pour commencer

Les trois composants sont fournis avec sgcWebSockets. Récupérez la version d'essai gratuite, déposez les composants d'intégration et de magasin de vecteurs sur une fiche, pointez-les vers un fichier texte, et vous aurez une boucle RAG fonctionnelle en bien moins de cent lignes. Parcourez l'ensemble complet des briques d'IA sur le hub des composants IA et LLM.

Des questions sur l'application à votre propre corpus ? Contactez-nous — vous recevrez une réponse des personnes qui ont écrit le code.

Sur le même thème