RAG local: cómo monté un asistente que responde sobre mis propios PDFs sin subirlos a ChatGPT

Actualizado: 23/08/26 01:53

Creado: 23/08/26 00:48

Hace poco vi un vídeo de atareao titulado «Deja de subir tus PDFs a ChatGPT. Crea tu propia IA ya» que resume muy bien un problema con el que me he cruzado varias veces: para hacer preguntas sobre documentación propia (manuales, notas, contratos, apuntes) mucha gente sube esos ficheros directamente a ChatGPT. Funciona, pero implica ceder datos privados a un tercero.

La alternativa que plantea el vídeo es RAG (Retrieval-Augmented Generation): en vez de reentrenar un modelo con tus documentos, montas un sistema que busca los fragmentos relevantes en una base de datos local y se los pasa al modelo como contexto antes de responder. Todo corre en tu propia máquina, nada sale de tu red.

En este artículo cuento cómo llevé esa idea a un caso real: un asistente que responde preguntas sobre un conjunto de documentos en PDF y Markdown, 100% local.

El problema: RAG explicado en una frase

Un modelo de lenguaje tiene dos límites: no sabe nada de lo que ha pasado después de su fecha de corte, y no conoce tus documentos privados. RAG resuelve el segundo punto sin tocar el modelo: es como un examen a libro abierto, donde antes de responder alguien te acerca rápidamente las páginas relevantes del libro.

Frente al fine-tuning (caro, lento de actualizar y con riesgos de privacidad si el proveedor entrena con tus datos), RAG es dinámico: añades o borras un documento y el sistema lo refleja en la siguiente consulta, sin volver a entrenar nada.

El stack que usé

  • Modelo de lenguaje: Llama 3.2 (3B), servido en local con Ollama.
  • Embeddings: BGE-M3, un modelo multilingüe que funciona bien con textos en español (los modelos de embeddings solo-inglés son uno de los errores más comunes al montar esto).
  • Base de datos vectorial: PostgreSQL con la extensión pgvector.
  • Interfaz: OpenWebUI, que permite arrastrar PDFs directamente al chat sin tener que programar nada.
  • Orquestación: contenedores gestionados con Podman (también vale Docker Compose si lo prefieres).

Paso a paso

1. Levantar los servicios

Lo primero es tener Ollama, Postgres con pgvector y OpenWebUI corriendo. Con Docker Compose sería algo así:

version: "3.8"
services:
  ollama:
    image: ollama/ollama:latest
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama

  postgres:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_DB: ragdb
      POSTGRES_USER: raguser
      POSTGRES_PASSWORD: mi-clave
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data

  openwebui:
    image: ghcr.io/open-webui/open-webui:main
    ports:
      - "8080:8080"
    environment:
      OLLAMA_BASE_URL: http://ollama:11434
    depends_on:
      - ollama
      - postgres

volumes:
  ollama_data:
  pg_data:

2. Descargar los modelos

ollama pull llama3.2:3b
ollama pull bge-m3

3. Preparar la base vectorial

Con la extensión activada, la tabla de fragmentos necesita una columna de tipo vector con la dimensión que use tu modelo de embeddings:

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE documentos (
    id SERIAL PRIMARY KEY,
    documento TEXT,
    fragmento TEXT,
    embedding VECTOR(1024)
);

CREATE INDEX ON documentos USING hnsw (embedding vector_cosine_ops);

El índice HNSW es rápido pero aproximado; si prefieres precisión exacta a costa de velocidad, un índice plano (sin indexar, búsqueda por fuerza bruta) también es una opción válida en colecciones pequeñas.

4. Ingesta y chunking de documentos

Aquí está la parte que más se suele hacer mal: si divides el texto en trozos demasiado pequeños o sin solape, pierdes contexto y las respuestas empiezan a fallar. La configuración que mejor me funcionó fue:

  • Fragmentos de 500 a 1000 tokens.
  • Solape de entre el 10% y el 20% entre fragmentos consecutivos, para no cortar una idea a la mitad.
  • Markdown como formato de entrada preferido: los PDF «sucios» (con cabeceras, pies de página, tablas mal extraídas) meten ruido que degrada la calidad de las respuestas. Si puedes convertir el PDF a Markdown limpio antes de indexarlo, hazlo.

Por cada fragmento, generas su embedding con el modelo BGE-M3 y lo guardas junto al texto en la tabla documentos.

5. Consulta: de la pregunta a la respuesta

El flujo en tiempo de consulta es siempre el mismo:

  1. Se convierte la pregunta del usuario en un vector con el mismo modelo de embeddings.
  2. Se buscan en Postgres los fragmentos más cercanos a ese vector (similitud coseno).
  3. Esos fragmentos se inyectan como contexto en el prompt que recibe Llama 3.2.
  4. El modelo responde apoyándose en ese contexto, en vez de inventar (alucinar) la respuesta.

Con OpenWebUI todo esto queda oculto detrás de la interfaz: subes el PDF arrastrándolo al chat y ya puedes preguntar sobre él.

Cómo saber si funciona bien

La forma más práctica de evaluarlo es un test manual de 20 preguntas: eliges 20 preguntas cuya respuesta conoces con certeza porque está en tus documentos, y comparas la respuesta del sistema con la respuesta correcta. Si falla en varias, el problema suele estar en el chunking (fragmentos mal cortados), en el número de fragmentos recuperados por consulta, o en el solape. Se ajusta y se repite el test hasta que la tasa de aciertos es aceptable.

Errores comunes a evitar

  • Chunking mal hecho: fragmentos demasiado grandes diluyen la relevancia; demasiado pequeños pierden contexto.
  • Embeddings solo en inglés: si tus documentos están en español, un modelo de embeddings entrenado solo en inglés da resultados mediocres. BGE-M3 es una buena opción multilingüe.
  • PDFs sucios: metadatos, cabeceras y pies de página que se cuelan en el texto extraído meten ruido en los fragmentos.
  • No reindexar: si actualizas un documento y no regeneras sus embeddings, el sistema seguirá respondiendo con la versión antigua.
  • Saltarse el re-ranking: recuperar directamente los primeros resultados por similitud vectorial sin un paso de re-ranking suele saturar el contexto del modelo con fragmentos poco relevantes.

Para ir más allá

Una vez el caso básico funciona, hay dos mejoras que vale la pena explorar:

  • Búsqueda híbrida: combinar la búsqueda vectorial (semántica) con búsqueda por palabra clave tipo BM25, muy útil cuando la pregunta incluye identificadores exactos, códigos o parámetros técnicos que la búsqueda semántica pura no capta bien.
  • Re-ranking: pasar los resultados iniciales por un modelo especializado que reordene por relevancia real antes de dárselos al LLM, reduciendo el ruido en el contexto.

Más adelante, para casos con relaciones complejas entre documentos, existen enfoques como GraphRAG (de Microsoft, que construye un grafo de conocimiento entre entidades) o Agentic RAG, donde el propio modelo decide cuándo y cómo lanzar una búsqueda en lugar de hacerlo siempre de forma automática.

Conclusión

Montar tu propio sistema RAG local ya no requiere infraestructura compleja: con Ollama, Postgres+pgvector y OpenWebUI tienes en un rato un asistente que responde sobre tus propios documentos, sin subir nada a un tercero, sin coste de suscripción y funcionando incluso sin conexión a internet.

Este artículo está inspirado en el vídeo/podcast de atareao «809 – Deja de subir tus PDFs a ChatGPT. Crea tu propia IA ya», que recomiendo ver para profundizar en la teoría detrás de cada componente.