Visualización de sistema de IA y recuperación de datos
IAAIRAGLLMsOpen Source

Construyendo sistemas RAG que realmente funcionan

Por Demetrio Esteban · Fundador e ingeniero de IA, Quorax|25 de marzo de 2026|22 min de lectura

La Promesa y la Realidad de RAG

La Generación Aumentada por Recuperación (RAG) se ha convertido en la arquitectura estándar para anclar modelos de lenguaje en datos organizacionales. El concepto es directo: en lugar de depender únicamente de lo que un LLM aprendió durante el entrenamiento, recuperas documentos relevantes en el momento de la consulta y los proporcionas como contexto.

En teoría, esto elimina alucinaciones, mantiene las respuestas actualizadas y respeta los límites de datos. En la práctica, la mayoría de implementaciones RAG dan resultados mediocres — no porque la arquitectura sea defectuosa, sino porque los detalles importan enormemente.

Hemos desplegado sistemas RAG para ONG que gestionan más de 50.000 registros de beneficiarios, instituciones de investigación con décadas de publicaciones y agencias gubernamentales con documentación regulatoria compleja. Cada despliegue nos enseñó algo. Este artículo destila esas lecciones en una guía práctica — construida íntegramente con herramientas open-source.

Diagrama de arquitectura RAG

¿Por Qué RAG Open-Source?

Antes de entrar en arquitectura, una decisión crítica: ¿por qué construir con open-source?

Cuando envías una consulta con datos de beneficiarios a una API de IA propietaria, esos datos viajan a servidores externos. Pierdes el control sobre quién los procesa, dónde se almacenan y qué pasa con ellos. Para organizaciones que manejan información sensible — registros de refugiados, datos médicos, casos legales — esto es inaceptable.

Un stack RAG open-source significa:

  • Los datos nunca salen de tu infraestructura — Cada componente corre en servidores que controlas
  • Auditabilidad completa — Puedes inspeccionar cada modelo, cada algoritmo, cada flujo de datos
  • Sin costes por consulta — Tras la inversión inicial en infraestructura, consultar es prácticamente gratis
  • Sin dependencia de proveedor — Reemplaza cualquier componente sin reescribir tu sistema
  • Cumplimiento RGPD por diseño — La residencia de datos está garantizada, no prometida

Entendiendo el Pipeline RAG

Un sistema RAG tiene tres fases diferenciadas, y cada una puede degradar silenciosamente la calidad general:

graph LR
  A["INGESTAR\nParsear · Limpiar\nFragmentar · Embeber"] --> B["RECUPERAR\nEmbeber · Buscar\nRe-rank · Filtrar"]
  B --> C["GENERAR\nEnsamblar · Prompt\nGenerar · Stream"]
  C --> D["VALIDAR\nFidelidad · Relevancia\nCitas · Feedback"]
  style A fill:#EFF6FF,stroke:#1E40AF,color:#0F172A
  style B fill:#ECFDF5,stroke:#0F766E,color:#0F172A
  style C fill:#FEF3C7,stroke:#D97706,color:#0F172A
  style D fill:#FCE7F3,stroke:#DB2777,color:#0F172A

La clave: la calidad de recuperación limita la calidad de generación. Si recuperas documentos incorrectos, incluso el mejor LLM producirá respuestas erróneas — con confianza y gramática perfecta.

Dónde Fallan la Mayoría de Sistemas RAG

1. Preprocesamiento de Documentos

El paso más ignorado. Los documentos crudos contienen cabeceras, pies de página, números de página, formato de tablas y ruido de metadatos que contamina tus embeddings.

Fallos comunes:

  • Extracción de PDF que pierde estructura de tablas
  • Artefactos de OCR en documentos escaneados
  • Conversión HTML-a-texto que elimina la estructura semántica
  • Contenido duplicado de múltiples versiones del documento

Lo que funciona — todo open-source:

  • unstructured.io (Apache 2.0) para documentos complejos multi-formato
  • pymupdf4llm (AGPL) para PDFs con tablas y contenido estructurado
  • Docling (MIT, de IBM) para conversión de documentos nivel empresarial
  • Tesseract OCR (Apache 2.0) para documentos escaneados
# Pipeline de procesamiento de documentos open-source
from unstructured.partition.auto import partition
from unstructured.chunking.title import chunk_by_title

elements = partition(filename="informe_anual.pdf")

# Filtrar elementos de ruido
clean_elements = [
    el for el in elements
    if el.category not in ["Header", "Footer", "PageNumber"]
    and len(str(el)) > 50  # Saltar fragmentos diminutos
]

# Fragmentar respetando estructura del documento
chunks = chunk_by_title(
    clean_elements,
    max_characters=1500,
    overlap=200,
    combine_text_under_n_chars=300
)

