RAG local con ChromaDB y Ollama: tutorial Python
Implementar un RAG en local con ChromaDB, Ollama y Python implica combinar tres piezas: un almacén vectorial que conserva los datos en disco (ChromaDB), un modelo de embeddings que convierte tus fragmentos en vectores (nomic-embed-text a través de Ollama) y un LLM de chat que responde basándose en los pasajes recuperados. Sin clave de API, sin fugas de datos. Esta guía te lleva de un PDF sin procesar a un chatbot que cita sus fuentes en 22 minutos.
#¿Por qué esta pila para un RAG local?
Muchos tutoriales de RAG comienzan con LangChain o LlamaIndex. Estos frameworks son potentes, pero ocultan lo que sucede bajo el capó. Aquí escribimos el pipeline a mano con solo tres dependencias. Entenderás cada paso y sabrás qué optimizar más adelante.
- ChromaDB
- Almacén vectorial de código abierto, escrito íntegramente en Python, con modo persistente integrado (SQLite + índice HNSW). No hay que iniciar ningún servidor.
- Ollama
- Sirve tanto el modelo de embeddings (nomic-embed-text) como el LLM de chat (Qwen 3.5, Granite 4.2, Gemma 4). Punto de acceso HTTP único en localhost:11434.
- Python nativo
- Algunas funciones, sin framework. Podrás conectar LangChain más adelante si es necesario, pero no es necesario para comenzar.
#Prerrequisitos
Esta guía te lleva al modelo. El kit te lleva al copiloto que programa en tu editor.
- Espacio en línea de por vida
- PDF + archivos
- Reembolsado 30 j
- Python 3.10+
- ChromaDB requiere mínimo 3.10. Verifica con python --version.
- Ollama instalado y arrancado
- El daemon escucha por defecto en http://localhost:11434. Si empiezas desde cero, sigue primero la guía de instalación de Ollama.
- 8 GB de RAM
- 16 GB permiten trabajar con comodidad. El modelo de chat de 9B en Q4 ocupa aproximadamente 6 GB y el modelo de embeddings, alrededor de 300 MB.
- No es necesario tener un GPU
- La inferencia en CPU funciona, aunque es más lenta. Para la ingestión de un gran corpus, un GPU de 6 GB o más acelera mucho los embeddings.
#1. Instalar ChromaDB y preparar Ollama
Se crea un entorno virtual limpio, se instalan las tres bibliotecas necesarias y se descargan los modelos en Ollama.
Tres paquetes: chromadb para el almacenamiento vectorial, ollama para el cliente Python oficial, pypdf para leer PDFs. Eso es todo.
nomic-embed-text es un modelo de embeddings de 137M parámetros, multilingüe, que produce vectores de dimensión 768. Ligero, rápido, bueno en francés. Qwen 3.5 9B (6,6 GB, 256k de contexto, multilingüe, Apache 2.0) sirve para el chat final: es la opción de 8 GB por defecto en 2026. Puedes reemplazarlo por granite4.2:8b (más austero en el uso de recursos) o gemma4:12b sin cambiar nada en el código.
#2. Configurar el modelo de embeddings
Un embedding es un vector que representa el significado de un texto. Dos textos cercanos semánticamente tienen vectores cercanos. Es el motor del RAG: se buscan los chunks cuyo embedding se parece más al de la pregunta.
Deberías ver «Dimension du vecteur : 768». Si falla con model not found, es que no se ha ejecutado ollama pull nomic-embed-text.
#3. Ingestión de PDF en francés
La ingestión hace tres cosas: leer las páginas de un PDF, cortar el texto en fragmentos de tamaño razonable, y almacenar cada fragmento junto con su embedding en ChromaDB en modo persistente.
El chunker divide en bloques de 800 caracteres con 100 de solapamiento. Es un punto de partida: bloques ni demasiado pequeños (falta de contexto) ni demasiado grandes (diluyen la señal). Para contenido jurídico muy denso, reduce a 500. Para manuales técnicos con una maquetación más espaciada, aumenta a 1200.
Inicia la ingestión en un directorio ./pdfs/ que contenga tus documentos:
#4. Búsqueda top-k en ChromaDB
Una vez indexados los chunks, la búsqueda consiste en generar un embedding de la pregunta y luego pedir a Chroma los k vectores más cercanos según la distancia coseno. Es instantáneo, incluso con 100.000 chunks.
k=4 es un buen valor predeterminado. Si el valor es demasiado pequeño, pierdes contexto relevante; si es demasiado grande, ahogas al LLM en ruido y sobrepasas la ventana de contexto. Para preguntas muy precisas, k=2 es suficiente. Para preguntas transversales, sube a 6.
#5. Bucle de chat con citas
Ahora lo ensamblamos todo: buscamos los chunks pertinentes, construimos un prompt con el contexto, lo enviamos a Qwen 3.5 a través de Ollama y pedimos al modelo que cite sus fuentes.
Tres detalles que importan. Primero, temperature=0.2: se busca una respuesta basada en hechos, no creativa. Segundo, num_ctx=8192: la ventana predeterminada de Ollama (2048) es demasiado corta una vez que se introducen 4 fragmentos de 800 caracteres. Tercero, el prompt de sistema obliga al modelo a decir 'no sé' en lugar de generar alucinaciones: es la principal protección contra las alucinaciones en el RAG.
#6. Ejemplo concreto: chatbot jurídico para contratos
Imaginemos un despacho que quiere consultar 200 contratos de prestación de servicios en PDF. Con la pila anterior, en menos de una hora tenemos un asistente que responde a preguntas del tipo:
- Pregunta típica
- «¿Qué contratos incluyen una cláusula de no competencia tras la finalización del contrato con una duración superior a 12 meses?»
- Lo que sucede
- El embedding de la pregunta encuentra los chunks que contienen palabras clave semánticamente cercanas (no competencia, tras la ruptura, duración). Qwen 3.5 lee estos 4 pasajes y responde con los nombres de los archivos correspondientes.
- Garantía de confidencialidad
- Ningún dato sale del equipo. Sin clave de API. Sin telemetría. Esto es lo que distingue un RAG local de un wrapper de OpenAI.
#Solución de problemas
- ChromaDB lento durante la ingestión
- El cuello de botella es casi siempre la llamada a Ollama para generar embeddings. Comprueba con ollama ps que nomic-embed-text se esté ejecutando en GPU. En CPU, calcula unos 50 chunks por segundo; en GPU, unos 500.
- « model not found »
- Ollama no encuentra nomic-embed-text. Vuelve a ejecutar ollama pull nomic-embed-text y compruébalo con ollama list.
- Respuestas que inventan fuentes
- Un modelo 9B todavía alucina a veces. Pasa a mistral-small (24B, ~14 GB, muy bueno en francés) o a qwen3.8:27b si tienes suficiente VRAM. O añade un reranker (cross-encoder) después de ChromaDB para filtrar los falsos positivos.
- Embeddings de baja calidad en FR
- nomic-embed-text es multilingüe, pero no es óptimo para contenido exclusivamente en francés. Para contenido jurídico o médico, prueba Solon-embeddings-large-0.1 o bge-m3 (que deben cargarse mediante sentence-transformers, fuera de Ollama).
- ChromaDB crece sin límite
- Cada reindexación añade duplicados. Antes de volver a ingerir un PDF, ejecuta collection.delete(where={"source": name}) para eliminar los fragmentos antiguos.
#Para ir más allá
Tienes un RAG funcional. Estas son las líneas de trabajo que puedes seguir para llevarlo más lejos:
- Comparar los modelos de embeddings FR
- Nuestra guía «Los mejores modelos de embeddings FR» compara BGE, E5, Solon y nomic en contenido francófono.
- Mejorar el chunking
- «Estrategias de chunking» detalla el chunking semántico, por títulos de Markdown o por párrafos: a menudo es lo que más mejora la precisión.
- Añadir un reranker
- « Añadir un reranker al pipeline »: +15% de relevancia colocando un cross-encoder después de Chroma. El paso lógico siguiente.
- Búsqueda híbrida
- «Búsqueda híbrida BM25 + vectorial» combina la búsqueda léxica y la semántica, y resulta indispensable cuando hay mucha jerga o nombres propios.
¿Un comentario, un error, una precisión? Avísanos, eso mejora la guía para todos.