RAG implementation

El código para mi implementación está aquí en GitHub

Requerimientos

  • LangChain
  • Un modelo de embeddings (text-embedding-3-large)
  • Un modelo de chat (gpt-4.1)
  • Una base de datos vectorial (CosmosDB)
  • PyPDF (libreria para chunking inteligente)
  • Documentos como base de conocimiento para el RAG (documentos sobre una herramienta propietaria)

Sobre CosmosDB for NoSQL: Se puede usar como una base NoSQL tradicional pero también soporta búsqueda nativa con vectores (hay que habilitarlo primero y establecer una policy vectorial). Yo la uso en vez de ChromaDB por estar en Azure y ser entorno Microsoft ya que personalmente me entra dentro del tier gratuito. Si no, ChromaDB es una buena opción gratuita.

Implementación

Se divide en tres procesos:

Habilitar búsqueda vectorial para CosmosDB. Tras hacer el siguiente paso, podemos ejecutar el script para recrear el contenedor con una policy para vectores

(dentro de CosmsoDB) Settings < Features < Vector Search for NoSQL API < Enable

Cargar documentos en CosmosDB:

  • Cargamos los documentos en memoria
  • Los dividimos en chunks
  • Limpiamos los chunks de caracteres especiales (\n, \t, \r)
  • Creamos los embeddings en batches
  • Para cada chunk subimos el original y su embeddings

Realizar una query:

  • Obtenemos los embeddings para la query (tiene que ser el mismo modelo que se usó para crear los embeddings de los ficheros)
  • Hacer una búsqueda vectorial en CosmosDB para sacar el texto original de los chunks con contexto relevante a nuestra query
  • Invocar el modelo de chat a través de LangChain, pasándole ambos pregunta y chunks con contexto en la misma llamada

Conceptos aprendidos

Calidad del dato

La calidad del dato es de máxima importancia. Hay que revisar manualmente los datos que se meten y ver que sean aporten valor. Si metemos datos que no aporten, solo generamos ruido.

También hay que revisar que los PDFs sean texto puro, ya que si son puramente imágenes o contienen imágenes importantes habrá que pasarlos por algún tipo de OCR para extraer texto.

Chunking

La parte de dónde y cómo hacer chunking es complicada. Lo importante es que haya un buen overlap entre chunks para que la respuesta a tu pregunta no caiga en tierra de nadie. Yo estoy probando con un chunk_size de 500 tokens y un overlap de 100 tokens.

Limpiar los chunks después de partirlos es igual de importante. En mis primeras pruebas se metían en cosmos muchos caracteres como \n o \t y esto genera mucho ruido.

Para sistemas grandes o genéricos puede ser importante meter metadatos junto a los chunks para filtrar luego al buscar. Cosas como el nombre del documento al que pertenece un chunk o la fecha de creación del documento ayudan a filtrar luego y favorecer documentación reciente o saber de dónde viene algún dato erróneo o con menor calidad.

Debilidades BBDD Vectorial

Una base de datos vectorial es muy buena para hacer búsquedas relacionadas o búsquedas semánticas, pero se queda corta para hacer búsquedas por keywords. En esas situaciones se puede implementar una búsqueda híbrida (algoritmo BM25).

Referencia(s)

RAG Systems in 5 Levels of Difficulty (With Full Code) | Data Science Collective *RAG vs Fine Tuning. The Great LLM Showdown | by Agneya Pathare | Medium