Módulo 6: Evaluar la calidad de la recuperación
Módulo 6: Evaluar la calidad de la recuperación
Descripción
Los cinco módulos anteriores construyeron, en orden, un pipeline completo: el Módulo 1 parseó y chunkeó 13 documentos en 57 chunks con metadatos. El Módulo 2 los indexó con BM25. El Módulo 3 envolvió ese índice en la tool search_docs. El Módulo 4 puso esa tool a decidir dentro de un bucle de agente. El Módulo 5 le enseñó a sobrevivir cuando los documentos cambian. En ningún momento, hasta ahora, esta guía se preguntó formalmente: ¿el sistema recupera bien?
No es una pregunta retórica. El Módulo 2, Lección 07, ya corrió las seis queries ancla de esta guía contra el índice BM25 completo y reportó, sin maquillar, que solo una de seis acertó el documento esperado en el primer lugar. Ese número quedó ahí, como una advertencia honesta, pero sin el vocabulario ni las herramientas para decir cuánto de "casi correcto" hay en las otras cinco, ni dónde exactamente falla cada una. Este módulo construye ese vocabulario: un set de evaluación fijo, con la respuesta correcta anotada de antemano, y dos métricas de forma —recall@k y precision@k— que convierten "funciona" o "no funciona" en un número reproducible, el mismo hoy que dentro de un año, sin que un modelo de lenguaje tenga que opinar.
Conexión con el módulo
Este módulo no agrega ninguna pieza de ingeniería nueva al pipeline: no toca el índice del Módulo 2, no cambia el contrato de search_docs del Módulo 3, no modifica el chunk store del Módulo 5. Lo que agrega es una medida, aplicada desde afuera, que le pone un número honesto a todo lo construido hasta ahora — y, con esa medida en la mano, señala exactamente qué mejora de producción (Módulo 7 en adelante, y las guías vecinas de AI Engineering) resolvería cada falla específica que vas a medir aquí.
Dónde estamos en la guía
RAG de producción e ingesta de documentos — el agente de Reservo
├── Módulo 1: Parsing y chunking de documentos
│ → 13 documentos crudos → 57 chunks con metadatos (doc_id, sección, posición)
├── Módulo 2: Indexar chunks para recuperación
│ → El índice BM25 hand-rolled (k1=1.5, b=0.75), search(query, k) -> list[Chunk]
├── Módulo 3: search_docs como herramienta del agente
│ → El contrato, qué devuelve, la description, acotarla, ejecutada de punta a punta
├── Módulo 4: Recuperación agéntica en el bucle
│ → Cuándo buscar, reformular, multi-hop, combinar con tools de Reservo, grounding
├── Módulo 5: Ingestión incremental e idempotente
│ → Documentos que cambian: hash de contenido, detectar new/modified/deleted
├── Módulo 6: Evaluar la calidad de la recuperación ← ESTÁS AQUÍ
│ → Set de evaluación fijo, recall@k, precision@k, por qué falla BM25 y qué lo arregla
├── Módulo 7: Operar RAG en producción
└── Módulo 8: Proyecto — search_docs lista para producción
Los Módulos 1-5 respondieron, uno por uno, "¿cómo se construye cada pieza?". Este módulo responde una pregunta distinta y más incómoda: "¿cómo sé, con un número que no cambia según mi humor, si lo que construí sirve?"
Analogía: un examen con las respuestas correctas ya escritas
Imagina que evaluar un sistema de recuperación es corregir un examen de opción múltiple del que ya tienes la hoja de respuestas. No le preguntas al alumno "¿te parece que entendiste bien el tema?" —eso es opinión, y cambia según a quién le preguntes y qué día sea—; comparas cada respuesta que dio contra la respuesta correcta, ya fijada de antemano, y cuentas. Recall@k pregunta: de las respuestas correctas que existen, ¿cuántas aparecieron entre las primeras k que el alumno escribió? Precision@k pregunta lo contrario: de las k respuestas que el alumno escribió, ¿cuántas eran correctas? Ninguna de las dos preguntas necesita que un tercero "entienda" el examen — solo necesita la hoja de respuestas y un contador.
Ahora imagina un alumno particular: uno que, sin importar la pregunta, siempre escribe "depende de la política" en alguna parte de su respuesta. En un examen de opción múltiple sobre políticas de una empresa, esa palabra aparece en casi todas las preguntas —y por pura coincidencia, a veces el alumno acierta, no porque entendió la pregunta, sino porque "política" estaba, casualmente, en la respuesta correcta también. Ese alumno es exactamente lo que este módulo va a medir, con nombre y apellido: un chunk —cancellation-policy-003, la sección "Related Policies" que menciona refund-policy y no-show-policy por su nombre— que gana preguntas por solapamiento de palabras, no por ser la respuesta correcta.
Qué se reusa, y de dónde (léelo antes de seguir)
Este módulo no reintroduce nada de la mecánica de búsqueda — la declara reusada, con la fuente exacta, y construye la evaluación encima:
- El corpus canónico (
RAW_DOCS, 13 documentos) y el chunker completo (parse_html,parse_markdown,clean_text,chunk_by_structure,Chunk,ingest_document,build_corpus). Exactamente como quedó fijado en el Módulo 1 y reproducido, verbatim, en el Módulo 5 — mismo esquema dechunk_id(f"{doc_id}-{i:03d}", 0-indexed), mismos 57 chunks. La Lección 03 de este módulo lo reproduce completo, una última vez, como el archivo del que importa el resto del módulo. - El índice BM25 completo (
tokenize,build_inverted_index,bm25_idf,bm25_scoreconk1=1.5/b=0.75,Index,build_index,search(query, k) -> list[Chunk]). Exactamente como quedó construido en el Módulo 2, Lecciones 03, 05 y 07 — sin cambiar una línea de la fórmula ni del ordenamiento. - El set de seis queries ancla y su
doc_idesperado. Definido desde el diseño de esta guía y ya ejecutado una vez en el Módulo 2, Lección 07, donde acertó 1 de 6 en el top-1. Este módulo reusa exactamente ese mismo set —ni una palabra cambiada— y lo formaliza con métricas.
Lo que este módulo construye desde cero: recall_at_k, precision_at_k y evaluate, las tres funciones que convierten los resultados crudos de search en un score reproducible. Nada de esto existía antes del Módulo 6 — hasta ahora, "acertó" o "no acertó" era una inspección manual del top-1, lección por lección.
Si algo de la lista de reuso no te resulta familiar, es prerequisito real de los Módulos 1 y 2, no un repaso opcional — este módulo avanza asumiendo que esas piezas ya están firmes.
Frontera de este módulo (léela antes de seguir)
Dos límites duros, con la guía o el ecosistema vecino nombrado en ambos sentidos:
- Este módulo NO usa un LLM-juez. No hay ninguna llamada a
claude-sonnet-5(ni a ningún otro modelo) evaluando si una respuesta "suena bien" o "es fiel a la fuente". La evaluación es determinista: mismoEVAL_SET, mismo índice, mismo score, siempre — corrido hoy o corrido dentro de un año, con el mismo resultado exacto. Evaluar la generación —si la respuesta final del modelo es fiel, relevante, completa— con un juez semántico (RAGAS, faithfulness, answer relevancy) es un tema distinto y completo, cubierto porevaluation-frameworks-guide(AI Engineering). La frontera es literal: aquí se mide si el chunk correcto volvió; esa guía mide si la respuesta final es buena. - Este módulo MIDE, no optimiza a fondo. Vas a ver, con números reales, exactamente dónde y por qué BM25 falla contra este corpus — pero este módulo no reimplementa un re-ranker, un índice híbrido, ni un embedding semántico para arreglarlo. Esas técnicas existen, tienen nombre, y se nombran en la Lección 07 con precisión sobre qué falla específica resolvería cada una — pero la implementación completa vive en
advanced-rag-techniques-guide(re-ranking con cross-encoders, hybrid search BM25+embeddings, query expansion) y enembeddings-deep-dive-guide/vector-databases-fundamentals-guide(la teoría del embedding semántico y cómo se indexa un vector database real). Esta guía te deja sabiendo exactamente qué medir y qué pedir cuando llegues a esas guías; no reconstruye ahí lo que ellas ya cubren.
Regla dura de ejecución (se mantiene de M1-M5)
- El harness de evaluación SÍ se ejecuta.
recall_at_k,precision_at_kyevaluatecorren con Python 3.14.0 real, sobre el índice BM25 real construido sobre los 57 chunks del corpus canónico, contra las seis queries ancla reales. Cada bloque "Qué esperar" de este módulo es salida real de tu propia terminal, no un número inventado para que la lección se vea prolija. - Sin
random, sindatetime.now(). ElEVAL_SETes una lista fija, escrita en el código, la misma en cada corrida. No hay ningún elemento probabilístico en todo el módulo. - Ningún LLM-juez, en ninguna lección. Ni siquiera de forma conceptual —a diferencia de M3/M4, donde
claude-sonnet-5aparece decidiendo qué tool llamar, en este módulo no hay ninguna decisión de modelo, ni real ni representativa. La evaluación es matemática de conteo, sobre resultados ya calculados por BM25. - Los números se reportan tal cual salen, sin ajustar el
EVAL_SETni el corpus para que "el score se vea mejor". Vas a ver un módulo con un score de recuperación que empieza bajo (recall@1 = 1/6) — esa incomodidad es el punto pedagógico central del módulo, no un error a corregir.
Prerequisitos
Conocimiento requerido:
- ✅ Módulo 2 completo:
build_index,search(query, k, index) -> list[Chunk], y por qué BM25 es recuperación léxica (coincidencia de palabras), no semántica (significado). - ✅ Las seis queries ancla y su
doc_idesperado, y el resultado ya visto en el Módulo 2, Lección 07 (1/6 en el top-1) — este módulo parte de ahí, no lo repite desde cero. - ✅ Python: comprensión de listas y funciones simples (
sum,len, comprensión de listas) — no hace falta nada más allá de lo ya usado en M1-M5.
NO requerido:
- ❌ No necesitas ninguna librería de evaluación externa (
ragas,trulens) — las dos métricas de este módulo se implementan en menos de diez líneas cada una, sin dependencias. - ❌ No necesitas ninguna API key ni llamada a un modelo — no hay decisión de LLM en ninguna lección de este módulo.
- ❌ No necesitas construir un re-ranker ni un índice híbrido — se nombran como el camino a seguir (Lección 07), no se implementan aquí.
Entorno:
- ✅ Python 3.14.0 (stdlib:
re,html.parser,collections.Counter/defaultdict,math,dataclasses) +numpy2.5.1 (ya instalado, $0, sin red — usado únicamente paraargsortsobre el vector de scores BM25, exactamente igual que en el Módulo 2). Sinsqlite3en este módulo: la evaluación corre en memoria, sobrelist[Chunk].
Roadmap del módulo
Lección 01 — Introducción al módulo (esta)
La analogía del examen con respuestas correctas, qué se reusa de M1/M2, la frontera con el LLM-juez de evaluation-frameworks-guide y con las técnicas de mejora de advanced-rag-techniques-guide.
Lección 02 — Por qué se evalúa la recuperación, no la generación
La diferencia entre "¿el chunk correcto volvió?" (determinista, sin modelo) y "¿la respuesta final es buena?" (semántico, necesita un juez); por qué esta guía se queda en la primera pregunta.
Lección 03 — El set de evaluación fijo
El corpus canónico y el índice, reproducidos verbatim; EVAL_SET, las seis queries con su doc_id esperado; primera corrida cruda de search sobre las seis, sin métricas todavía — solo para ver, otra vez, el material con el que vas a trabajar.
Lección 04 — Recall@k
Qué mide, cómo se calcula, recall_at_k implementada y ejecutada para varios valores de k sobre las seis queries ancla — con el número real, sin forzar.
Lección 05 — Precision@k
Qué mide (y en qué se diferencia de recall), precision_at_k implementada y ejecutada sobre el mismo set — y por qué un recall alto con k grande no es gratis.
Lección 06 — Cuándo falla la recuperación léxica
El corazón del módulo: el "imán léxico" cancellation-policy-002/-003, diseccionado con números reales; la trampa reembolso/no-show, palabra por palabra; qué demuestra esto sobre BM25 como recuperación léxica, no semántica.
Lección 07 — El camino hacia mejor recall
Qué arreglaría cada falla medida: re-ranking con cross-encoders, hybrid search (BM25 + embeddings), embeddings semánticos — nombrados con precisión, sin implementarlos, con la guía exacta de AI Engineering que los cubre.
Lección 08 — Mini-proyecto: un harness de evaluación de recuperación
Las tres funciones (recall_at_k, precision_at_k, evaluate) juntas en un guion de punta a punta, corrido para múltiples valores de k sobre el índice completo, con un reporte final.
Evidencia de éxito
Antes de avanzar al Módulo 7 (Operar RAG en producción), deberías poder:
- ✅ Explicar, con una frase, por qué la evaluación de recuperación de este módulo es determinista y no necesita un LLM-juez.
- ✅ Calcular recall@k y precision@k a mano para un caso de una sola query, y confirmar el número con
evaluate. - ✅ Ejecutar el harness completo sobre las seis queries ancla y reportar el recall@k y precision@k reales, para al menos tres valores de
k. - ✅ Explicar, con evidencia numérica (no solo intuición), por qué
cancellation-policy-002/-003gana preguntas que no debería ganar. - ✅ Nombrar —sin implementar— al menos dos técnicas concretas que mejorarían el recall medido en este módulo, y en qué guía de AI Engineering se aprenden.
Resumen
- Este módulo le pone un número honesto a los cinco módulos anteriores: un
EVAL_SETfijo de seis queries condoc_idesperado, y dos métricas de forma —recall@k, precision@k— calculadas sin ningún modelo de por medio. - Reusa, sin repetir, el corpus canónico y el índice BM25 completos de M1/M2; construye desde cero únicamente las funciones de evaluación.
- El resultado no se ajusta para que se vea bien: vas a medir, ejecutado, que BM25 falla la mayoría de las seis queries ancla en el top-1 — y vas a entender exactamente por qué, con evidencia de tokens y scores, no con una afirmación vaga.
- Frontera doble: evaluación semántica con LLM-juez es
evaluation-frameworks-guide; las técnicas que mejorarían lo medido aquí (re-ranking, hybrid search, embeddings) sonadvanced-rag-techniques-guideyembeddings-deep-dive-guide/vector-databases-fundamentals-guide— nombradas con precisión en la Lección 07, no implementadas aquí.
Siguiente lección: 02 — Por qué se evalúa la recuperación, no la generación. La distinción exacta entre las dos preguntas, y por qué esta guía responde solo la primera.
Recursos adicionales
- Manning, Raghavan & Schütze — Introduction to Information Retrieval, cap. 8: "Evaluation in information retrieval" — La referencia académica estándar de recall/precision en recuperación de información, el tema completo de este módulo.
production-rag-and-document-ingestion-guide— Módulo 2, Lección 07 (search(query, k), ejecutado): el resultado crudo (1/6) que este módulo formaliza con métricas.evaluation-frameworks-guide(AI Engineering) — evaluación semántica de RAG con LLM-juez (RAGAS, faithfulness, answer relevancy); la frontera exacta con este módulo, que es determinista.advanced-rag-techniques-guide(AI Engineering) — re-ranking con cross-encoders, hybrid search, query expansion; adónde ir con el diagnóstico de este módulo en la mano.- Python 3.14 — What's New — La versión con la que se ejecuta todo el código de este módulo.