Métricas de evaluación de RAG: retrieval, reranking y generación
Traducción automática Este artículo se tradujo automáticamente a partir de la versión original en inglés.
Un sistema RAG con filtros rotos puede funcionar durante meses sin activar ninguna alerta operativa. Sigue devolviendo respuestas y cumple su objetivo de latencia, pero las respuestas se basan en evidencias incompletas. Recall@k frente al conjunto gold original deja al descubierto la pérdida; los dashboards de latencia y disponibilidad, no.
Para los ingenieros que operan o evalúan sistemas RAG de varias etapas, esta referencia relaciona los fallos de parsing, filtrado, retrieval, reranking y generación con la métrica que identifica cada uno, y muestra después dónde debe situarse esa medición en un gate de release o de monitorización.
- Un stack de evaluación útil cubre ingestion, retrieval, grounding de la generación, conformidad con la ontología y señales del sistema. [RAGAS](https://docs.ragas.io/), [TruLens](https://www.trulens.org/), [DeepEval](https://deepeval.com/), [Arize Phoenix](https://phoenix.arize.com/) y el [TREC 2024 RAG Track](https://trec.nist.gov/data/rag2024.html) proporcionan herramientas. No eligen tus métricas por ti. - En RAG basado en metadatos y ontologías, una etiqueta incorrecta o un predicado hard frágil pueden reducir el recall a cero. Recall@k estándar detecta la pérdida cuando conserva el conjunto gold original. Una métrica de false-exclusion del filtro identifica la causa. Faithfulness puede seguir puntuando las claims frente a un contexto incompleto, pero no puede diagnosticar la causa en el filtro o en el retrieval. Una negativa vacía puede no producir statements y `NaN`, según la implementación.¿Quieres saltar directamente y ejecutar el código?
El repositorio ejecutable
slavadubrov/rag-evals-demoaplica las métricas a SciFact.make evalejecuta la suite ymake benchmarkcompara configuraciones de chunking, embedding y LLM. Los notebooks 00–09 aíslan cada métrica. La demo usa Qdrant embebido, por lo que no necesita Docker.
Las secciones siguen el orden del pipeline. Empieza por la tabla de decisión y utiliza las secciones posteriores como referencia para cada etapa.
Tabla de decisión para la evaluación de RAG
Utiliza esta tabla como punto de partida antes de elegir un framework. La métrica adecuada depende del modo de fallo que quieras detectar, no del nombre de la herramienta.
| Pregunta | Familia de métricas | Úsala cuando | Precauciones |
|---|---|---|---|
| ¿El parsing conservó la fuente? | Completitud de extracción, cobertura de tablas/figuras | Entran en el corpus PDFs, presentaciones, escaneos y páginas HTML | Un texto limpio a la vista puede haber perdido captions, notas al pie o estructura tabular |
| ¿El retrieval encontró la evidencia correcta? | Recall@k, nDCG@k, MRR, precision/recall del contexto | Puedes etiquetar chunks o documentos relevantes | Un filtro de metadatos hard puede eliminar el documento correcto antes de empezar el ranking |
| ¿El reranking mejoró la shortlist? | Uplift del reranker, Precision@1, delta de nDCG | Hay cross-encoders o rankers basados en LLM después del retrieval | Mide la latencia y el coste junto con la mejora de calidad |
| ¿La respuesta utilizó la evidencia? | Faithfulness, groundedness, soporte de citas | La respuesta cita documentos o afirma hechos del contexto | Faithfulness no puede diagnosticar un parsing o retrieval incorrectos |
| ¿El sistema es estable en producción? | Drift, regeneración, fallback, latencia p95, coste por respuesta | El tráfico cambia después del lanzamiento | La telemetría de producción necesita revisión humana muestreada para mantenerse calibrada |
Para una comparación más breve de herramientas, consulta Best RAG Evaluation Tools: Ragas, DeepEval, and TruLens.
Parte 1: Define el éxito antes de la arquitectura
Prepara el conjunto de evaluación antes del diagrama de arquitectura. Esto proporciona a cada decisión posterior sobre componentes un objetivo medible.
No puedes elegir entre BM25 y dense retrieval, chunking recursivo y semántico, o Cohere Rerank y BGE hasta saber qué estás optimizando. «Mejores respuestas» no es una métrica. Un contrato ilustrativo sería «faithfulness ≥ 0,85 en un golden set de 200 consultas que cubra nuestras tres intenciones principales, con latencia p95 < 1,5 s y una tasa de false-exclusion del filtro < 2 %». Sus cifras son provisionales; lo importante es que calidad, cobertura, latencia y filtrado tengan gates explícitos.
Define el harness antes de escribir el código de retrieval. El primer harness será incorrecto y tendrás que revisarlo. Revisar una métrica es mucho más barato que revisar un sistema que ya has puesto en producción.
Tres capas del pipeline y dos modos de ejecución
El RAG moderno es un pipeline, así que la evaluación también tiene que serlo. Ningún número aislado detecta todos los modos de fallo.
La evaluación en producción tiene tres capas de pipeline. La evaluación de ingestion pregunta si el corpus y el índice conservan la fuente. La evaluación en tiempo de consulta pregunta si la reescritura, el filtrado, el retrieval, el reranking y el ensamblado del contexto encontraron la evidencia correcta. La evaluación de respuestas y producción pregunta si la respuesta utilizó esa evidencia y si la calidad se mantiene con tráfico real. Si colapsas las capas en una sola puntuación, un bug de normalización puede desaparecer dentro de una puntuación de respuesta aceptable.
Estas capas describen dónde ocurre un fallo. Offline y online describen cuándo se ejecuta la comprobación y contra qué datos. La evaluación offline utiliza un dataset fijo con ground truth conocido; es reproducible y corresponde a la selección de componentes, las comparaciones A/B y los gates de CI. La evaluación online puntúa muestras de tráfico real y captura la regeneración, el tiempo de permanencia, el feedback explícito y el drift real de las consultas. Es más ruidosa y más difícil de instrumentar.
Cada capa del pipeline puede aportar comprobaciones offline y online. Un corpus fijo de ingestion detecta regresiones del parser antes del release, mientras que los monitores de freshness y fallos de parsing cubren las actualizaciones reales. Un conjunto fijo de consultas mide el retrieval antes del release, mientras que las trazas reales muestreadas revelan el drift en producción. Solo offline no detecta los cambios reales; solo online dificulta reproducir las regresiones.
Evaluación por componente frente a end-to-end
Hay dos errores habituales. La evaluación exclusivamente end-to-end te dice que el sistema está roto, pero no dónde. La evaluación exclusivamente por componentes puede mostrar que todas las partes pasan mientras el sistema completo sigue fallando. La solución consiste en usar unas pocas métricas end-to-end principales para las decisiones go/no-go, además de métricas por componente para el diagnóstico. Las métricas de retrieval detectan regresiones del retriever. Las métricas de generación detectan regresiones del generator. La corrección end-to-end de la respuesta detecta fallos de integración.
Los frameworks de referencia (recorrido con opinión)
| Framework | Lo que mejor hace | Dónde falla |
|---|---|---|
| RAGAS | Mi criterio de selección: un vocabulario compartido para faithfulness, answer relevancy y precision/recall del contexto (metrics) | Coste del LLM-judge; componentes de la puntuación opacos al depurar; cambios de versión |
| ARES | Mi criterio de selección: un classifier judge específico de la tarea justifica el coste de entrenamiento y anotación (paper); su precision depende del benchmark | Configuración más pesada; tienes que entrenar modelos de verdad |
| TruLens | Mi criterio de selección: las feedback functions enlazadas a trazas y la integración con OpenTelemetry son más importantes que un catálogo de métricas RAG (project) | Menos completo en métricas específicas de RAG que RAGAS |
| DeepEval | Mi criterio de selección: la integración con el test runner y las métricas personalizadas importan más que el valor por defecto del framework (project) | El uso intensivo de LLM-judge provoca picos de coste |
| Arize Phoenix | Mi criterio de selección: el tracing y las visualizaciones de embeddings ayudan a investigar una hipótesis de drift (project) | Tienes que aportar tus propias definiciones de métricas |
| TREC 2024 RAG Track | Benchmark público para la evaluación de nuggets (AutoNuggetizer), la evaluación del soporte y la fluidez sobre MS MARCO Segment v2.1 | No es una herramienta de runtime; es un benchmark con el que calibrarse |
Mi stack por defecto es RAGAS para el vocabulario de métricas, DeepEval para los gates de CI, Phoenix para el tracing en producción y código personalizado para las métricas específicas de la ontología. Superarás cualquier solución con la que empieces. Elige el framework que facilite la creación de métricas personalizadas.
Para benchmarks, utiliza BEIR (Thakur et al., NeurIPS 2021) para la generalización zero-shot del retrieval, MTEB para la calidad general de embeddings, MIRACL para el retrieval multilingüe y el TREC 2024 RAG Track para la evaluación end-to-end de RAG.
Parte 2: Sitúa los puntos de evaluación en el pipeline
Un sistema RAG de producción es más grande que «hacer embedding de documentos, recuperar chunks y llamar a un LLM». Todas las etapas entre la adquisición del documento y la entrega de la respuesta pueden fallar.
Cada etapa del diagrama tiene al menos una métrica. Una etapa sin métrica puede fallar sin que nadie lo advierta.
Los tres carriles coinciden con los puntos donde puede perderse evidencia. El carril de ingestion cubre parsing, limpieza, chunking, embedding e indexación. El carril de tiempo de consulta cubre rewriting, filtrado, retrieval, reranking y ensamblado del contexto. El carril de respuestas y producción cubre faithfulness, verificación de citas, señales de usuario, drift, latencia y coste.
Los errores se acumulan a lo largo de la cadena: un parsing incorrecto limita lo que puede hacer el chunking, un chunking incorrecto limita el retrieval y un retrieval incorrecto limita tanto el reranking como la generación. Faithfulness solo mide la respuesta final, nunca la causa upstream.
Parte 3: Evaluación de ingestion
Muchos fallos de RAG en producción empiezan en ingestion. El sistema funciona con documentos de prueba limpios, pero falla con PDFs, escaneos, tablas y páginas desordenadas del corpus real.
Adquisición y parsing de documentos
Qué medir:
-
Completitud de extracción de texto:
extracted_chars / expected_charssobre una muestra etiquetada, calculada por clase de documento. No existe un paquete canónico: escribe un pequeño harness que compare la salida del parser con una referencia limpiada manualmente. Busca notas al pie, encabezados y captions ausentes. -
Precisión del OCR: CER (Character Error Rate) y WER (Word Error Rate), las métricas estándar de speech/OCR:
donde , y son sustituciones, eliminaciones e inserciones a nivel de carácter, y es el número de caracteres de referencia (el subíndice corresponde a la versión para palabras). No apliques el mismo umbral de CER a todo el corpus. Calíbralo por clase de documento y pérdida de calidad en la respuesta downstream. El texto impreso, la escritura manuscrita y el material multilingüe tienen perfiles de error diferentes. Calcula la métrica con
jiwer(jiwer.cer(refs, hyps),jiwer.wer(refs, hyps)) o conevaluatede Hugging Face. Para corpus de evaluación, FUNSD y SROIE son benchmarks públicos.from jiwer import cer, wer refs = ["Mars has two moons, Phobos and Deimos."] hyps = ["Mars has two m00ns, Phobos and Deirnos."] print(f"CER = {cer(refs, hyps):.3f}") # CER = 0.105 print(f"WER = {wer(refs, hyps):.3f}") # WER = 0.286 -
Fidelidad de extracción de tablas: TEDS (Tree-Edit-Distance-based Similarity) mide hasta qué punto un árbol de tabla HTML predicho se parece al de referencia, normalizado por el tamaño del árbol más grande. De Zhong et al., 2020 (PubTabNet):
TEDS utiliza tanto la estructura (filas, columnas, spans) como el contenido de las celdas. TEDS-S elimina el contenido y puntúa solo la estructura. Implementación de referencia:
teds.pyde PubTabNet (utilizaaptedinternamente). Para corpus de evaluación, consulta PubTabNet, FinTabNet y SciTSR. Los parsers ingenuos suelen fallar con las tablas. Haz un benchmark antes de confiar en ellos. -
Conservación del layout y la estructura: orden de encabezados, integridad de listas y orden de lectura en PDFs con varias columnas. Utiliza DocLayNet como benchmark etiquetado. Una comparación directa puede incluir un parser de elementos como
unstructured, una librería PDF comopymupdfy un parser basado en VLM comodocling.
Compara familias de parsers distintas: por ejemplo, un baseline con Tesseract, un modelo de OCR basado en VLM y el candidato de tu proveedor. Utiliza una muestra estratificada de clases de documentos reales a un DPI fijo, incluidos escaneos limpios, fotografías, tablas, texto multilingüe, matemáticas y escritura manuscrita. Informa del CER o WER por clase y del TEDS para las páginas con tablas.
Limpieza y normalización
-
Precisión de eliminación de boilerplate: precision/recall frente a spans de boilerplate etiquetados por humanos. Una eliminación agresiva descarta contenido relevante; una eliminación laxa contamina los embeddings. Herramientas para comparar:
trafilatura,jusText,Resiliparse. Barbaresi (2021) hace un benchmark comparativo. -
Normalización Unicode: el porcentaje de documentos que producen salidas NFC y NFKC idénticas (calculado con
unicodedata.normalize, de la stdlib) es una señal útil de drift. Las discrepancias son las que hacen que los zero-width joiners y los caracteres parecidos rompan el recall del retrieval. -
Precisión de detección de idioma: F1 sobre una muestra multilingüe etiquetada. Es crítica para índices multilingües. Utiliza
fasttext-langdetect(ellid.176de Facebook),lingua-pyocld3. FLORES-200 proporciona texto de evaluación en 200 idiomas, pero la mezcla de idiomas en producción debe determinar el slice de prueba. -
Efectividad de deduplicación (MinHash / LSH): precision/recall de tu detector de near-duplicates frente a un conjunto etiquetado manualmente. La idea subyacente es estimar la similitud de Jaccard entre conjuntos de shingles de documentos mediante hashes de permutaciones aleatorias (Broder, 1997) y agrupar near-duplicates con banding de LSH (Indyk & Motwani, 1998). Haz un sweep del número de hashes y del umbral de Jaccard en tu corpus. Registra por separado la tasa de false-merge (corrompe las respuestas) y la tasa de merges no detectados (desperdicia espacio del índice).
datasketchproporciona la implementación utilizada más abajo; sus parámetros son ilustrativos:from datasketch import MinHash, MinHashLSH def shingles(text: str, k: int = 5) -> set[str]: text = text.lower() return {text[i:i + k] for i in range(len(text) - k + 1)} def to_minhash(text: str, num_perm: int = 128) -> MinHash: m = MinHash(num_perm=num_perm) for s in shingles(text): m.update(s.encode("utf-8")) return m docs = { "d1": "Mars has two moons, Phobos and Deimos.", "d2": "Mars has two moons, Phobos and Deimos!", # near-dup "d3": "Curiosity rover landed on Mars in 2012.", } lsh = MinHashLSH(threshold=0.8, num_perm=128) for did, text in docs.items(): lsh.insert(did, to_minhash(text)) print(sorted(lsh.query(to_minhash(docs["d1"])))) # ['d1', 'd2'] -
Eliminación de PII: precision y recall, calculados por separado para cada tipo de entidad (correos electrónicos, SSN, nombres y direcciones). Los errores de recall crean riesgo de compliance; los errores de precision perjudican la calidad de las respuestas. Define el operating point con el equipo jurídico. Entre las herramientas candidatas están Microsoft Presidio,
scrubadubo un modelo NER fine-tuned sobre un conjunto etiquetado.
El chunking controla la calidad del retrieval
El chunking puede crear una brecha de recall en varios puntos aunque el modelo de embedding permanezca fijo. En el benchmark de proveedores de NVIDIA de 2025, el chunking a nivel de página produjo la mayor precisión y la menor varianza en documentos paginados. Considera ese resultado como evidencia para el corpus evaluado, no como un ganador universal.
El chunking semántico agrupa frases adyacentes según su similitud de embeddings y corta en límites disímiles. SemanticChunker de LangChain y SemanticSplitterNodeParser de LlamaIndex implementan esta estrategia. Puede mejorar el recall frente a ventanas fijas cuando importan los límites temáticos.
El splitting recursivo por caracteres intenta primero los saltos de párrafo, después los de frase y, por último, los de palabra, hasta que cada chunk cabe en el tamaño objetivo. RecursiveCharacterTextSplitter de LangChain implementa esta secuencia. Elige valores candidatos de ventana y overlap que encajen con la estructura de tus documentos y deja que el golden set determine los valores finales.
Métricas que conviene seguir:
- Coherencia del chunk: , donde son embeddings de frases. Los chunks saludables son internamente similares y disímiles en los límites. Calcula la métrica con
sentence-transformersy elcosine_similaritydescikit-learn. - Calidad de los límites: etiqueta manualmente «¿es un corte razonable?» sobre una muestra y añade una comprobación estructural para garantizar que los chunks no parten tablas, listas o secciones numeradas.
- Tamaño óptimo del chunk: haz un sweep de tamaños en tokens (128, 256, 512, 1024) y representa Recall@k frente al tamaño en tu golden set. Elige el punto de inflexión. No elijas el valor que aparecía en el tutorial.
- Efectividad del overlap: elimina varios porcentajes de overlap y mide Recall@k. Deja de aumentarlo cuando la curva local de recall se aplane o el coste de duplicación supere la ganancia.
- Fidelidad de atribución del chunk: porcentaje de chunks que conservan un puntero de fuente verificable (número de página, ancla de sección, ID del documento). Esto es necesario para la auditabilidad.
- Chunking late frente a early: el late chunking (Günther et al., 2024) genera embeddings del documento completo y después lo segmenta, conservando el contexto global (implementación de referencia en
jina-embeddings-v3). Contextual Retrieval (Anthropic, 2024) antepone a cada chunk contexto generado por un LLM. Ambas técnicas añaden coste. Haz un benchmark sobre tu corpus antes de adoptar cualquiera de ellas.
Mi opinión: el chunking estructural (dividir por encabezados, tablas y secciones —implementado por parsers como unstructured.io o recorriendo el AST que ya haya producido tu parser—) está infrautilizado. Si tus documentos tienen estructura, úsala antes de añadir heurísticas de similitud. El splitting recursivo por caracteres es el baseline; el chunking semántico merece la sobrecarga principalmente en prosa no estructurada.
Extracción y enriquecimiento de metadatos
- Precision/recall/F1 de NER: por tipo de entidad, sobre un subconjunto etiquetado. Sigue el estándar estilo CoNLL/MUC. Calcula la métrica con
seqeval(from seqeval.metrics import f1_score) para la versión consciente de etiquetas BIO/IOB, o con scikit-learn para comparar conjuntos de spans. CoNLL-2003 y OntoNotes 5.0 son los corpus de referencia canónicos. - F1 de extracción de relaciones: aún más importante en sistemas basados en ontologías. Etiqueta manualmente un conjunto estratificado por tipo de relación y clase de documento. TACRED y DocRED son benchmarks públicos; entre las implementaciones candidatas están
opennrey los pipelines de relaciones despaCy. - Precisión de extracción de títulos y encabezados: exact-match más similitud de Levenshtein normalizada () frente al ground truth;
python-Levenshteinorapidfuzzproporcionan ambas en una sola llamada. - Conservación de metadatos jerárquicos: porcentaje de chunks que conservan correctamente su sección padre, documento padre y ruta de ascendencia. Esta es la métrica que decide si tu RAG puede responder a preguntas del tipo «¿qué dice el child de la política X?».
Generación de embeddings
- Benchmarks de selección de modelos: utiliza los resultados de tareas de MTEB (nDCG@10 es la métrica principal; el paquete Python de MTEB permite reproducir el leaderboard localmente), BEIR para la generalización zero-shot y MIRACL para retrieval multilingüe como puntos de comparación. Trata la transferencia de MTEB en inglés a un idioma con menos recursos como una hipótesis que debes probar con el conjunto etiquetado de ese idioma.
- Evaluación específica del dominio: no trates la posición en un benchmark general como un resultado de dominio. Dimensiona un golden set de dominio a partir de su matriz de cobertura y de la incertidumbre que tu decisión pueda tolerar. Después vuelve a ordenar los modelos candidatos con
ranxopytrec_eval. Un conjunto de dominio puede invertir el orden del leaderboard, así que publica el slice del dataset, el protocolo de retrieval y el intervalo de confianza junto con el resultado. - Detección de drift en embeddings: sigue la KL distributiva o el drift basado en modelos entre una ventana de referencia fija y los embeddings de producción en una ventana móvil; mide también la estabilidad de los vecinos más próximos para un conjunto fijo de probes.
evidentlyyalibi-detectimplementan detectores basados en modelos y estadísticos. El estudio comparativo de Evidently es una evaluación de proveedor; compara los métodos ante shifts conocidos en tus propios embeddings. - Multi-vector frente a single-vector: la late interaction conserva representaciones a nivel de token en lugar de colapsar cada documento en un único vector; ColBERT es el diseño canónico, con implementaciones de referencia en RAGatouille y PyLate. Esta representación más rica aumenta el coste del índice y del retrieval. Compara calidad, almacenamiento y latencia con un baseline single-vector sobre el mismo conjunto de dominio antes de adoptarla.
Construcción del índice
- Recall@k con aproximación: compara el índice approximate-nearest-neighbour (ANN) con un baseline exacto de brute-force usando el mismo k; en FAISS, es
IndexHNSWFlat(oIndexIVFFlat) frente aIndexFlatIP/IndexFlatL2. Define la pérdida de recall aceptable a partir del presupuesto de calidad downstream. El proyectoann-benchmarksregistra curvas de Pareto recall–QPS entre librerías. - Ajuste de HNSW: HNSW (Hierarchical Navigable Small World) es un grafo de proximidad por capas; consulta Malkov & Yashunin, 2018. Está implementado en
hnswlib, enIndexHNSWFlatde FAISS y en la mayoría de las bases de datos vectoriales. HNSW expone tres parámetros:M(fan-out del grafo),efConstruction(anchura de candidatos durante la construcción) yefSearch(anchura de candidatos durante la consulta). Parte de los valores por defecto documentados por la librería y haz un sweep de los parámetros hasta que la curva recall–latencia cumpla los requisitos de tu conjunto de evaluación. - Ajuste de IVF: IVF (Inverted File index) particiona los vectores con k-means en
nlistceldas y, en tiempo de consulta, explora lasnprobeceldas más próximas; consultaIndexIVFFlatyIndexIVFPQde FAISS. Haz un sweep denlistynprobefrente al recall y la latencia de la búsqueda exacta. Evalúa por separado las consultas filtradas, porque las familias de índices y las bases de datos vectoriales implementan el recorrido de filtros de forma diferente. - Retraso de freshness de las actualizaciones: tiempo desde el commit del documento hasta que puede recuperarse. Registra p50 y p99. En sistemas con requisitos regulatorios, registra también el porcentaje de consultas servidas contra índices obsoletos.
Parte 4: Evaluación en tiempo de consulta
El carril de tiempo de consulta contiene las métricas que diagnostican la ruta de retrieval. Recall@k por sí solo no puede mostrar si el fallo lo causó el rewriting, el filtrado, el reranking o el ensamblado del contexto.
Comprensión y rewriting de consultas
- Calidad de la expansión de consultas: uplift de Recall@k en tu golden set, comparando la consulta expandida con la original. Define de antemano la ganancia mínima útil y su incertidumbre. Si la expansión no supera ese gate local, no justifica su latencia y coste. Los baselines clásicos de PRF (pseudo-relevance feedback), como RM3 y Bo1, siguen siendo comprobaciones de cordura útiles; la expansión basada en LLM debe superarlos.
- Evaluación de HyDE: HyDE (Gao et al., 2022) genera una respuesta hipotética con el LLM, crea su embedding y hace retrieval contra ella. Añade latencia de generación y una nueva superficie de fallo. Mide Recall@10 por separado en slices in-domain, out-of-domain y de baja confianza, y decide si debe estar en la ruta por defecto, en un fallback o en ninguna.
- Generación multi-query: unión de Recall@k de N rewrites frente a una sola consulta. Haz un sweep de N y elige un punto de tu frontera recall–latencia. Implementaciones:
MultiQueryRetrieverde LangChain yQueryFusionRetrieverde LlamaIndex. - Precisión de clasificación de intención: precision/recall/F1 estándar por intención (calcula con
sklearn.metrics.classification_report), pero la métrica operativa es la corrección del routing: ¿se invoca el pipeline downstream adecuado? - Routing adaptativo: Adaptive-RAG (Jeong et al., NAACL 2024) defiende que no todas las consultas merecen la misma estrategia de retrieval. Registra la precisión del router como un problema de clasificación frente a un conjunto etiquetado de «no necesita retrieval / one-shot / iterativa».
Métricas de retrieval
Estas son las métricas baseline. Si no las registras, no puedes saber si el retrieval mejora.
| Métrica | Qué mide | Cuándo utilizarla |
|---|---|---|
| Recall@k | fracción de documentos relevantes de una consulta devueltos en los primeros k | cuando importa no perder ninguna parte del conjunto relevante |
| Precision@k | porcentaje de los top-k que son relevantes | útil cuando la ventana de contexto es el cuello de botella |
| MRR | media de 1/rango del primer documento relevante | cuando los usuarios solo miran el top-1 o el top-3 |
| nDCG@k | ganancia descontada por posición y ponderada por grados de relevancia | métrica estándar de retrieval para relevancia graduada |
| MAP | media entre consultas de la precision media | cuando importa toda la lista ordenada |
| Hit Rate@k | si aparece al menos un documento relevante en los primeros k | promedia el resultado binario entre consultas como comprobación rápida |
| Coverage | porcentaje de documentos gold recuperados alguna vez en todas las consultas | detecta carencias sistemáticas del índice |
Las fórmulas, como referencia (relevancia binaria con conjunto relevante para la consulta , y si el documento recuperado en la posición pertenece a ):
Para relevancia graduada, ; el nDCG binario es el caso particular utilizado en el código siguiente. MAP es la media entre consultas de . Consulta Manning, Raghavan, Schütze, Introduction to Information Retrieval, capítulo 8, para las derivaciones.
Para código de producción, utiliza ranx, pytrec_eval o ir_measures: implementan toda la familia de métricas TREC y gestionan correctamente la relevancia graduada. Define los objetivos del release a partir de un golden set realista, la calidad downstream de la respuesta y el coste de un fallo. No heredes los umbrales de un tutorial.
El harness de pruebas para estas métricas es corto. Puedes ejecutarlo desde un notebook antes incluso de elegir una base de datos vectorial.
from math import log2
from statistics import mean
# synthetic gold set: query_id -> set of relevant doc ids
gold = {
"q1": {"d3"},
"q2": {"d7", "d2"},
"q3": {"d11"},
"q4": {"d5"},
}
# ranked retrieval results: query_id -> ranked list of doc ids (top-10)
runs = {
"q1": ["d8", "d3", "d1", "d4", "d2", "d9", "d6", "d10", "d12", "d13"],
"q2": ["d2", "d6", "d4", "d7", "d1", "d3", "d8", "d11", "d5", "d9"],
"q3": ["d11", "d2", "d3", "d4", "d1", "d6", "d7", "d8", "d10", "d12"],
"q4": ["d1", "d2", "d3", "d6", "d8", "d9", "d10", "d12", "d13", "d14"],
}
def recall_at_k(ranked, gold_set, k):
if not gold_set:
return 0.0
hit = sum(1 for d in ranked[:k] if d in gold_set)
return hit / len(gold_set)
def reciprocal_rank(ranked, gold_set):
# MRR contribution per query: 1/rank of the first relevant doc.
for rank, d in enumerate(ranked, start=1):
if d in gold_set:
return 1.0 / rank
return 0.0
def ndcg_at_k(ranked, gold_set, k):
# binary relevance: rel ∈ {0, 1}
gains = [1.0 if d in gold_set else 0.0 for d in ranked[:k]]
dcg = sum(g / log2(i + 2) for i, g in enumerate(gains))
# ideal DCG: all gold docs ranked first, capped by k
n_gold_in_topk = min(k, len(gold_set))
idcg = sum(1.0 / log2(i + 2) for i in range(n_gold_in_topk))
return dcg / idcg if idcg else 0.0
K = 5
print(f"Recall@{K}: {mean(recall_at_k(runs[q], gold[q], K) for q in gold):.3f}")
print(f"MRR: {mean(reciprocal_rank(runs[q], gold[q]) for q in gold):.3f}")
print(f"nDCG@{K}: {mean(ndcg_at_k(runs[q], gold[q], K) for q in gold):.3f}")
# Recall@5: 0.750
# MRR: 0.625
# nDCG@5: 0.627
Ese es tu gate de CI de retrieval. Conéctalo a un subconjunto rápido basado en cobertura para cada PR y ejecuta el golden set completo en el gate de release más lento. Bloquea el merge cuando una métrica preregistrada cruce su presupuesto de regresión.
El repositorio complementario fija los números exactos anteriores (Recall@5 = 0.750, MRR = 0.625, nDCG@5 = 0.627) como una prueba unitaria en tests/test_retrieval_metrics.py; el notebook 01 hace un sweep de Recall@k / MRR / nDCG sobre un índice real de SciFact, y el harness con forma de producción está en evaluation/retrieval.py.
Retrieval híbrido y reciprocal rank fusion
BM25 es un scorer léxico sparse que combina coincidencia exacta de términos, ponderación de términos y normalización por longitud. Está disponible en rank_bm25, Elasticsearch, OpenSearch y la mayoría de motores de búsqueda.
Reciprocal Rank Fusion (Cormack, Clarke y Buettcher, SIGIR 2009) combina rankings BM25 y dense por posición. La configuración original k=60 es un baseline útil. RRF no depende de las puntuaciones, lo que evita la normalización entre carriles necesaria con la interpolación lineal. Con un conjunto etiquetado lo bastante grande para estimar un delta estable, prueba también una combinación convexa y ajusta α.
Mi hipótesis es que el retrieval híbrido más un reranker cross-encoder pueden ayudar en corpus técnicos, de logs y de código. La ganancia puede ser pequeña en corpus muy semánticos. Mide frente a los carriles dense-only y sparse-only, porque una configuración de fusión deficiente puede rendir peor que cualquiera de sus entradas. El notebook de SciFact complementario es una prueba acotada, no un resultado general.
La implementación cabe en unas pocas líneas.
from collections import defaultdict
# two retrieval lanes: dense embeddings and BM25.
dense = ["d3", "d7", "d1", "d4", "d2", "d9", "d10"]
sparse = ["d2", "d3", "d8", "d1", "d11", "d4", "d6"]
def rrf(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
"""Reciprocal Rank Fusion (Cormack et al., SIGIR 2009).
score(d) = sum over rankings of 1 / (k + rank(d))
Score-agnostic: only rank position matters. k=60 is the canonical default.
"""
scores: dict[str, float] = defaultdict(float)
for ranking in rankings:
for rank, doc in enumerate(ranking, start=1):
scores[doc] += 1.0 / (k + rank)
return sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
fused = rrf([dense, sparse], k=60)
for doc, score in fused[:5]:
print(f"{doc} score={score:.5f}")
# d3 score=0.03252 <- rank 1 dense, rank 2 sparse
# d2 score=0.03178 <- rank 5 dense, rank 1 sparse
# d1 score=0.03150
Observa lo que RRF no hace: nunca consulta las puntuaciones de similitud brutas. Un retriever dense que devuelve coseno 0,98 y un carril BM25 con puntuación 17,4 no son comparables directamente. Si los normalizas con z-scores o escalado min-max, puedes acabar favoreciendo el carril con mayor varianza en ese batch.
RRF solo utiliza el rango. Si un retriever coloca un documento en la posición 2, ese voto vale 1 / (60 + 2), independientemente de la puntuación bruta que lo haya producido.
Hybrid + RRF en SciFact: el notebook 02 compara dense frente a BM25 y RRF con deltas por consulta. El fuser con forma de producción está en retrieval/hybrid_rrf.py; tests/test_rrf.py fija el orden canónico de d3 / d2 / d1 en k=60.
Reranking
- ΔnDCG / ΔMRR: uplift frente a no hacer rerank, sobre tu golden set y a la profundidad que realmente utiliza la aplicación. Calcula las métricas de retrieval con y sin reranker sobre conjuntos de candidatos idénticos.
- Cross-encoder frente a bi-encoder: un bi-encoder genera embeddings de la consulta y del documento de forma independiente (un vector por lado) y puntúa mediante producto escalar; un cross-encoder concatena consulta y documento y ejecuta un único forward pass que atiende conjuntamente a ambos. Los cross-encoders intercambian un forward pass por candidato por una interacción consulta–documento más rica. Implementación de referencia:
sentence-transformersCrossEncoder. Haz el benchmark de relevancia y latencia indicando hardware, batch size y profundidad de candidatos; no traslades el resultado de un modelo o servicio gestionado a otro entorno. - Listwise frente a pointwise: pointwise puntúa cada par (consulta, documento) de forma independiente; listwise puntúa conjuntamente toda la lista de candidatos para que el modelo pueda compararlos. Evalúa ambos sobre los mismos conjuntos de candidatos. Calibra cualquier umbral de puntuación por modelo y corpus, en lugar de tratar un ejemplo publicado como portable.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
query = "How do I rotate database credentials in production?"
candidates = [
"Production database credentials are rotated via Vault every 30 days.",
"The new logo was unveiled at the all-hands meeting.",
"To rotate prod DB creds, run the `rotate-secrets` GitHub Action.",
]
scores = reranker.predict([(query, c) for c in candidates])
ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])
for doc, score in ranked:
print(f"{score:+.3f} {doc}")
Un reranker suele ayudar a un pipeline RAG básico, pero no es una mejora garantizada. Mide su ΔPrecision@1 y ΔnDCG en tu golden set y consérvalo solo si la ganancia supera su presupuesto de latencia y coste. Compara esa ganancia medida con cambios de retrieval más pequeños antes de elegir la siguiente optimización.
ΔnDCG y ΔPrecision@1 de un cross-encoder sobre SciFact: notebook 03; módulo: retrieval/reranker.py.
Construcción del contexto y lost-in-the-middle
Muchos fallos de «buen retrieval, mala respuesta» empiezan en la construcción del contexto.
- Relevancia del contexto: puntuación de relevancia por chunk de RAGAS
ContextRelevanceo de un cross-encoder, agregada como media y como porcentaje de chunks por debajo de un umbral. - Utilización del contexto: de los chunks incluidos en el contexto, ¿cuántos se citaron o utilizaron realmente en la respuesta? Calcula sobre una muestra etiquetada. Define el umbral operativo a partir de la calidad de la respuesta y el coste en tokens, no mediante un porcentaje universal.
- Detección de lost-in-the-middle: evaluación sintética en la que colocas el chunk gold en las posiciones {primera, intermedia, última} de un contexto largo y mides la corrección de la respuesta. El estudio citado de Liu et al. (TACL 2023) informa de una degradación en forma de U bajo sus condiciones de contexto largo. Trata de probar el mismo patrón en un modelo actual como una hipótesis. Mitigaciones: haz rerank y después reordena los top-k para que el chunk con mayor puntuación quede primero o último (
LongContextReorderde LangChain hace exactamente esto), o comprime agresivamente los chunks intermedios. Mide con una evaluación estratificada por posición, no solo con una puntuación agregada. Hay una evaluación ejecutable y explicada, estratificada por posición, en el notebook 06 (módulo:evaluation/lost_in_middle.py). - Compresión del contexto: informa de la ratio de compresión (tokens de entrada / tokens de salida) junto con la corrección de la respuesta. Entre las herramientas están
ContextualCompressionRetrieverde LangChain y LongLLMLingua. Define de antemano la mayor pérdida de corrección aceptable según el riesgo de la aplicación y el presupuesto de tokens, y rechaza las configuraciones que la superen.
Parte 5: Tasa de false-exclusion del filtro
Esta métrica tiene una sección propia porque las puntuaciones agregadas de retrieval no pueden atribuir un miss al filtro.
Un filtro hard de metadatos como tenant_id = X AND product = Y AND locale = en-US puede reducir el recall efectivo a cero. Recall@k correctamente implementado detecta la pérdida porque su denominador sigue siendo el conjunto original de documentos relevantes. No indica si el fallo lo causó el filtro, el retriever o el ranker. Faithfulness evalúa las claims frente al contexto recuperado. Puede seguir puntuando claims respaldadas por ese contexto incompleto, pero no puede diagnosticar la causa en el filtro o el retrieval. Una negativa vacía puede no producir statements y NaN, según la implementación; no la trates como evidencia de que faithfulness aprobó la negativa.
La rama resaltada es el fallo habitual: el documento correcto existe, pero el filtro lo elimina antes del retrieval. Recall@k registra la caída; solo la tasa de exclusión la atribuye al predicado.
La métrica
filter_false_exclusion_rate =
(# queries where all gold docs were excluded by metadata filter) /
(# queries with at least one gold doc)
Esta definición a nivel de consulta cuenta las exclusiones catastróficas: ningún documento relevante sobrevive. Para consultas con varios gold, Recall@k estándar sigue mostrando la pérdida parcial; añade una tasa de exclusión por documento si ese límite es relevante. Para calcular cualquiera de las dos tasas necesitas (a) IDs de documento ground truth para cada consulta de evaluación y (b) instrumentación que registre los predicados de filtro aplicados, no solo los resultados finales. Define el objetivo según el coste de excluir una respuesta válida y el intervalo de confianza de tu muestra de producción.
Esta es una implementación funcional. Compara el recall estándar correcto con un evaluador inválido que redefine la relevancia después del filtrado.
# A small worked example where hard filters remove relevant documents.
docs = [
{"id": "d1", "tenant": "acme", "locale": "en-US"},
{"id": "d2", "tenant": "acme", "locale": "en-GB"},
{"id": "d3", "tenant": "globex", "locale": "en-US"},
{"id": "d4", "tenant": "acme", "locale": "en-US"},
{"id": "d5", "tenant": "acme", "locale": "de-DE"},
]
queries = [
# the gold doc lives in en-GB but the dynamic filter forced en-US
{"qid": "q1", "gold": {"d2"}, "filter": lambda d: d["locale"] == "en-US"},
# the gold doc is correctly within the tenant filter
{"qid": "q2", "gold": {"d4"}, "filter": lambda d: d["tenant"] == "acme"},
# the gold doc is in a different tenant and gets dropped
{"qid": "q3", "gold": {"d3"}, "filter": lambda d: d["tenant"] == "acme"},
# the gold doc passes the filter (de-DE locale match)
{"qid": "q4", "gold": {"d5"}, "filter": lambda d: d["locale"] == "de-DE"},
]
def filter_false_exclusion_rate(queries, docs):
n_with_gold, n_excluded = 0, 0
for q in queries:
if not q["gold"]:
continue
n_with_gold += 1
survivors = {d["id"] for d in docs if q["filter"](d)}
if not (q["gold"] & survivors):
n_excluded += 1
return n_excluded / n_with_gold if n_with_gold else 0.0
rate = filter_false_exclusion_rate(queries, docs)
print(f"filter_false_exclusion_rate = {rate:.2%}")
# filter_false_exclusion_rate = 50.00%
# Correct Recall@k keeps the original gold set as its denominator.
def standard_recall_at_k(queries, docs, k=10):
recalls = []
for q in queries:
# demo only: the survivor set stands in for a ranked run.
# A real harness ranks the survivors first, then slices to k.
survivors = [d for d in docs if q["filter"](d)][:k]
survivor_ids = {d["id"] for d in survivors}
recalls.append(len(q["gold"] & survivor_ids) / len(q["gold"]))
return sum(recalls) / len(recalls) if recalls else 0.0
print(f"standard recall@10 = {standard_recall_at_k(queries, docs):.2%}")
# standard recall@10 = 50.00%
# INVALID: rebuilding the gold set after filtering changes the question.
# It drops queries whose relevant documents did not survive, then scores 100%.
def invalid_recall_over_filtered_gold(queries, docs, k=10):
recalls = []
all_doc_ids = {d["id"] for d in docs}
for q in queries:
all_survivors = {d["id"] for d in docs if q["filter"](d)}
filtered_gold = q["gold"] & all_doc_ids & all_survivors
if not filtered_gold:
continue
top_k_ids = set(list(all_survivors)[:k])
recalls.append(len(filtered_gold & top_k_ids) / len(filtered_gold))
return sum(recalls) / len(recalls) if recalls else 0.0
invalid = invalid_recall_over_filtered_gold(queries, docs)
print(f"INVALID recall (filtered gold) = {invalid:.2%}")
# INVALID recall (filtered gold) = 100.00%
assert rate == 0.5
assert standard_recall_at_k(queries, docs) == 0.5
assert invalid == 1.0
La mitad de las consultas pierde su documento gold por culpa del filtro, así que Recall@10 correcto baja al 50 %. Esta puntuación detecta el síntoma, pero no puede atribuirlo. La tasa de false-exclusion muestra que el predicado eliminó dos respuestas antes de que se ejecutara el retriever. El evaluador deliberadamente inválido informa del 100 % solo porque descarta esos fallos de su conjunto gold. Ningún modelo puede recuperar un documento que se ha filtrado.
La tasa del 50 % anterior se reproduce como prueba unitaria en el repositorio complementario: tests/test_filter_exclusion.py::test_50_percent_exclusion_rate. El Notebook 04 lo ejecuta sobre SciFact con metadatos sintéticos para que puedas observar cómo un filtro real deja el recall a cero; la métrica de runtime (con el companion de precision/recall del predicado) está en evaluation/filter_exclusion.py.
Métrica complementaria: precision y recall del predicado
Cuando el filtrado es dinámico —por ejemplo, cuando un LLM extrae predicados de filtro de la consulta—, trata el extractor de predicados como un modelo de clasificación y evalúalo como tal. Mide la precision y el recall de los predicados frente a un conjunto etiquetado de pares (query, correct predicate). La tasa de error de predicados no se traduce directamente en la misma pérdida puntual de recall del retrieval; mide con qué frecuencia esos errores excluyen un documento gold. Una vez que un filtro hard elimina el documento gold, ningún reranking puede ayudar.
Boost soft frente a filtro hard
Esta métrica obliga a tomar una decisión de diseño. Usa filtros hard cuando la corrección es binaria: jurisdicción legal, límites de ACL, publicado frente a borrador. Usa boosts soft cuando la relevancia es graduada: preferencia regional, recencia o versión. Sin medir la tasa de exclusión, es difícil detectar una elección incorrecta.
La regla de decisión, de forma medible:
For each filter predicate F:
hard_recall_F = retrieval_recall@k with F as a hard filter
soft_recall_F = retrieval_recall@k with F as a +0.X rerank boost
hard_precision = relevant_in_top_k / k under hard filter
soft_precision = relevant_in_top_k / k under soft boost
exclusion_rate = % of queries where the gold doc was filtered out (hard)
Use hard filter only if exclusion_rate < ε AND hard_precision >> soft_precision.
Otherwise prefer soft boost.
Elige ε según el daño de una falsa exclusión, el beneficio de una mayor precision y el tamaño de la muestra de evaluación. Está previsto publicar un artículo específico sobre este equilibrio; consulta los follow-ups al final.
Parte 6: Evaluación de la generación
Las métricas de retrieval te dicen que el sistema podría responder correctamente. No te dicen que lo haya hecho. Las métricas de generación cubren esa brecha.
Faithfulness y groundedness
Faithfulness de RAGAS descompone la respuesta en claims atómicas (afirmaciones factuales breves y autosuficientes) y verifica cada una frente al contexto recuperado mediante un LLM judge:
El porcentaje de claims respaldadas es la puntuación. La estructura es más útil que cualquier número aislado porque indica qué claims no están respaldadas. El código de producción está en el paquete ragas. Lo siguiente es un esquema heredado de una API que no se ejecuta para RAGAS 0.4.3, no un programa para copiar y pegar. Para ejecutarlo, fija ragas==0.4.3, instala un cliente del proveedor, configura sus credenciales, crea un LLM de RAGAS respaldado por el proveedor y pásalo como evaluator_llm. La configuración queda intencionadamente fuera del bloque y depende del proveedor; la función recibe el objeto configurado como argumento en lugar de dar a entender que existe un valor por defecto. Para código nuevo, utiliza la API actual basada en collections, que RAGAS documenta como ruta de migración desde la API heredada de métricas:
# No-run: legacy RAGAS 0.4.3 API shape.
# Before calling this function, configure a provider-backed RAGAS LLM.
# For example, with the provider credentials already set:
# from openai import AsyncOpenAI
# from ragas.llms import llm_factory
# evaluator_llm = llm_factory("gpt-4o-mini", client=AsyncOpenAI())
from ragas import EvaluationDataset, evaluate
from ragas.metrics import (
Faithfulness,
LLMContextPrecisionWithoutReference,
ResponseRelevancy,
)
def run_legacy_ragas_043(evaluator_llm):
dataset = EvaluationDataset.from_list([
{
"user_input": "How many moons does Mars have?",
"response": "Mars has two moons, Phobos and Deimos.",
"retrieved_contexts": ["Mars has two moons named Phobos and Deimos."],
"reference": "Mars has two moons.",
}
])
return evaluate(
dataset,
metrics=[Faithfulness(), ResponseRelevancy(), LLMContextPrecisionWithoutReference()],
llm=evaluator_llm,
)
RAGAS cambió el nombre de estos campos en 0.2 (question → user_input, answer → response, contexts → retrieved_contexts, ground_truth → reference). El código escrito contra 0.1 falla de forma confusa: las claves antiguas se descartan en lugar de rechazarse, por lo que el error identifica las nuevas columnas como ausentes.
A continuación se muestra el mismo loop desplegado con un judge determinista de sustitución para que puedas ver la forma completa de extremo a extremo.
def extract_claims(answer: str) -> list[str]:
# Production: an LLM call that decomposes the answer.
# Demo: split on sentence-final punctuation.
return [c.strip() for c in answer.replace("?", ".").replace("!", ".").split(".") if c.strip()]
def verify_claim(claim: str, context: str) -> bool:
# Production: an NLI (natural-language inference) model or LLM judge.
# Demo: a deterministic stand-in so the example runs offline.
entailed_pairs = {
"Mars has two moons": True,
"Phobos and Deimos orbit Mars": True,
"Mars has a thick atmosphere": False, # unsupported by context
"Curiosity landed in 2012": True,
}
for k, v in entailed_pairs.items():
if k.lower() in claim.lower() or claim.lower() in k.lower():
return v
words = [w.lower() for w in claim.split() if len(w) > 3]
return all(w in context.lower() for w in words) if words else False
context = (
"Mars has two moons, Phobos and Deimos. NASA's Curiosity rover "
"landed on Mars in 2012."
)
answer = (
"Mars has two moons. Phobos and Deimos orbit Mars. "
"Mars has a thick atmosphere. Curiosity landed in 2012."
)
claims = extract_claims(answer)
verdicts = [(c, verify_claim(c, context)) for c in claims]
faithfulness = sum(1 for _, ok in verdicts if ok) / len(verdicts)
for c, ok in verdicts:
print(f" [{'✓' if ok else '✗'}] {c}")
print(f"faithfulness = {faithfulness:.2f}")
# faithfulness = 0.75 (one unsupported claim about the atmosphere)
La estructura importa. En producción, verify_claim se convierte en un modelo NLI o una llamada a un LLM. El resto del harness permanece igual: extraer, verificar y agregar.
Extracción y verificación end-to-end de claims sobre respuestas generadas de SciFact: notebook 05; módulo: evaluation/faithfulness.py. El repositorio ejecuta el mismo loop con dos familias de judges —el propio modelo del generator y un judge de otra familia (RAG_EVALS_JUDGE_MODEL)—, además de un baseline léxico determinista, para que puedas ver dónde discrepan las familias.
Una alternativa específica a LLM-as-judge es HHEM-2.1-Open (Hughes Hallucination Evaluation Model, Vectara), un classifier fine-tuned para detectar alucinaciones. Su model card documenta el checkpoint, la puntuación bruta de 0 a 1 que emite y los resultados de balanced accuracy en AggreFact y RAGTruth. No publica un umbral de decisión por defecto, así que te corresponde elegirlo. Trata esos datos como evidencia de la model card, no como una garantía para tu corpus: calibra el umbral con etiquetas locales y compáralo con el judge que hayas elegido antes del despliegue.
Evaluación de hechos atómicos
FActScore (Min et al., EMNLP 2023) descompone las generaciones largas en hechos atómicos, recupera evidencia para cada hecho, etiqueta cada supported / not-supported y comunica la fracción respaldada:
Implementación de referencia: shmsw25/FActScore. Funciona bien para biografías, resúmenes y otras salidas largas. Precauciones: los hechos triviales repetitivos pueden inflar la puntuación, y los ataques de «MontageLie» (hechos verdaderos en un orden engañoso) pueden vencerla. VeriScore gestiona claims con modificadores necesarios; el filtro Core ayuda a evitar el fact-padding.
Precisión de las citas
Registra la precision de las citas (los spans citados respaldan realmente la claim) y el recall de las citas (las claims que deberían citarse se citan):
El TREC 2024 RAG Track define un protocolo reproducible de evaluación del soporte. Thakur et al. (SIGIR 2025) informan de que GPT-4o coincide con los jueces humanos el 56 % de las veces en una evaluación manual desde cero, cifra que sube al 72 % con post-edición de las predicciones del LLM. Esto resulta útil como multiplicador de fuerza en sus condiciones, no como sustituto de la evaluación humana en contextos de alto riesgo. Como aproximación automatizada, ALCE (Gao et al., EMNLP 2023) implementa precision/recall de citas con verificación basada en NLI.
Corrección, completitud y negativa de la respuesta
- Corrección de la respuesta frente al ground truth: cuando lo tengas, exact match o token-F1 para tareas de respuesta corta (
evaluate.load("squad")); similitud semántica para respuestas abiertas (bert-score, coseno de embeddings mediantesentence-transformersoAnswerCorrectnessde RAGAS). - Completitud mediante nuggets: un «nugget» es una pieza atómica de información que debe contener cualquier respuesta correcta (por ejemplo, para «¿Cuándo se fundó la empresa?», los nuggets podrían ser
{year: 1994, founder: Jane Doe}). El AutoNuggetizer de TREC extrae de una referencia los nuggets gold de una respuesta correcta y puntúa qué fracción cubre el sistema; mostró una fuerte correlación con la evaluación manual en 21 temas × 45 runs en el TREC 2024. - Comportamiento de negativa: las consultas sin respuesta en el corpus deben producir abstención, no alucinaciones. Registra la precision de abstención (negativas correctas) y el recall de abstención (consultas fuera de alcance que activaron una negativa). NoMIRACL es el benchmark público; en tu propio dominio, etiqueta un slice de consultas fuera de alcance y registra la precisión de abstención.
Verificación posterior a la generación
Las mejoras de fiabilidad más baratas suelen proceder de comprobaciones deterministas posteriores, no de modelos más grandes.
- Comprobación de grounding de entidades: toda entidad nombrada en la respuesta debe aparecer en el contexto recuperado —o poder derivarse de él—. Una comprobación sencilla con regex + exact-match (o
entsdespaCycontra una cadena de contexto normalizada) detecta una fracción sorprendentemente grande de las alucinaciones. - Verificación de claims: extrae las claims, ejecuta NLI contra el contexto y falla o marca cualquiera que quede por debajo del umbral. Modelos NLI para faithfulness:
cross-encoder/nli-deberta-v3-large,MoritzLaurer/DeBERTa-v3-large-mnli-fever-anli-ling-wanli. Añade latencia. Merece la pena en dominios de alto riesgo. - Self-consistency (Wang et al., ICLR 2023): muestrea varias generaciones con temperature > 0; informa de la tasa de acuerdo (por ejemplo, la proporción de generaciones que coincide con la respuesta modal o el BERTScore por pares); elige el número de muestras a partir de la curva estabilidad–coste y marca para revisión humana las respuestas con bajo acuerdo.
- Calibración de confianza: recopila la confianza verbalizada («¿Qué confianza tienes, de 0 a 1?») y compárala con la corrección real en el conjunto de evaluación. Representa una curva de calibración e informa del Expected Calibration Error: , donde son los bins de confianza. Implementaciones:
netcal,torchmetrics.CalibrationError. Un modelo que informa de una confianza de 0,9 debería acertar aproximadamente en el 90 % de los casos comparables; mide la diferencia en lugar de asumir la calibración.
Parte 7: Evaluación de RAG basado en ontologías
Las métricas estándar anteriores cubren el RAG sobre corpus abiertos. Si tu RAG recupera contra una ontología estructurada, una taxonomía o un grafo de conocimiento, esas métricas son necesarias, pero no suficientes. Algunos ejemplos son productos de un catálogo, condiciones de SNOMED, componentes de una BOM y técnicas de seguridad de MITRE ATT&CK. También necesitas medir la capa de ontología.
Precisión del entity linking
La primera tarea consiste en mapear una mención de la consulta a una entidad de la ontología («Aspirin» → wikidata:Q18216, «the 737» → aircraft:Boeing_737).
- Precision/recall/F1 a nivel de mención: estándar, frente a spans de mención gold (calcula con
seqevalo un comparador de conjuntos de spans). - Precisión de desambiguación: de las menciones detectadas correctamente, ¿qué fracción se asigna al ID de entidad correcto? Las referencias públicas incluyen ReFinED, REL y GENRE; benchmarks como AIDA-CoNLL y BELB muestran que los resultados varían según el sistema y el dominio.
- Gestión de NIL: precision/recall sobre «entidad no presente en la ontología». Mide por separado el over-linking a entidades cercanas pero incorrectas y la abstención correcta.
Evaluación consciente de la jerarquía
La precisión plana trata «predecir Sedan cuando la verdad es Hatchback» igual que «predecir Sedan cuando la verdad es Submarine». Esos errores no son equivalentes.
-
Precision/recall/F1 jerárquicos (Kosmopoulos et al., 2015): asigna crédito a ancestros y descendientes en el DAG de la ontología. Con , el nodo predicho y todos sus ancestros, y , el nodo verdadero y todos sus ancestros:
Impleméntalo con
networkxsobre el grafo de la ontología: añade los ancestros a cada predicción y etiqueta, y calcula las intersecciones de conjuntos anteriores. -
Similitud de Wu-Palmer entre la entidad predicha y la gold en la taxonomía (Wu & Palmer, 1994):
donde LCA es el ancestro común más bajo de la taxonomía. Está disponible directamente en NLTK para WordNet (
from nltk.corpus import wordnet as wn; wn.synset("car.n.01").wup_similarity(wn.synset("truck.n.01"))); para taxonomías personalizadas, calcula el LCA connetworkx. -
Tasa de confusión entre hermanos y padres: registra por separado las confusiones con hermanos, padres e hijos:
count_sibling / total_errors,count_parent / total_errors,count_descendant / total_errors. Utiliza ejemplos revisados para comprobar si los errores entre hermanos proceden de menciones ambiguas o si los errores con padres proceden de una generalización excesiva.
Tasa de false-exclusion del filtro (de nuevo, ahora crítica)
En sistemas basados en ontologías, los filtros hard suelen proceder de la propia ontología («recuperar solo documentos etiquetados con la categoría X»). La métrica de tasa de exclusión (definida en la Parte 5) se convierte en una señal primaria de corrección. Una predicción de categoría incorrecta puede reducir el recall a cero; la tasa de exclusión atribuye esa pérdida al filtro.
Conformidad de la generación restringida
Cuando la salida debe cumplir una ontología —cada nombre de entidad de la respuesta debe ser un miembro válido de la ontología y cada predicado debe proceder de un vocabulario cerrado—, mide:
- Tasa de validez del schema: porcentaje de salidas que se parsean y validan contra el schema de la ontología. Valida con
jsonschemaopydantic. JSONSchemaBench es el benchmark público para structured output general; para schemas específicos de una ontología, crea tu propio validator. - Conformidad del vocabulario: porcentaje de entidades nombradas en la salida que son IDs válidos de la ontología; basta una comprobación de pertenencia a un conjunto contra el vocabulario cerrado.
- Conformidad semántica: una salida sintácticamente válida aún puede elegir una entidad incorrecta pero válida. Combina la conformidad con la corrección downstream de la respuesta.
Los frameworks de constrained decoding (Outlines, XGrammar, Guidance, OpenAI Structured Outputs) están diseñados para imponer la validez del schema. JSONSchemaBench compara eficiencia, cobertura y calidad entre implementaciones. Vuelve a ejecutar los casos que coincidan con tus schemas y backend de serving, porque la cobertura y la latencia dependen de ambos.
Auditabilidad
Para sistemas basados en ontologías cuyas respuestas se revisan:
- Completitud de citas: porcentaje de claims factuales con al menos una cita verificable.
- Profundidad de procedencia: porcentaje de citas que llegan hasta un documento fuente con un ID estable, no solo hasta un hash de chunk.
- Tasa de reproducibilidad: al volver a ejecutar la misma consulta con un snapshot fijo, se obtiene la misma respuesta. Fija la versión del modelo, el runtime, la configuración de decoding y la seed, y define la tasa de repetición necesaria según las necesidades de auditabilidad del workflow. Temperature cero por sí sola no garantiza el determinismo. Un miss puede proceder de la generación, del runtime de serving o de cualquier etapa upstream.
Parte 8: Evaluación a nivel de sistema
Calidad holística de la respuesta
- LLM-as-judge (Zheng et al., NeurIPS 2023): enfoque escalable de evaluación basada en modelos. G-Eval (Liu et al., EMNLP 2023) deriva una rúbrica a partir de un criterio en lenguaje natural y puntúa mediante una salida ponderada por log-prob. El acuerdo depende del judge, la tarea, el prompt y el conjunto de calibración.
- Preferencia por pares: presenta al judge la respuesta A frente a la respuesta B y registra su preferencia. Esto evita los problemas de calibración de las puntuaciones absolutas. MT-Bench informó de un acuerdo del judge GPT-4 superior al 80 % tanto con las preferencias humanas como con el acuerdo entre humanos bajo sus condiciones de benchmark; no traslades esa tasa a otro dominio sin calibrarla.
LLM-as-judge presenta sesgos reales:
- Sesgo de posición: los judges prefieren la primera o la segunda respuesta independientemente de su calidad. Mitigación: aleatoriza el orden o ejecuta ambos órdenes y promedia.
- Sesgo de verbosity: los judges pueden confundir longitud con calidad. Un estudio controlado de 2026 detectó un comportamiento heterogéneo en pares de expansión. Tres judges prefirieron respuestas más largas, Claude prefirió respuestas concisas y GPT-4o fue aproximadamente neutral. Los cinco rindieron bien en controles de truncamiento. Esos resultados dependen del benchmark: indica al judge cómo debe tratar la completitud y el relleno, e informa del rendimiento controlado por longitud con tu propia rúbrica.
- Sesgo de autopreferencia: GPT-4 prefiere las salidas de GPT-4; el sesgo se correlaciona con la perplexity de la salida (los judges prefieren texto que les resulta familiar). Mitigación: utiliza una familia de judges distinta de la del sistema evaluado. No utilices un modelo para juzgarse a sí mismo.
Receta práctica: selecciona un judge con datos de calibración etiquetados por humanos, aleatoriza el orden de las respuestas, oculta las identidades de los modelos e indica la política de longitud en la rúbrica. Repite casos solo cuando las muestras adicionales reduzcan materialmente la incertidumbre. En evaluaciones de alto riesgo, compara judges de familias de modelos distintas y analiza los desacuerdos frente a etiquetas humanas.
Schema-Guided Reasoning para judges
La salida libre es una fuente de variación en las ejecuciones del judge. Dos ejecuciones sobre la misma respuesta pueden organizar la rúbrica de forma distinta y producir puntuaciones diferentes. Schema-Guided Reasoning (SGR) hace explícita esa rúbrica: define las etapas de evaluación como un schema de Pydantic y utiliza structured output restringido mediante Outlines, XGrammar, vLLM structured outputs u OpenAI response_format para que cada ejecución devuelva los mismos campos en el mismo orden.
En la evaluación de RAG, el schema descompone el juicio en campos explícitos y auditables, en lugar de permitir que el modelo salte directamente a un número:
from pydantic import BaseModel, Field
from typing import Literal
class FaithfulnessJudgment(BaseModel):
extracted_claims: list[str] = Field(
description="Atomic factual claims in the answer, one per item."
)
supported_claims: list[str] = Field(
description="Subset of extracted_claims that are entailed by the context."
)
unsupported_claims: list[str] = Field(
description="Subset that is NOT entailed by the context."
)
failure_mode: Literal[
"none", "fabrication", "overgeneralization", "wrong_entity", "stale_fact"
]
score: float = Field(ge=0.0, le=1.0)
rationale: str
Los campos estructurados permiten recuperar la puntuación como len(supported) / len(extracted) y muestran exactamente en qué claims discreparon dos judges. El modelo de Pydantic también hace visible un cambio de rúbrica como un diff de código. La salida restringida garantiza la forma, no un veredicto sin sesgos; por tanto, siguen siendo aplicables la aleatorización de posición, los judges de familias distintas y la calibración humana.
Esto funciona para cualquier judge basado en rúbricas, no solo para faithfulness. La preferencia por pares, el soporte de citas y la corrección de negativas también se benefician del mismo tratamiento.
En el notebook 07 hay un harness de G-Eval / pairwise / sesgo de posición / judge de familias distintas; módulo: evaluation/llm_judge.py. El sweep del benchmark (make benchmark en el repositorio) conecta tres modelos (gpt-5-mini, claude-haiku-4-5, gemini-2.5-flash) en un A/B pairwise con judge rotatorio, de forma que cada modelo juzga a los otros dos y la autopreferencia aparece como un número.
Latencia y coste
- p50, p95 y p99 en cada etapa del pipeline. Elige el percentil del SLO y el umbral de alerta a partir del journey del usuario, el volumen de tráfico y el error budget.
- Time-to-first-token frente al tiempo total de generación. A los usuarios les importa TTFT en una UX con streaming.
- Desglose por etapa: retrieval, reranking, generación y postprocesado. Utiliza la traza para localizar la cola en lugar de asumir qué etapa la causó; registra el dispositivo del reranker y el batch size al comparar ejecuciones.
- Total $/query = embedding + retrieval + rerank + generación + almacenamiento amortizado. Registra p50 y p99; el long tail es donde se consume el presupuesto.
- Tasas de cache hit en los niveles de embedding cache, retrieval cache y KV-cache. Define objetivos separados según la repetición observada, la política de invalidación y el coste evitado en cada capa.
El desglose p50/p95/p99 por etapa está integrado en el notebook 08 y en el runner de evaluation/latency.py; el informe del benchmark combina latencia y faithfulness en una única matriz que puedes volver a ejecutar con make benchmark.
Pruebas A/B
- Unidad de aleatorización: elige la unidad a partir del estimand, el carryover y la interferencia. Usa asignación por usuario o sesión cuando la exposición repetida pueda cambiar el comportamiento o crear una UX incoherente. La asignación por consulta solo es defendible cuando esos efectos son despreciables y el análisis modela las observaciones repetidas.
- Métricas primarias, de guardrail y exploratorias: preregístralas. Elige la medida primaria a partir del resultado de producto; los proxies de satisfacción incluyen thumbs, regeneraciones y dwell. Trata latencia y coste como guardrails cuando limiten la experiencia.
- Tamaño de muestra: haz un power analysis antes del lanzamiento, a partir del efecto mínimo que merezca la pena detectar, la varianza baseline, la unidad de asignación y la regla de parada.
Parte 9: Construcción del conjunto de pruebas
Una métrica solo es tan buena como el conjunto de pruebas sobre el que se ejecuta. Si tu golden set cubre tres intenciones y el tráfico de producción abarca doce, Recall@10 mide solo esas tres intenciones. Peor aún, un conjunto de pruebas sobreajustado a preguntas fáciles («¿Cuál es la política de devoluciones de la empresa?») puede aprobar un sistema que falla en las difíciles («¿Qué requisitos tiene la devolución por cancelación parcial en virtud de la Ley de Servicios Digitales de la UE de 2023, facturada en EUR y originada en Irlanda?»). La puntuación agregada sube mientras el sistema sigue fallando en una parte importante del tráfico de producción.
El mismo problema afecta al ground truth. Si los SMEs etiquetaron los documentos obvios, pero omitieron los relevantes de la long tail, Recall@k infravalorará un retriever que en realidad sí los encontró. Optimizas hacia las etiquetas, no hacia la verdad.
Construye primero el conjunto de pruebas en torno a la distribución y dificultad reales de las consultas. Después elige métricas que respondan a los modos de fallo objetivo y ajusta el sistema con ellas.
Generación sintética de consultas
Utiliza un LLM para generar preguntas a partir de tu corpus:
- Por chunk: «Genera 3 preguntas que podría hacer un usuario y que este chunk responda».
- Multi-hop: muestrea dos chunks y genera una pregunta que requiera ambos.
- Adversarial: genera preguntas con entidades distractoras, phrasing casi duplicado y menciones ambiguas.
RAGAS incluye una distribución integrada por tipo de pregunta (reasoning, conditional, multi-context). DataMorgana genera benchmarks sintéticos configurables entre categorías de usuarios y preguntas. Los datos sintéticos son útiles para cold starts y pruebas de cobertura. No pueden sustituir a las consultas reales de usuarios.
Construcción del golden dataset
Los datos seleccionados por humanos anclan el golden set.
- Muestrea consultas reales de usuarios (o simuladas si el producto aún no se ha lanzado), estratificadas por intención.
- Pide a SMEs que respondan cada pregunta e identifiquen qué documento(s) contienen la respuesta.
- Dimensiona el conjunto a partir de la matriz de cobertura y del intervalo de confianza necesario para las decisiones de release; la cobertura importa más que un número de consultas prestado.
- Vuelve a curarlo cuando la cadencia de release, las señales de drift, el riesgo del dominio y la capacidad de anotación lo justifiquen.
Conjuntos de pruebas adversariales
- Contrafactuales: intercambia entidades clave en la consulta. ¿Recupera el sistema los chunks correctos para la consulta modificada?
- Distractores: consultas en las que el corpus contiene una respuesta plausible pero incorrecta que no debería recuperarse. Esto es lo que somete a stress test RGB (Chen et al., AAAI 2024): robustez al ruido, rechazo de negativos, integración de información y robustez contrafactual.
- Negación y cuantificadores: consultas con «no», «excepto» y «solo». Los retrievers dense suelen tener dificultades con ellas.
- Fuera de alcance: consultas sin respuesta en el corpus. El sistema debería decir «No lo sé», no alucinar. NoMIRACL pertenece a esta categoría. Evalúa explícitamente la abstención sobre los tipos de consultas de producción.
Cobertura y evaluación continua
- Construye una matriz de cobertura: intención de consulta × tipo de documento × rama de la ontología. Intenta tener ≥1 consulta por celda. Las celdas vacías son regiones sin monitorizar donde se esconden las regresiones.
- Ejecuta un subconjunto de regresión acotado y rápido en cada PR, y la suite completa con una frecuencia más lenta.
- Programa la evaluación del golden set completo según la cadencia de release y el coste de evaluación; los release candidates son un gate natural.
- Programa la evaluación de drift según el volumen de tráfico, el cambio esperado y el riesgo. Usa una muestra móvil de producción y estratifícala por feedback, en lugar de cambiar silenciosamente la distribución objetivo.
Parte 10: Monitorización en producción
La suite de evaluación que publicas describe el sistema en el lanzamiento. El tráfico de producción cambia después.
Feedback implícito y explícito
- Click-through / open rate sobre las fuentes citadas (si la UI las expone).
- Dwell time sobre la respuesta.
- Tasa de regeneración: porcentaje de respuestas que el usuario vuelve a preguntar o pide rehacer. Trátala como una señal de insatisfacción y calibra su relación con conversaciones revisadas.
- Tasas de copia, compartición y exportación: señales positivas fuertes.
- Patrones de follow-up: patrones como «¿Estás seguro?» o «¿Y qué ocurre con X?» sugieren desconfianza.
- Thumbs up/down con categorías de motivo opcionales (incorrecta, incompleta, fuera de tema, dañina, lenta). Las ediciones inline, cuando la UI las permite, suelen aportar más información que cualquier otra señal de feedback.
Detección de drift
- Drift de consultas: sigue la distribución de embeddings de las consultas frente a una ventana de referencia usando divergencia KL, MMD o un detector basado en modelos. Lanza una alerta ante un shift y depura después por segmentos.
- Drift de embeddings: fija un conjunto de probes de documentos; vuelve a generar periódicamente sus embeddings y mide el coseno frente a los embeddings originales. Incluso un drift pequeño entre versiones del modelo del proveedor puede romper silenciosamente el retrieval. El almacenamiento versionado de embeddings (snapshots inmutables por versión) es la mitigación más barata.
- Drift de rendimiento: sigue métricas equivalentes a producción (tasa de regeneración por intención) a lo largo del tiempo. Los saltos repentinos indican que algo se ha roto; los drifts lentos indican que el mundo ha cambiado.
Evaluación en shadow y human-in-the-loop
Ejecuta el sistema candidato en paralelo con producción, compara las salidas offline y no las sirvas a los usuarios. Esto detecta regresiones antes del lanzamiento. Tiene un coste adicional de inference, pero no afecta a los clientes.
Para la revisión human-in-the-loop (HITL):
- Envía las salidas de baja confianza a una cola de revisión.
- Incluye una muestra aleatoria del tráfico de producción para revisión ciega; define su tasa según el volumen de tráfico, el riesgo y la capacidad de los revisores.
- Da mucho peso a las salidas con thumbs-down.
- Utiliza las salidas revisadas para ampliar el golden set.
El conjunto mínimo de guardrails
Lanza alertas sobre estos elementos, por orden de prioridad:
- Puntuación de Faithfulness/HHEM por debajo del umbral en una muestra móvil de producción.
- Latencia p95 por encima del SLO.
- Tasa de false-exclusion del filtro por encima del umbral (basada en muestras).
- Tasa de regeneración fuera de una banda de control calibrada localmente que tenga en cuenta el tamaño de la ventana, el tráfico, la estacionalidad y el presupuesto de falsas alertas.
- Coste/query por encima del presupuesto.
Si se dispara una alerta sin un cambio correspondiente de código o modelo, probablemente tienes drift. Si se dispara después de un cambio, probablemente tienes una regresión. En cualquier caso, obtienes una señal antes de que lleguen los tickets de soporte.
Precauciones
- Los objetivos son locales, no universales. Cualquier cifra marcada como ilustrativa en esta guía es una configuración de ejemplo o un resultado trabajado, no un umbral de release. Calibra los umbrales según tu dominio, el nivel de riesgo, la incertidumbre del conjunto de evaluación y las expectativas de los usuarios.
- El espacio de frameworks evoluciona rápido. Las versiones de HHEM, los nombres de las métricas de RAGAS, las model cards y el orden de los leaderboards pueden cambiar después de la publicación. Vuelve a comprobar las fuentes enlazadas y repite el benchmark antes de comprometerte.
- Las cifras de acuerdo de LLM-as-judge tienen asteriscos. La cifra del 80 % de GPT-4 frente a humanos procede de las condiciones de MT-Bench / Chatbot Arena. En dominios especializados y casos adversariales, el acuerdo cae drásticamente. Utiliza judges como multiplicadores de fuerza, no como sustitutos de las comprobaciones puntuales.
- Los uplifts de benchmarks de proveedores a menudo no se pueden reproducir de forma independiente. Reproduce los resultados con tus propios datos antes de creer una cifra, especialmente en rerankers y sistemas de OCR recientes.
- Ninguna métrica sustituye a revisar las salidas. Programa una revisión ciega de una muestra aleatoria de producción según el tráfico, el riesgo y la capacidad de los revisores. Las métricas escalan ese hábito; no lo sustituyen.
Próximos artículos de esta serie
Este era el índice. Estos son los follow-ups que estoy planificando:
- Boosts soft frente a filtros hard: análisis detallado de la tasa de false-exclusion del filtro, con código, ejemplos reales de producción y un framework de decisión.
- El chunking es la variable oculta: experimento controlado con chunking recursivo, semántico, late y estructural sobre tres corpus.
- Selección de rerankers en 2026: BGE frente a Cohere, ZeRank y los modelos cross-encoder actuales, comparados en coste, latencia y uplift.
- RAG basado en ontologías: recorrido end-to-end: construcción del harness de evaluación completo para un sistema de retrieval basado en entidades.
- LLM-as-judge sin caer en la trampa de la autopreferencia: recetas prácticas para una evaluación automatizada sin sesgos.
- Evaluación online en producción: patrones de instrumentación, políticas de alertas y dashboards que detectan regresiones reales.
Referencias
Frameworks y benchmarks
- Es et al., Ragas: Automated Evaluation of Retrieval Augmented Generation, 2023.
- Documentación de RAGAS y GitHub.
- Saad-Falcon et al., ARES: An Automated Evaluation Framework for Retrieval-Augmented Generation Systems, NAACL 2024.
- TruLens, DeepEval, Arize Phoenix.
- Thakur et al., BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models, NeurIPS 2021.
- Leaderboard de MTEB.
- TREC 2024 RAG Track.
- Pradeep et al., Initial Nugget Evaluation Results for the TREC 2024 RAG Track with the AutoNuggetizer Framework, 2024.
Retrieval y ranking
- Cormack, Clarke, Buettcher, Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods, SIGIR 2009.
- Gao et al., Precise Zero-Shot Dense Retrieval Without Relevance Labels (HyDE), 2022.
- Jeong et al., Adaptive-RAG: Learning to Adapt Retrieval-Augmented Large Language Models through Question Complexity, NAACL 2024.
- Anthropic, Introducing Contextual Retrieval, septiembre de 2024.
- Günther et al., Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models, 2024.
Generación, faithfulness y judges
- Min et al., FActScore: Fine-grained Atomic Evaluation of Factual Precision in Long Form Text Generation, EMNLP 2023.
- Liu et al., Lost in the Middle: How Language Models Use Long Contexts, TACL 2023.
- Chen et al., Benchmarking Large Language Models in Retrieval-Augmented Generation (RGB), AAAI 2024.
- Vectara, HHEM-2.1-Open hallucination evaluation model.
- Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena, NeurIPS 2023.
- Thakur et al., Support Evaluation for the TREC 2024 RAG Track: Comparing Human versus LLM Judges, SIGIR 2025.
- Thakur et al., NoMIRACL: Knowing When You Don’t Know for Robust Multilingual Retrieval-Augmented Generation, 2023.
- Geng et al., JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language Models, 2025.
- Kosmopoulos et al., Evaluation Measures for Hierarchical Classification: a unified view and novel approaches, 2015.
Drift y producción
- Evidently, Embedding drift detection methods compared.
Código complementario
slavadubrov/rag-evals-demo: harness ejecutable para todas las métricas de este artículo sobre el corpus SciFact, además de un sweep de benchmark chunking × embedding × LLM. Incluye notebooks 00–09, pruebas unitarias que fijan los ejemplos trabajados anteriores y un índice Qdrant embebido para ejecutarlo sin Docker.