IA/ServerStack Journal

RAG en producción: tres bugs reales que casi nadie te cuenta

Los tutoriales de RAG muestran demos que funcionan en 50 líneas. La producción no se parece. Aquí están tres bugs reales que encontramos en nuestro pipeline RAG con Qdrant — los síntomas, las causas raíz y qué se hace cuando aparecen.

La narrativa popular sobre RAG (Retrieval-Augmented Generation) suele sonar así: "carga tus documentos, conviértelos en vectores, guárdalos en una base vectorial, y ya — un chatbot que sabe sobre tu negocio".

Eso funciona en una demo de 50 líneas. En producción, con clientes reales, tenants distintos y documentos de verdad, el RAG se rompe de formas que ningún tutorial te cuenta.

Estos son tres bugs reales que encontramos en nuestro pipeline RAG (Qdrant + embeddings + streaming) durante abril de 2026, con sus causas raíz y los fixes aplicados. Si estás llevando un RAG a producción, este post te puede ahorrar varias semanas.

Bug 1: filtros que devolvían cero resultados, sin error

Síntoma

El usuario hacía una pregunta sobre un documento que sabíamos que estaba indexado. El chatbot respondía con su variante educada de "no encuentro información sobre eso". El log mostraba 0 chunks recuperados. El stream del modelo seguía adelante con manos vacías.

Lo más desconcertante: no había error visible. El sistema "funcionaba". Solo no devolvía nada útil.

Causa raíz

El filtro que enviábamos a Qdrant tenía un formato que la base vectorial no entendía como filtro válido — pero no devolvía error de sintaxis. Devolvía un resultado válido vacío.

El problema estaba en la serialización. La aplicación pasaba un Dictionary de C# directo como valor de match:

{"key":"documentVersionMap","match":{"value":{"1":1}}}

Qdrant espera valores escalares (un número, una cadena, un booleano), no objetos. Internamente lo convertía a algo así como "System.Collections.Generic.Dictionary..." y al no encontrar ningún chunk con ese valor, devolvía 0 resultados.

Fix

Construir el filtro explícitamente con valores escalares:

// En vez de pasar el dictionary directo
filters["documentVersionMap"] = options.DocumentVersionMap;

// Pasar el ID concreto que Qdrant entiende
if (options.DocumentVersionMap.Count == 1)
    filters["document_version_id"] = options.DocumentVersionMap.Values.First();
else
    filters["document_version_id"] = options.DocumentVersionMap.Values.ToArray();

Y validar el contrato del filtro con un helper que tipifica cada caso (long, string, bool, array) antes de mandarlo a Qdrant.

Lección general

Tu base vectorial puede devolver "0 resultados" cuando el problema es tu filtro mal formado, no la ausencia de datos. Siempre que un RAG empiece a no encontrar nada de un día para el otro, sospecha del filtro antes que del índice.

Bug 2: burbuja en blanco antes del primer token del stream

Síntoma

El usuario hacía la pregunta. Aparecía una burbuja vacía del asistente durante varios segundos. Después, sin previo aviso, empezaban a aparecer las palabras una por una. Los usuarios pensaban que el chat estaba roto.

Causa raíz

El frontend creaba el contenedor del mensaje del asistente al recibir el primer evento del stream, no al iniciar la petición. El primer evento (los metadatos del modelo y los chunks recuperados) llegaba antes que el primer token de texto. Resultado: 0.5 a 2 segundos de burbuja vacía.

Para el usuario eso es una eternidad. Y peor: no hay indicador de actividad. El usuario no sabe si la pregunta llegó, si está pensando, o si simplemente colgó.

Fix

Mostrar un indicador de carga inmediatamente al enviar la pregunta, antes de que llegue el primer evento:

[Usuario] ¿Cuál es el horario de entrega?
[Asistente] ⋯ pensando ⋯           ← este indicador aparece de inmediato
[Asistente] El horario de entrega es...   ← reemplaza al indicador cuando llega el primer token

Implementación: al hacer la petición, crear el placeholder con el indicador. Cuando llega el primer token de texto, eliminar el indicador y empezar a renderizar el contenido.

Lección general

En interfaces con LLM, la percepción de velocidad importa tanto como la velocidad real. Un sistema que responde en 800ms pero da feedback inmediato se siente más rápido que uno que responde en 500ms con una burbuja vacía durante 200ms. Cada estado intermedio del stream necesita una representación visual deliberada.

Bug 3: chunks que estaban en BD pero no se recuperaban

Síntoma

Un documento subido el día anterior aparecía en el listado del dashboard, los chunks estaban indexados (lo verificamos directamente en Qdrant con consulta manual), pero al hacer una pregunta sobre ese documento, el sistema no traía ninguno de sus chunks.

Causa raíz

Mismatch entre dos campos de identificación del documento:

  • En el momento de indexar, los chunks se guardaban con un campo document_version_id (el ID de la versión específica del documento).
  • En el momento de buscar, el filtro se construía con documentVersionMap (un mapa de documento → versión).

