RAG en Delphi: fundamenta un LLM en tus propios documentos

· Componentes

Respuesta rápida: Retrieval-Augmented Generation (RAG) son dos ideas unidas. Primero, incrustas tus documentos una vez y almacenas los vectores resultantes. Después, en cada pregunta, incrustas la pregunta, recuperas los fragmentos más cercanos por significado y se los entregas al LLM como contexto, de modo que la respuesta provenga de tus datos en lugar de la memoria de entrenamiento del modelo. En sgcWebSockets todo el flujo son tres componentes: TsgcAIOpenAIEmbeddings para convertir texto en vectores, TsgcAIDatabaseVectorFile o TsgcAIDatabaseVectorPinecone para almacenarlos y buscarlos, y TsgcHTTP_API_OpenAI para redactar la respuesta final fundamentada.

Un modelo de chat de propósito general sabe mucho sobre el mundo y nada sobre el manual de tu producto, tus tickets de soporte o el informe interno del último trimestre. Pregúntale sobre eso y o se negará o, peor aún, inventará algo plausible. RAG soluciona eso sin reentrenar nada: dejas el modelo tal cual y le entregas los pasajes adecuados de tu propio corpus en el momento de la pregunta. A continuación está el bucle completo en Delphi, de principio a fin, con nombres de componentes reales.

Qué hace realmente RAG

Una incrustación (embedding) es una lista de números que captura el significado de un fragmento de texto. Dos pasajes sobre el mismo tema quedan cerca el uno del otro en ese espacio numérico, aunque no compartan ninguna palabra clave. Una base de datos vectorial almacena esos números y, dado un vector de consulta, devuelve las entradas más cercanas ordenadas por similitud. RAG encadena todo esto:

EtapaQué ocurrePieza de sgcWebSockets
1. Ingesta (una vez)Divide los documentos en fragmentos, incrusta cada fragmento, almacena los vectoresTsgcAIOpenAIEmbeddings.CreateEmbeddingsFromFile
2. AlmacenarGuarda los vectores en un archivo local o en un índice en la nubeTsgcAIDatabaseVectorFile · TsgcAIDatabaseVectorPinecone
3. Recuperar (por pregunta)Incrusta la pregunta, encuentra los fragmentos más cercanosGetEmbeddingQueryData
4. ResponderColoca los fragmentos en el prompt, pregunta al modeloTsgcHTTP_API_OpenAI._CreateChatCompletion

Los pasos 1 y 2 se ejecutan cuando cambian tus datos. Los pasos 3 y 4 se ejecutan en cada pregunta del usuario. Construyamos cada uno.

Paso 1 — incrusta tus documentos

Crea un TsgcAIOpenAIEmbeddings, dale una clave de OpenAI, apunta su propiedad Database a un almacén de vectores y llama a CreateEmbeddingsFromFile. Esa única llamada lee el archivo, lo divide en fragmentos (controlado por EmbeddingsOptions.ChunkSize), incrusta cada fragmento y escribe los vectores en el almacén mediante la secuencia BeginAddData / AddData / EndAddData por ti.

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;

Ese es el paso completo de ingesta. Ejecútalo una vez, o siempre que cambien tus documentos. El modelo de incrustación predeterminado es text-embedding-3-small; cámbialo a través de EmbeddingsOptions.Model si necesitas otro distinto. Hay más detalles en la página del componente Embeddings.

Paso 2 — elige dónde viven los vectores

Ambos backends descienden del mismo componente base, TsgcAIDatabaseVector, así que son intercambiables: cambia uno por el otro y tu código de ingesta y consulta no cambia. La única diferencia es dónde residen los datos.

Para una aplicación de escritorio, una herramienta sin conexión o un corpus más pequeño, TsgcAIDatabaseVectorFile mantiene todo en un archivo local sin ningún servicio externo. Cuando el índice es grande, debe compartirse entre procesos o usuarios, o tiene que escalar más allá de una sola máquina, cambia a TsgcAIDatabaseVectorPinecone, que hace upsert de cada fragmento a través de la API REST gestionada 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;

Fíjate en que la línea de ingesta es idéntica a la del Paso 1. Ese es todo el sentido de la clase base compartida. Consulta la página de Bases de datos vectoriales para el backend de archivo y la página de Pinecone para el de la nube.

Paso 3 — recupera y responde

Ahora el camino por pregunta. Incrusta la pregunta del usuario y encuentra los fragmentos almacenados más cercanos en una sola llamada: GetEmbedding incrusta el texto y lo pasa por el QueryData de la base de datos, devolviendo los pasajes más relevantes de tu corpus. Esos pasajes son tu contexto. Concaténalos con la pregunta y envía todo al modelo de 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;

Eso es RAG. El modelo nunca vio tus documentos durante el entrenamiento, y aun así responde a partir de ellos, porque colocaste los pasajes relevantes delante de él en el momento de la petición. Cambia el corpus y las respuestas cambian con él, sin ningún ajuste fino (fine-tuning) involucrado. La instrucción de negarse cuando el contexto está vacío es lo que mantiene al modelo honesto en lugar de adivinar.

Local o en la nube, el mismo código

Un detalle que vale la pena repetir: la elección entre el almacén de archivo y Pinecone es reversible. Como TsgcAIDatabaseVectorFile y TsgcAIDatabaseVectorPinecone comparten la base TsgcAIDatabaseVector, puedes crear un prototipo con el archivo local (infraestructura cero, funciona sin conexión) y pasar a Pinecone más adelante cambiando el componente que asignas a Embeddings.Database. Nada en tu código de ingesta o consulta cambia. Lo mismo aplica al LLM al final: _CreateChatCompletion en TsgcHTTP_API_OpenAI puede sustituirse por el componente de Anthropic o Gemini si prefieres un modelo distinto para redactar la respuesta final.

Una nota sobre la fragmentación y la calidad

La calidad de la recuperación depende de cómo se dividan tus documentos. Los fragmentos más pequeños hacen las coincidencias más precisas pero pueden perder contexto; los fragmentos más grandes conservan el contexto pero diluyen la coincidencia. EmbeddingsOptions.ChunkSize controla esto para CreateEmbeddingsFromFile, así que vale la pena ajustarlo a tu material. Para un control más fino también puedes incrustar cadenas individuales con CreateEmbeddings y dar forma a los fragmentos tú mismo antes de la ingesta.

Primeros pasos

Los tres componentes vienen incluidos en sgcWebSockets. Consigue la prueba gratuita, coloca los componentes de incrustaciones y de almacén de vectores en un formulario, apúntalos a un archivo de texto y tendrás un bucle RAG funcional en bastante menos de cien líneas. Explora el conjunto completo de bloques de construcción de IA en el hub de componentes de IA y LLM.

¿Preguntas sobre cómo aplicar esto a tu propio corpus? Ponte en contacto — recibirás una respuesta de las personas que escribieron el código.

Relacionado