2. Estrategia de Fragmentación

El error más común es tratar la fragmentación como un paso sin importancia. Los fragmentos de tamaño fijo ignoran la estructura del documento por completo.

EstrategiaCalidadComplejidadMejor para
Tamaño fijo⭐⭐TrivialTexto simple
Por frases⭐⭐⭐BajaConversacional
Semántica⭐⭐⭐⭐MediaDocs técnicos
Consciente doc⭐⭐⭐⭐⭐AltaEstructurados

Recomendación: Empezar con fragmentación consciente del documento. Solo caer a semántica si el parsing no es fiable.

Lo que funciona: Fragmentación semántica que respeta la estructura del documento. Superpón fragmentos un 10-15% para mantener el contexto en los límites. Añade un prefijo de metadatos a cada fragmento.

# Añadir contexto de procedencia a cada fragmento
for chunk in chunks:
    chunk.text = f"[Fuente: {doc.title} | Sección: {chunk.section}]\n{chunk.text}"

3. Calidad de los Embeddings

No todos los embeddings son iguales. Los genéricos pierden conexiones semánticas en vocabulario especializado — términos legales, códigos médicos, jerga humanitaria.

Ejemplo real: En un proyecto para una ONG, el término "protección" aparecía constantemente. En contextos humanitarios, se refiere a salvaguardar poblaciones vulnerables. Los embeddings genéricos lo trataban como sinónimo de "seguridad informática", recuperando documentos irrelevantes de TI.

Modelos de embedding open-source recomendados:

ModeloIdiomasDimensionesLicencia
BAAI/bge-large-en-v1.5EN1024MIT
BAAI/bge-m3100+1024MIT
intfloat/e5-large-v2EN1024MIT
intfloat/multilingual-e5-large-instruct100+1024MIT
nomic-ai/nomic-embed-text-v1.5EN768Apache 2.0
Snowflake/arctic-embedEN1024Apache 2.0

Para multilingüe (nuestro default): BAAI/bge-m3 o e5-large. Para solo inglés: Snowflake/arctic-embed o bge-large.

Hemos visto mejoras del 30-40% en precisión de recuperación cambiando a embeddings open-source ajustados por dominio. Ajustar bge-m3 en pares específicos del dominio redujo las alucinaciones un 60% en un despliegue.

4. Estrategia de Recuperación

La similitud coseno simple con recuperación top-k es un punto de partida, no una solución.

La jerarquía de recuperación:

graph TB
  L4["Nivel 4: AGÉNTICO<br/>El LLM razona, itera búsquedas,<br/>combina evidencia multi-salto"]
  L3["Nivel 3: RE-RANKEADO<br/>Cross-encoder re-puntúa candidatos<br/>(cross-encoder/ms-marco — open-source)"]
  L2["Nivel 2: HÍBRIDO<br/>Embeddings densos + BM25 por keywords<br/>Captura coincidencias semánticas y léxicas"]
  L1["Nivel 1: BÁSICO<br/>Similitud coseno, recuperación top-k<br/>Solo punto de partida"]
  L4 --> L3
  L3 --> L2
  L2 --> L1
  style L4 fill:#7C3AED,stroke:#5B21B6,color:#FFFFFF
  style L3 fill:#1E40AF,stroke:#1E3A8A,color:#FFFFFF
  style L2 fill:#0F766E,stroke:#065F46,color:#FFFFFF
  style L1 fill:#94A3B8,stroke:#64748B,color:#FFFFFF
# Recuperación híbrida con re-ranking open-source
from sentence_transformers import CrossEncoder  # Apache 2.0

# Etapa 1: Recuperación híbrida (Qdrant maneja denso + disperso)
dense_results = qdrant_client.search(
    collection_name="documents",
    query_vector=embed_query(query),
    limit=30
)
sparse_results = bm25_index.search(query, k=30)  # rank_bm25 (BSD)

# Fusionar y deduplicar
candidates = merge_results(dense_results, sparse_results, k=50)

# Etapa 2: Re-ranking con cross-encoder (modelo open-source)
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-12-v2")
pairs = [(query, doc.text) for doc in candidates]
scores = reranker.predict(pairs)

# Devolver mejores resultados por puntuación re-rankeada
ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
final_context = [doc for doc, score in ranked[:8]]

5. Ensamblaje de Contexto

Incluso con recuperación perfecta, cómo ensamblas el contexto importa. Meter todos los documentos en un solo prompt lleva al problema de "perdido en el medio".

Lo que funciona:

  • Ordenar fragmentos por relevancia, más relevante primero
  • Incluir metadatos de fuente para citas
  • Establecer un presupuesto de contexto (ej. 4000 tokens)
  • Usar una plantilla de prompt estructurada