Ambos campos eran válidos, ambos representaban información correcta, pero no eran el mismo campo. Qdrant buscaba documentos con el campo del segundo y los chunks lo tenían con el campo del primero. Cero resultados.

Era el típico bug de "estaba bien por separado, mal en conjunto" — propio de equipos que evolucionan código en sprints distintos sin alinear contratos.

Fix

Tres cosas:

  1. Estandarizar el nombre del campo en todo el pipeline: document_version_id. Cualquier referencia con otro nombre se renombró.
  2. Documentar en el código el contrato exacto de los campos esperados por Qdrant — qué tipo, qué nombre, qué semántica.
  3. Test de integración que indexa un documento de prueba, hace una búsqueda con un filtro, y verifica que recupera al menos un chunk. Si en algún momento el contrato se rompe, este test lo detecta antes de producción.

Lección general

En un pipeline RAG, los nombres de campos son contratos. Cuando el equipo evoluciona en paralelo, esos contratos se desincronizan en silencio. La defensa son tests de integración pequeños pero mortalmente útiles, que prueban el camino completo desde indexar hasta recuperar.

Lo que estos tres bugs tienen en común

Ninguno de los tres bugs lanzaba un error visible. Todos hacían que el sistema "funcionara" pero produjera resultados malos. Esa es la categoría más peligrosa de bugs en sistemas con IA: los que no fallan, los que solo dan respuestas pobres.

Detectarlos requiere:

  1. Tests de calidad de respuestas — no solo "el endpoint responde 200". También: para esta pregunta sobre este documento, el sistema debe traer al menos N chunks relevantes.
  2. Métricas de recuperación en producción — porcentaje de queries que retornaron 0 chunks. Si ese número sube, hay un bug que no rompe el endpoint pero está quemando la calidad.
  3. Logging del flujo completo — qué query se hizo, qué filtros se aplicaron, cuántos chunks volvieron, cuáles fueron, qué generó el modelo. Sin esto, depurar un RAG es imposible.

Lo que los tutoriales no te dicen

El RAG es fácil de hacer funcionar en una demo. Es difícil de mantener en producción. La distancia entre los dos no es código, es:

  • Calidad de los embeddings (¿el modelo que usas tiene buen comportamiento en español? ¿en tu vertical?).
  • Estrategia de chunking (¿cuánto del contexto preservas? ¿cómo manejas tablas, código, listas?).
  • Filtros multi-tenant (¿cómo aíslas los datos de cada cliente? ¿cómo verificas que no se mezclan?).
  • Re-ranking (la primera búsqueda devuelve 50 chunks; ¿cómo eliges los mejores 5?).
  • Evaluación continua (¿cómo sabes que la calidad de las respuestas no degrada?).

Cada una de esas decisiones tiene la potencia para hundir un RAG bonito.

En ServerStack Solutions

Operamos pipelines RAG en producción con clientes reales y datos reales. Si estás llevando uno a producción y empiezas a chocar contra estos problemas, hablemos. Una sesión de revisión arquitectónica suele ser la diferencia entre seis meses descubriendo cada bug por las malas y un par de semanas blindando el sistema antes.


Relacionado: Bases de datos vectoriales explicadas · Redes neuronales y self-attention · FlowOrdr: vende mientras duermes con IA.

Contando…

Preguntas frecuentes

¿Qué es Qdrant y por qué se usa para RAG?

Qdrant es una base de datos vectorial — guarda los textos convertidos en vectores numéricos (embeddings) y permite buscar por similitud. Se usa en RAG para encontrar los fragmentos de documentos más parecidos a la pregunta del usuario, antes de pasárselos al modelo de lenguaje. Compite con Pinecone, Weaviate y Milvus. Para muchos casos de PyME, Qdrant en Docker es suficiente y autohosteable, sin pagar mensualidad de SaaS.

¿Por qué no usar simplemente OpenAI Assistants o ChatGPT con archivos?

Funciona para casos simples y para experimentar. Pero pierdes control sobre el chunking, el filtrado multi-tenant, el modelo de embeddings, la latencia y los costos. Para un producto SaaS que ofrece chatbot a múltiples clientes con sus propios datos, usar Assistants directo es difícil de escalar y de auditar. Un RAG propio cuesta más de configurar al inicio pero da control total a largo plazo.

¿Qué es chunking y por qué importa tanto?

Chunking es el proceso de dividir tus documentos en pedazos antes de convertirlos en vectores. El tamaño y la forma de los chunks impactan directamente en la calidad del RAG: chunks muy pequeños pierden contexto; chunks muy grandes son ruido. Las estrategias varían: por número de tokens, por estructura del documento (títulos, párrafos), por similitud semántica. No hay una talla única — depende del tipo de contenido. Probar varias estrategias con queries reales es la única forma de elegir bien.

Escrito por

Equipo ServerStack Solutions

Fundador, ServerStack Solutions. Fundador de ServerStack Solutions. Diseño infraestructura y automatización para negocios que quieren dormir tranquilos. Escribo sobre CI/CD, DevOps y herramientas que hacen la diferencia.