Depuración de fallos silenciosos en RAG: recuperación de información, fragmentado, filtros y evaluaciones
Las respuestas de RAG pueden sonar correctas y aun así estar equivocadas. Una guía orientada a Python para rastrear la recuperación de información, el fragmentado, los filtros de metadatos, las evaluaciones, la búsqueda híbrida y el anclaje en datos reales.
Fuentes: RAG Is Failing Quietly: A Debugging Playbook for Python Teams
Los fallos que nadie ve
Los errores más molestos en los sistemas RAG no son los ruidosos. Los fallos ruidosos son fáciles de detectar: el bot admite que no encontró nada, la base de datos de vectores está inactiva, una tarea de inserción de embeddings falló o la respuesta llega sin citaciones.
Los fallos silenciosos cuestan más. Se recuperan los documentos, la respuesta se lee sin problemas, las citaciones parecen legítimas y el panel de control indica que la recuperación fue exitosa. Luego, un experto en el área lee la respuesta y afirma que está equivocada. Suele ser entonces cuando el equipo se da cuenta de que ha estado depurando en el nivel incorrecto.
RAG no es una sola funcionalidad. Es una secuencia de decisiones que se acumulan. Alguien decide cómo se interpreta y reescribe una pregunta, dónde se dividen los documentos, qué recuperador se utiliza, qué metadatos reducen las opciones, cómo se vuelve a clasificar la lista corta, qué termina incluido en el prompt, cómo se comporta el modelo cuando hay poca evidencia y si la respuesta escrita realmente se deriva de lo proporcionado. Mirar solo el prompt final significa llegar después de que el daño ya se ha causado.
El pipeline que se está depurando
Un sistema realista se parece más a lo siguiente que al diagrama ordenado de una diapositiva:
flowchart LR
A[User question] --> B[Query rewrite]
B --> C[Retriever]
C --> D[Reranker]
D --> E[Evidence pack]
E --> F[Answer generator]
F --> G[Verifier]
G --> H[Final answer]
C --> I[Trace log]
D --> I
E --> I
F --> I
G --> I
Cualquier etapa puede fallar. El generador recibe la culpa porque es la fase más visible, pero muchos fallos comienzan antes. El recuperador podría haber seleccionado el período de tiempo incorrecto. El reordenador podría haber promovido un fragmento similar que no es relevante. El paquete de evidencias podría haber omitido el único párrafo útil debido a un presupuesto de tokens demasiado limitado. Una buena depuración implica hacer visible cada etapa.
Fallo 1: el texto similar no es evidencia útil
La búsqueda vectorial detecta similitud semántica, lo cual no siempre implica relevancia para la tarea. Tomemos el caso de un usuario que pregunta si los clientes empresariales pueden obtener un reembolso tras la actualización de políticas del segundo trimestre. El recuperador podría devolver un fragmento sobre políticas de reembolso simplemente porque menciona “empresarial”, “reembolso” y “políticas”. Ese fragmento podría estar desactualizado, incompleto o no relacionado con el segundo trimestre.
Una huella útil muestra el ID del documento recuperado, su versión, su fecha de vigencia, la puntuación de similitud, la puntuación de reclasificación, los filtros de metadatos aplicados y si la respuesta final lo citó. Si los registros solo indican una puntuación como 0.83, no es posible diagnosticar el error.
Fallo 2: el particionamiento destruyó el significado
El particionamiento no es un detalle de almacenamiento; determina la calidad de la respuesta. Una política de reembolso podría organizarse en criterios de elegibilidad, excepciones, un apéndice empresarial, fechas de vigencia y diferencias regionales. Si el herramienta de particionamiento separa la fecha de vigencia de las excepciones, la respuesta puede citar el párrafo sobre elegibilidad y pasar por alto la condición que cambia el resultado.
Los fragmentos más adecuados incluyen un ID de documento estable, un título de sección, una versión, una fecha, suficiente texto adyacente para mantener el significado y un enlace hacia el original. Nada de esto justifica fragmentos excesivamente grandes, que solo generan ruido. El objetivo son fragmentos que conserven el punto en el que se toma una decisión.
Fallo 3: filtros de metadatos faltantes
Muchos sistemas RAG comienzan como búsquedas semánticas puras. Esto funciona hasta que el mismo tema aparece en diferentes versiones, productos, usuarios, regiones o grupos de permisos. Una pregunta sobre la política actual de reembolsos de la empresa no debería hacer que el sistema busque en cada documento de reembolso que la compañía haya elaborado; primero debería aplicar filtros.
from dataclasses import dataclass
from datetime import date
@dataclass(frozen=True)
class SearchFilters:
product: str | None
customer_tier: str | None
region: str | None
as_of: date
permission_group: str
def build_filters(user_context: dict) -> SearchFilters:
return SearchFilters(
product=user_context.get("product"),
customer_tier=user_context.get("tier"),
region=user_context.get("region"),
as_of=date.today(),
permission_group=user_context["permission_group"],
)
El orden es importante. Aplique filtros de permisos y actualidad antes de pedirle al modelo que razone. Buscar contexto privado o desactualizado con la esperanza de que el prompt lo resuelva es una mala estrategia.
Fallo 4: un conjunto de evaluación con escenarios problemáticos
La forma más rápida de mejorar un sistema RAG es utilizar un conjunto de evaluación pequeño y seleccionado intencionalmente. Omita los mil ejemplos; comience con unos treinta que reflejen las formas reales en que el sistema falla:
- preguntas cuya respuesta no está en el corpus
- preguntas que requieren dos fuentes
- preguntas en las que una política antigua y una nueva entran en conflicto
- preguntas con redacción similar sobre productos diferentes
- preguntas que dependen de una tabla
- preguntas para las cuales la respuesta correcta es derivarlas a un humano
Un sistema de recuperación muy sencillo podría verse así:
from dataclasses import dataclass
@dataclass(frozen=True)
class RagCase:
question: str
required_doc_ids: set[str]
forbidden_doc_ids: set[str]
def evaluate_retrieval(cases: list[RagCase], retrieve) -> dict:
total = len(cases)
hit = 0
leaked_forbidden = 0
for case in cases:
results = retrieve(case.question)
retrieved_ids = {item["doc_id"] for item in results}
if case.required_doc_ids & retrieved_ids:
hit += 1
if case.forbidden_doc_ids & retrieved_ids:
leaked_forbidden += 1
return {
"cases": total,
"required_hit_rate": hit / total,
"forbidden_leak_rate": leaked_forbidden / total,
}
Está diseñado intencionadamente de forma sencilla. No garantiza la calidad de las respuestas, pero detecta numerosos fallos en la recuperación de información antes de que el modelo vea cualquier contexto.
Fallo 5: juzgar las respuestas sin las pruebas
Una respuesta RAG debe evaluarse en función de las pruebas realmente proporcionadas, y no en relación con todo el corpus. Esta distinción es importante por tres razones. Si la respuesta correcta existe en la base de conocimientos pero no se recuperó, no se puede esperar que el generador la conozca, y eso constituye un fallo en la recuperación. Si se recuperaron las pruebas adecuadas pero la respuesta las ignoró, eso es un fallo en la generación. Si la respuesta cita una fuente que no respalda su afirmación, eso es un fallo en la fundamentación. Se necesitan etiquetas para los tres casos.
@dataclass(frozen=True)
class AnswerEval:
question: str
answer: str
evidence_doc_ids: set[str]
expected_claims: set[str]
def simple_claim_check(eval_case: AnswerEval) -> dict:
answer_lower = eval_case.answer.lower()
missing = [
claim
for claim in eval_case.expected_claims
if claim.lower() not in answer_lower
]
return {
"passed": len(missing) == 0,
"missing_claims": missing,
"evidence_count": len(eval_case.evidence_doc_ids),
}
Los sistemas de producción pueden incluir una revisión humana o un evaluador basado en LLM para las afirmaciones más sutiles, pero incluso una verificación básica de las afirmaciones es mejor que un simple visto bueno vago.
Un rastro mejor
Cada respuesta debe dejar un rastro estructurado:
import time
import uuid
from dataclasses import dataclass, field
@dataclass
class RagTrace:
run_id: str = field(default_factory=lambda: str(uuid.uuid4()))
started_at: float = field(default_factory=time.time)
query: str = ""
rewritten_query: str | None = None
filters: dict = field(default_factory=dict)
retrieved: list[dict] = field(default_factory=list)
evidence_doc_ids: list[str] = field(default_factory=list)
prompt_tokens: int = 0
completion_tokens: int = 0
verifier_result: str | None = None
latency_ms: int | None = None
def finish_trace(trace: RagTrace) -> RagTrace:
trace.latency_ms = int((time.time() - trace.started_at) * 1000)
return trace
Con él, las preguntas prácticas reciben respuestas. ¿Se buscó la colección correcta? ¿Los filtros eliminaron los documentos adecuados? ¿El sistema de recuperación mostró la fuente esperada? ¿El conjunto de pruebas incluía demasiado contexto? ¿El verificador notó citas faltantes? ¿La latencia se debió a la recuperación, generación o verificación? Sin respuestas a estas preguntas, aún no existe un sistema RAG, solo un prompt con un cuadro de búsqueda adjunto.
La búsqueda híbrida suele ser la solución poco efectiva
Los equipos a menudo pasan demasiado rápido de la búsqueda vectorial simple a la RAG agente. Primero se debería probar la recuperación híbrida. La búsqueda semántica brilla cuando el usuario y el documento utilizan palabras diferentes. La búsqueda por palabras clave gana siempre que sea importante la precisión en cadenas literales, como un identificador, un código de error, un nombre de campo o una cláusula de redacción legal. Los sistemas reales con frecuencia terminan necesitando ambos enfoques.
def hybrid_rank(vector_results: list[dict], keyword_results: list[dict]) -> list[dict]:
scores: dict[str, float] = {}
items: dict[str, dict] = {}
for rank, item in enumerate(vector_results, start=1):
doc_id = item["doc_id"]
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (rank + 10)
items[doc_id] = item
for rank, item in enumerate(keyword_results, start=1):
doc_id = item["doc_id"]
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (rank + 10)
items[doc_id] = item
return sorted(
items.values(),
key=lambda item: scores[item["doc_id"]],
reverse=True,
)
Este método de fusión basado en rango recíproco no es sofisticado, pero sí útil. Recompensa a los documentos que aparecen en ambas listas de resultados, al tiempo que deja espacio a cualquiera de los sistemas de recuperación para destacar algo importante.
Cuando la recuperación agente es beneficiosa
El RAG agente es adecuado para problemas que requieren investigación. Ejemplos adecuados incluyen casos en los que la primera pasada suele carecer de pruebas suficientes, preguntas que involucran varios documentos, flujos de trabajo que deben elegir entre consultas SQL, búsquedas vectoriales y llamadas a API, modelos que deben determinar si cuentan con evidencia suficiente y procesos que se benefician al intentarlo nuevamente con una consulta diferente.
Ejemplos poco adecuados son respuestas simples a preguntas frecuentes, autocompletado de baja latencia, búsquedas en documentación estable y cualquier situación en la que cada llamada adicional al modelo sea demasiado costosa. El problema debe justificar el uso de la recuperación agente. Si un filtro de metadatos puede resolverlo, utilícelo.
Lista de verificación para producción
Antes de culpar al modelo, revise lo siguiente:
- ¿Están los documentos versionados?
- ¿Se aplican los permisos antes de la recuperación?
- ¿Se excluyen por defecto los documentos obsoletos?
- ¿Los fragmentos se seleccionan según el significado y no solo según el recuento de caracteres?
- ¿Los términos exactos se detectan mediante búsquedas por palabras clave?
- ¿Las tablas e imágenes se manejan por separado?
- ¿Las evaluaciones incluyen casos sin respuesta?
- ¿Se registran los IDs de los documentos recuperados?
- ¿Se verifican las fuentes citadas?
- ¿Se mide la calidad de la respuesta de forma independiente de la calidad de la recuperación?
La mayoría de los fallos en RAG no son misteriosos. Simplemente no se han observado.
Donde se rompe la cadena de evidencias
RAG hace que los sistemas LLM parezcan más sólidos, pero esa solidez no se logra automáticamente. El modelo solo puede utilizar las pruebas que recibe. El recuperador solo puede encontrar lo que se indexó correctamente. El índice solo puede proporcionar los metadatos que se conservaron. Un evaluador solo puede detectar los fallos que alguien se tomó la molestia de identificar. Este trabajo es menos llamativo que una demostración, pero es donde se construye la confianza.
Por lo tanto, cuando un sistema RAG falla silenciosamente, el prompt no es el punto de partida adecuado. La mejor pregunta es: ¿dónde se rompió la cadena de pruebas?
Leer más
- Cómo funciona la IA moderna: explicación de LLMs, prompts, RAG, agentes y MCP — Una explicación en lenguaje sencillo de la arquitectura de la IA: cómo los LLMs predicen tokens, cuatro técnicas de prompts, RAG con embeddings, el ciclo de agentes ReAct y qué hace MCP.
- Cache Semántico para Agentes de LLM con Redis: Reduce Costos y Tiempos de Respuesta — Cómo el cache semántico disminuye los gastos y la latencia en los LLM: embeddings, umbrales, Redis y RedisVL, ajuste de precisión y almacenamiento en caché de subpreguntas dentro de los flujos de trabajo de los agentes.
- Medición de una Pipeline RAG: Tasa de Éxitos, MRR y Búsqueda Híbrida — Cómo crear un conjunto de datos de referencia, evaluar la recuperación de información con la tasa de éxitos y el MRR, y comparar la búsqueda de texto, vectorial y híbrida en 360 preguntas.
- Diez errores silenciosos en la búsqueda en RAG en producción y cómo solucionar cada uno — Desde el tamaño y la superposición de los fragmentos hasta la búsqueda híbrida, RRF, el reclasificado, la elección de embeddings y las evaluaciones de CI: diez errores en la búsqueda que causan respuestas incorrectas en RAG y sus soluciones.
- Notas prácticas: Mejorar el rendimiento de RAG — Parte 2: HyDE (Documento hipotético) — Guía paso a paso de las Notas prácticas: Mejorar el rendimiento de RAG — Parte 2: HyDE (Documento hipotético), con contratos, verificaciones y espacios para código listos para ser integrados por los equipos que implementan este patrón.