La Arquitectura Open-Source Completa

Nuestra arquitectura de producción — cada componente es open-source y auto-alojable:

graph TB
  subgraph ingestion["Pipeline de Ingestión"]
    D["Documentos<br/>(PDF, HTML, DOCX)"] --> P["Unstructured.io<br/>(parsing)"]
    P --> SC["Fragmentación<br/>Semántica"]
    SC --> ME["Enriquecimiento<br/>de Metadatos"]
    ME --> EMB["BGE-M3 / E5-Large<br/>(embeddings open-source)"]
    EMB --> QD[("Qdrant<br/>(vectores)")]
    EMB --> ES[("Elasticsearch<br/>(índice BM25)")]
  end
  subgraph query["Pipeline de Consulta"]
    UQ["Consulta Usuario"] --> QX["Expansión<br/>de Query"]
    QX --> HS["Búsqueda<br/>Híbrida"]
    HS --> RR["Re-ranking<br/>Cross-Encoder"]
    RR --> CA["Ensamblaje Contexto<br/>+ Plantilla Prompt"]
    CA --> LLM["Llama 3 / Mistral / Qwen<br/>(via vLLM u Ollama)"]
    LLM --> ANS["Respuesta + Citas<br/>+ Documentos Fuente"]
  end
  subgraph monitor["Monitorización"]
    AP["Arize Phoenix<br/>(open-source)"]
    PG["Prometheus<br/>+ Grafana"]
  end
  QD -.-> HS
  ES -.-> HS
  style ingestion fill:#EFF6FF,stroke:#1E40AF
  style query fill:#ECFDF5,stroke:#0F766E
  style monitor fill:#FEF3C7,stroke:#D97706

Stack Tecnológico Completo

ComponenteHerramientaLicencia
Vector StoreQdrantApache 2.0
Índice DispersoElasticsearch / MeilisearchSSPL / MIT
EmbeddingsBGE-M3 / E5-LargeMIT
Re-rankercross-encoder/ms-marcoApache 2.0
LLM (grande)Llama 3.1 70B / Qwen 2.5 72BMeta / Apache
LLM (rápido)Mistral 7B / Llama 3.1 8BApache 2.0
Serving LLMvLLM / OllamaApache 2.0 / MIT
OrquestaciónLangChain / HaystackMIT
MonitorizaciónArize PhoenixApache 2.0
MétricasPrometheus + GrafanaApache 2.0
Parsing DocsUnstructured / DoclingApache 2.0 / MIT
EvaluaciónRAGASApache 2.0

Coste total en licencias: 0€. Vendor lock-in: cero. Datos saliendo de tu infraestructura: ninguno.

RAG Multilingüe

Para organizaciones que operan en múltiples idiomas — común en ONG internacionales e instituciones de la UE:

  • Embeddings multilingüesBAAI/bge-m3 maneja más de 100 idiomas en el mismo espacio vectorial, completamente auto-alojado
  • Recuperación cross-lingual — Una consulta en español recupera documentos relevantes en inglés, y viceversa
  • Generación consciente del idioma — LLMs open-source como Llama 3 y Qwen 2.5 son nativamente multilingües

Hemos encontrado que la precisión multilingüe cae ~15% comparado con monolingüe. Compensa recuperando más candidatos (top-50 en vez de top-30) y confiando más en el re-ranking.

Evaluación: El Framework RAGAS

No despliegues un sistema RAG sin evaluación sistemática. RAGAS (open-source, Apache 2.0) mide cuatro dimensiones:

DimensiónPregunta que respondeObjetivo
Precisión de Contexto¿Son los docs recuperados realmente relevantes?> 0.75
Recall de Contexto¿Encontramos TODA la información relevante?> 0.80
Fidelidad¿La respuesta se ciñe al contexto? ¿Sin alucinaciones?> 0.85
Relevancia de Respuesta¿La respuesta aborda la pregunta del usuario?> 0.80

Por debajo de estos umbrales, los usuarios notan problemas de calidad.

# Evaluación automatizada con RAGAS (Apache 2.0)
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision

# Usando un LLM auto-alojado como evaluador
results = evaluate(
    dataset=eval_dataset,
    metrics=[faithfulness, answer_relevancy, context_precision],
    llm=local_llm  # Llama 3 via vLLM — ningún dato sale de tu infra
)

print(f"Fidelidad:  {results['faithfulness']:.2f}")
print(f"Relevancia: {results['answer_relevancy']:.2f}")
print(f"Precisión:  {results['context_precision']:.2f}")

Monitorización en Producción con Herramientas Open-Source

