Rechaza cuando el contexto está vacío: la única verificación que detiene las alucinaciones de RAG
Un bot de soporte cita con total seguridad una política de reembolsos que no existe. Nadie escribió un prompt defectuoso para que eso pasara — ocurrió porque nada verificó si el paso de recuperación realmente encontró algo relevante antes de entregárselo al modelo.
El problema: la recuperación siempre devuelve algo
Una búsqueda vectorial no falla como falla una consulta a una base de datos. Pídele los vecinos más cercanos a una consulta y te devolverá los vecinos más cercanos — incluso si los "más cercanos" siguen estando conceptualmente a kilómetros de distancia de la pregunta. No hay una respuesta incorporada de "no tengo eso" en una búsqueda por similitud; simplemente devuelve sus top-k resultados, ordenados, sin importar qué tan débil sea realmente la coincidencia.
Si tu pipeline mete a ciegas esos chunks top-k en el prompt y le pide al modelo que responda, el modelo hace lo que los modelos de lenguaje hacen con cualquier contexto: trata el texto proporcionado como autoritativo y responde a partir de él, incluso cuando ese texto no tiene nada que ver con la pregunta real. El resultado se ve seguro y se lee con fluidez — y está equivocado.
La solución: convierte la distancia en un puntaje de similitud y luego aplícale un umbral
La búsqueda vectorial devuelve distancias (qué tan separados están dos vectores), no directamente un puntaje de 0 a 1 de "qué tan relevante es esto". Una distancia menor significa una mejor coincidencia. Una conversión simple y ampliamente usada:
similarity = 1 - distance
(La fórmula exacta depende de tu métrica de distancia — la distancia coseno se mapea a esto de forma limpia; otras métricas como la euclidiana necesitan su propia normalización. Verifica qué devuelve tu vector store antes de usar esta fórmula tal cual.)
Una vez que tengas un puntaje de similitud, elige un umbral. Antes de generar una respuesta, compara el puntaje del chunk mejor recuperado contra él:
- Supera el umbral → pasa el contexto recuperado al modelo y genera una respuesta a partir de él, como siempre.
- No supera el umbral → omite por completo la generación a partir de ese contexto y devuelve una respuesta fija: "no se encontró contexto relevante".
Esa es toda la salvaguarda. Sin fine-tuning, sin una llamada extra al modelo — solo una comparación y una bifurcación.
results = vector_store.similarity_search_with_score(query, k=5)
top_score = 1 - results[0].distance # adapt to your store's distance metric
if top_score < SIMILARITY_THRESHOLD:
return "No relevant context found."
answer = generate(query, context=results)
Por qué esto importa más que las instrucciones a nivel de prompt de "no inventes cosas"
Decirle a un modelo que "solo responda a partir del contexto proporcionado, y que diga que no sabe si no está ahí" ayuda, pero depende de que el modelo juzgue correctamente la relevancia desde dentro de la misma ventana de contexto que está a punto de usar para responder. Una verificación de umbral estricta ocurre antes de que el modelo vea siquiera la consulta — es una compuerta determinista, no una petición para que el modelo se vigile a sí mismo. Usa ambas: el umbral como la compuerta estricta, y las instrucciones del prompt como una segunda línea de defensa para los casos que superan el listón pero son solo parcialmente relevantes.
Cómo elegir un umbral
- Ejecuta un lote de consultas reales — incluyendo algunas que sepas que no tienen una buena respuesta en tu corpus — a través de la recuperación y registra los puntajes de similitud.
- Observa dónde se agrupan los resultados genuinamente relevantes frente a dónde se agrupan los irrelevantes — el umbral queda entre esos dos grupos.
- Vuelve a revisar el umbral cada vez que cambies de modelo de embeddings o reindexes tu corpus; los puntajes de modelos distintos no son comparables entre sí.
Trucos avanzados
- No pongas el umbral tan alto que preguntas legítimas pero formuladas de forma imprecisa terminen rechazadas. Valida contra consultas reales de usuarios, no solo sintéticas.
- Un rechazo sobre el chunk principal no es lo mismo que un rechazo sobre todo el corpus. Si recuperas k>1 chunks, decide si aplicas el umbral al único mejor chunk o si exiges un número mínimo por encima del listón — lo segundo reduce los falsos positivos por una única coincidencia afortunada.
- Registra cada rechazo. Una tasa de rechazo en aumento es una señal — o tu corpus tiene una brecha real, o tu indexado/chunking se degradó y el contenido relevante está puntuando más bajo que antes.
Recursos
- Documentación de Chroma — consultas y puntajes de distancia
- LangChain — retrievers de vector store y umbrales de puntaje
¿Estás construyendo una funcionalidad con IA? Yeda AI diseña, audita y lleva a producción sistemas LLM.