graph TB
  subgraph phoenix["Arize Phoenix (Apache 2.0)"]
    PT["Trazas de LLM"]
    PQ["Dashboards de Calidad"]
    PH["Detección de Alucinaciones"]
    PD["Monitorización Drift Embeddings"]
  end
  subgraph prom["Prometheus + Grafana (Apache 2.0)"]
    PL["Latencia Recuperación (P50, P95, P99)"]
    PE["Latencia End-to-end (< 3s objetivo)"]
    PR["Tasa Recuperación Vacía"]
    PU["Throughput Tokens y Uso GPU"]
  end
  subgraph feedback["Bucle de Feedback"]
    FT["Pulgar Arriba/Abajo"]
    FR["Mecanismo Reportar Problema"]
    FC["Feedback alimenta Re-evaluación"]
  end
  style phoenix fill:#EFF6FF,stroke:#1E40AF
  style prom fill:#ECFDF5,stroke:#0F766E
  style feedback fill:#FEF3C7,stroke:#D97706

Optimización de Costes

Una de las mayores ventajas del RAG open-source: controlas la curva de costes.

Propietario (API)Open-Source (Auto-alojado)
Coste embedding~$0.0001 por 1K tokensCasi cero tras infraestructura
Generación LLM~$0.01-0.06 por 1K tokensCasi cero tras infraestructura
10K consultas/mes$300-1.800/mes$200-800/mes (servidor GPU)
EscaladoLineal — costes crecen con usoPlano — 100K consultas cuesta igual
Soberanía datosDatos salen en cada consultaDatos nunca salen de tu infra
Punto equilibrio~5.000 consultas/mes

A 50K+ consultas/mes, open-source es 5-10x más barato.

Consejos para optimizar tu stack auto-alojado:

  • Embedding por lotes durante la ingestión para maximizar utilización de GPU
  • Modelos cuantizados — GPTQ o AWQ ejecutan modelos 70B en una sola A100
  • Inferencia por niveles — Mistral 7B para análisis/routing, Llama 3.1 70B para generación final
  • Caché Redis — Cachear consultas frecuentes. Un 10% de acierto ya ahorra ciclos de GPU
  • Poda de contexto — No envíes 20 fragmentos al LLM si 5 son suficientes

Recomendaciones de Hardware

EscalaGPUSoporta
PrototipoRTX 4090 (24GB)Mistral 7B + embeddings
Org pequeñaA10G (24GB)Llama 3.1 8B cuantizado
Org medianaA100 (80GB)Llama 3.1 70B cuantizado
Org grande2x A100 (80GB)Qwen 2.5 72B precisión completa

Opción solo CPU: Ollama con Llama 3.1 8B funciona en cualquier servidor moderno — más lento pero coste GPU cero.

Errores Comunes que Hemos Visto

  1. "Usaremos los valores por defecto" — Fragmentación, embeddings y prompts por defecto. Esto da una demo, no un producto.

  2. Sin dataset de evaluación — No puedes mejorar lo que no mides. Construir un set de 50-100 preguntas es la mejor inversión que puedes hacer.

  3. Ignorar la frescura de documentos — Los documentos se actualizan, las políticas cambian. Sin re-indexación, tu sistema RAG sirve información obsoleta.

  4. Sobrecargar el contexto — Más contexto no es mejor. Después de ~4000 tokens, la calidad se degrada por "perdido en el medio".

  5. Saltar el humano en el bucle — Para aplicaciones de alto riesgo (legal, médica, humanitaria), siempre incluye revisión humana.

  6. Usar APIs propietarias por defecto — Enviar datos sensibles a APIs externas es un riesgo de cumplimiento y seguridad. Los modelos open-source auto-alojados eliminan este riesgo completamente.

Conclusión

RAG no es una solución plug-and-play. Es una arquitectura que requiere ingeniería cuidadosa en cada etapa. La diferencia entre un sistema RAG que "más o menos funciona" y uno que transforma cómo una organización usa su conocimiento está en estos detalles.

La buena noticia: el stack completo se puede construir con herramientas open-source. Sin dependencia de proveedor. Sin datos saliendo de tu infraestructura. Sin tarifas por consulta que escalan linealmente. Y con modelos como Llama 3.1, Mistral y Qwen 2.5, la brecha de calidad entre open-source y propietario se ha estrechado dramáticamente.

En Quorax, hemos construido sistemas RAG para organizaciones que procesan miles de documentos en múltiples idiomas — desde bases de datos de beneficiarios de ONG hasta archivos de instituciones de investigación. Cada sistema corre sobre infraestructura open-source que la organización posee y controla. No por ideología, sino porque para organizaciones que manejan datos sensibles, la soberanía no es opcional.

¿Quieres saber más?

Contáctanos para hablar sobre cómo podemos ayudar a tu organización con soluciones de IA, datos y seguridad.