La suite del sistema: el checkpoint C3
consolidarás N3 diseñando y ejecutando la suite de evals del agente de Aurora —RAG triad + métricas IR + evaluación de trayectoria— que, ante un fallo, dice en qué componente está. Es el entregable del checkpoint C3, y el activo que en N4 convertirás en gate de CI.
6.1El diagnóstico a mano no escala
Vuelve a las zapatillas. En N3·L1 abriste el nivel con ese fallo. Un cliente pregunta "¿puedo devolver unas zapatillas usadas?". El agente de Aurora —la tienda online ficticia que es tu banco de pruebas— responde con seguridad: "sí, hasta 90 días". La política real dice 30 días y solo sin usar.
Durante cinco lecciones aprendiste a localizar ese fallo. Mides el contexto recuperado con la RAG triad (L2). Examinas el retriever con métricas IR (L3). Juzgas la trayectoria del agente con tool-call correctness (L4). Y en L5 montaste el árbol de diagnóstico que recorre la traza y señala al culpable: retrieval, generación u orquestación.
Pero lo hiciste a mano, traza por traza. Eso sirve para entender. No sirve para gobernar.
Tu dataset de N1 tiene más de cien trazas etiquetadas. Mañana cambias el prompt del sistema, o el modelo, o el chunking del índice. ¿Vas a re-diagnosticar las cien a mano cada vez? Un diagnóstico que depende de tu atención manual no detecta una regresión silenciosa. La detecta el cliente, en producción, en un tuit.
Necesitas un activo ejecutable: una suite que corra sobre el dataset entero y localice los fallos sin que tú leas cada traza. Hoy lo montas.
6.2Qué vas a poder hacer
Al terminar esta lección habrás:
- Diseñado la suite de evals del agente de Aurora que cubre los tres componentes: RAG triad, métricas IR de retrieval y evaluación de trayectoria.
- Ejecutado esa suite sobre casos del dataset y producido, por caso, un veredicto que localiza el fallo en un componente.
- Aprobado el checkpoint C3 contra su rúbrica de cinco dimensiones, con feedback formativo por cada una.
Necesitas saber antes (todo de este nivel):
- La RAG triad y qué fallo detecta cada vértice (N3·L2).
- Las métricas IR y cómo justificar el
k(N3·L3). - Tool-call/argument correctness y el principio outcome > transcript (N3·L4).
- El árbol de diagnóstico que ordena la lectura de la traza (N3·L5).
Esta lección integra: no introduce métricas nuevas. Empaqueta las que ya dominas en un artefacto único. Es el cierre del nivel.
6.3Recupera
Esta es la lección de cierre del nivel. El repaso mezcla las cuatro lecciones técnicas a propósito —ese cruce, llamado interleaving, te obliga a discriminar entre métricas que se confunden—. Responde mentalmente antes de seguir.
- La RAG triad tiene tres vértices. Nombra los tres y di qué componente mira cada uno.
- El retriever de Aurora se deja fuera el chunk correcto. ¿Qué métrica IR lo delata: precision@k o recall@k?
- Un agente da la respuesta correcta pero escaló de más por el camino. ¿Qué métrica lo detecta? ¿Y qué principio dice que el outcome importa más que el guion exacto de pasos?
- El árbol de diagnóstico de L5 empieza por un componente concreto. ¿Por cuál, y por qué ese y no groundedness?
Las respuestas, ancladas en el corpus:
- Context relevance (mira retrieval), groundedness/faithfulness (mira generación), answer relevance (mira si la respuesta contesta la pregunta). Los tres vértices simultáneos dan confianza de ausencia de alucinación (corpus D.1).
- Recall@k —relevantes en top-k / total relevantes—. Mide si "lo bueno, lo traigo" (corpus D.5).
- Tool-call correctness lo detecta. El principio es outcome > transcript: prefieres juzgar el estado final logrado, no el guion exacto, porque los agentes hallan rutas válidas no anticipadas (corpus D.4).
- Empieza por la trayectoria: si el agente no llamó a
consultar_politica, no hay contexto que medir, así que la triad y las métricas IR no aplican. El primer fallo de la cadena manda (corpus A.3, A.6).
6.4El concepto: puntuar no es localizar
La diferencia entre una suite mediocre y una buena no está en cuántas métricas calcula. Está en si localiza o solo puntúa.
Una suite que puntúa te devuelve un número agregado: "groundedness media 0,71". Eso te dice que hay un problema. No te dice dónde nació ni qué arreglar. Es el score "3,72 hoy vs 4,2 mañana" que el curso desmonta desde N0: un número sin diagnóstico.
Una suite que localiza te devuelve, por cada fallo, una atribución a un componente y el arreglo que se deriva. "Caso 47: retrieval malo (context relevance 0,2) → revisa el chunking del índice." Ese veredicto es accionable.
Las cuatro propiedades de una suite que localiza
Una suite localiza cuando cumple cuatro cosas a la vez:
- Cubre los tres componentes. Triad para retrieval+generación, métricas IR para el retriever, tool/argument correctness para la trayectoria. Si te falta una familia, hay fallos que no ves —los de orquestación, por ejemplo, son invisibles para la triad porque sin tool-call no hay contexto que medir (corpus D.4).
- Reutiliza el juez validado de N2. Groundedness y relevance son juicios subjetivos: necesitan un LLM-as-judge. Ese juez no es uno nuevo improvisado; es el que calibraste en N2 con TPR/TNR validados (corpus E.3: superar el 90% en ambos antes de producción). Un vértice de la triad es ese juez reutilizado.
- Corre sobre el dataset versionado de N1. No sobre casos inventados al vuelo. El dataset etiquetado es la fuente de verdad: tus modos de fallo reales, no métricas genéricas.
- Su informe mapea cada fallo a un componente con un arreglo. El veredicto apunta a un cambio concreto: retrieval malo → chunking/índice/reranker; generación infiel → prompt/modelo; orquestación → descripción de tools o system prompt.
El andamiaje: primero RAG, luego trayectoria
Montar las tres familias de golpe satura. La construyes por capas, como diseñó el nivel: primero el componente RAG (la triad sobre un caso), luego añades la trayectoria (tool/argument correctness), y al final el informe de localización que une los veredictos.
Es común querer cablearlo todo de una vez y acabar con un script que ni corre ni se entiende. La diferencia: cada capa se prueba aislada antes de sumar la siguiente.
6.5Míralo funcionar: el esqueleto de la suite
Vamos a montar la suite por capas y verla correr sobre Aurora. Es el bloque más denso del nivel. Lee primero cada bloque entero; luego la explicación línea a línea.
Una nota sobre las herramientas. Usamos Ragas para la triad (el módulo moderno ragas.metrics.collections; el legacy ragas.metrics se elimina en v1.0) y agentevals de LangChain para la trayectoria. El cálculo IR lo haces a mano sobre los qrels de tu dataset, como en L3.
Capa 1 — la RAG triad (Ragas)
La triad opera sobre un SingleTurnSample: la pregunta, la respuesta del agente y los chunks que consultar_politica recuperó.
1import asyncio
2from ragas.dataset_schema import SingleTurnSample
3from ragas.metrics.collections import Faithfulness, AnswerRelevancy, ContextUtilization
4from ragas.llms import llm_factory
5from ragas.embeddings import embedding_factory
6
7# El juez validado de N2 (con TPR/TNR ≥90%) y sus embeddings.
8evaluator_llm = llm_factory()
9evaluator_embeddings = embedding_factory()
10
11# Caso de las zapatillas, trace B de L1: recuperó el chunk correcto pero inventó "90 días".
12sample = SingleTurnSample(
13 user_input="¿Puedo devolver unas zapatillas usadas?",
14 response="Sí, puedes devolverlas hasta 90 días después de la compra.",
15 retrieved_contexts=[
16 "Devoluciones: 30 días desde la entrega, solo artículos sin usar y con etiqueta."
17 ],
18)
19
20async def triad(sample: SingleTurnSample) -> dict:
21 faithfulness = Faithfulness(llm=evaluator_llm)
22 answer_relevance = AnswerRelevancy(llm=evaluator_llm, embeddings=evaluator_embeddings)
23 context_relevance = ContextUtilization(llm=evaluator_llm)
24 return {
25 "context_relevance": await context_relevance.single_turn_ascore(sample),
26 "groundedness": await faithfulness.single_turn_ascore(sample),
27 "answer_relevance": await answer_relevance.single_turn_ascore(sample),
28 }
29
30print(asyncio.run(triad(sample)))
31# Esperado para trace B: context_relevance ALTA, groundedness BAJA, answer_relevance alta.Self-explanation: ¿por qué ContextUtilization y no ContextPrecision? Respóndela antes de seguir.
Porque en producción no tienes reference para cada query. ContextPrecision la exige; ContextUtilization (la variante sin reference) usa solo el response para juzgar la relevancia del contexto (corpus D.2; api-nivel3 §1.6). Eliges la métrica según los datos que tienes, no la que suena mejor.
El patrón "buen contexto, generación infiel" salta a la vista: groundedness baja con context relevance alta. El agente tuvo el chunk correcto delante e inventó igual. Recuerda el matiz de L2: groundedness alta NO garantiza respuesta correcta si el contexto era erróneo —por eso necesitas también context relevance (corpus D.1)—.
Capa 2 — las métricas IR del retriever
ContextUtilization te dice que el retrieval fue bueno o malo en un caso. No te dice cuán bien recupera el retriever en general. Eso lo dicen las métricas IR sobre los qrels de tu dataset (los chunks que marcaste relevantes en N1).
1def precision_at_k(recuperados: list[str], relevantes: set[str], k: int) -> float:
2 top_k = recuperados[:k]
3 aciertos = sum(1 for chunk in top_k if chunk in relevantes)
4 return aciertos / k
5
6def recall_at_k(recuperados: list[str], relevantes: set[str], k: int) -> float:
7 top_k = recuperados[:k]
8 aciertos = sum(1 for chunk in top_k if chunk in relevantes)
9 return aciertos / len(relevantes) if relevantes else 0.0
10
11def mrr(recuperados: list[str], relevantes: set[str]) -> float:
12 for posicion, chunk in enumerate(recuperados, start=1):
13 if chunk in relevantes:
14 return 1.0 / posicion
15 return 0.0
16
17# Retrieval real de Aurora con k=5; qrels marcan 2 chunks relevantes.
18recuperados = ["envios-12", "devol-03", "envios-08", "devol-07", "faq-21"]
19relevantes = {"devol-03", "devol-07"}
20
21print(precision_at_k(recuperados, relevantes, 5)) # 2/5 = 0.4
22print(recall_at_k(recuperados, relevantes, 5)) # 2/2 = 1.0
23print(mrr(recuperados, relevantes)) # 1/2 = 0.5 (primer relevante en pos. 2)El k no es arbitrario: lo justificas por cuántos chunks pasa el agente al LLM como contexto. Aquí consultar_politica pasa 5, así que mides @5. Y eliges la métrica según el objetivo de Aurora: si una sola política basta para responder, MRR manda; si hace falta cubrir varios chunks (devoluciones + excepciones), recall@k importa más (corpus D.5).
Capa 3 — la trayectoria (agentevals)
La trayectoria responde a la pregunta que ni la triad ni IR pueden: ¿llamó a las tools correctas, en buen orden? Si el agente respondió de memoria sin llamar a consultar_politica (trace C de L1), aquí es donde se ve.
agentevals compara la trayectoria real contra una de referencia en formato de mensajes OpenAI.
1import json
2from agentevals.trajectory.match import create_trajectory_match_evaluator
3
4# Lo que el agente hizo de verdad: respondió SIN consultar la política (trace C).
5outputs = [
6 {"role": "user", "content": "¿Puedo devolver unas zapatillas usadas?"},
7 {"role": "assistant", "content": "Sí, hasta 90 días."},
8]
9
10# Lo que debía hacer: consultar la política de devoluciones antes de responder.
11reference_outputs = [
12 {"role": "user", "content": "¿Puedo devolver unas zapatillas usadas?"},
13 {
14 "role": "assistant",
15 "content": "",
16 "tool_calls": [
17 {
18 "function": {
19 "name": "consultar_politica",
20 "arguments": json.dumps({"tema": "devoluciones"}),
21 }
22 }
23 ],
24 },
25 {"role": "tool", "content": "Devoluciones: 30 días, sin usar."},
26 {"role": "assistant", "content": "No: solo 30 días y sin usar."},
27]
28
29evaluator = create_trajectory_match_evaluator(trajectory_match_mode="superset")
30result = evaluator(outputs=outputs, reference_outputs=reference_outputs)
31print(result) # {'key': 'trajectory_superset_match', 'score': False, 'comment': None}Usamos "superset" porque queremos comprobar que el agente hizo al menos las llamadas de la referencia, sin castigar pasos extra válidos —eso es outcome > transcript en la práctica (corpus D.4)—. El modo "strict" exigiría orden y secuencia idénticos; demasiado rígido cuando hay rutas válidas alternativas. Aquí el score: False delata el fallo de orquestación: el agente nunca llamó a consultar_politica.
Capa 4 — el informe de localización
Las tres capas producen scores. El informe los une en un veredicto, siguiendo el árbol de diagnóstico de L5: primero la trayectoria, luego el contexto, luego la generación.
1def localizar(triad_scores: dict, trayectoria_ok: bool, umbral: float = 0.5) -> dict:
2 # Paso 1: ¿llamó a las tools que debía? (trayectoria, L4)
3 if not trayectoria_ok:
4 return {"componente": "orquestacion",
5 "arreglo": "revisar descripción de tools / system prompt"}
6 # Paso 2: ¿el contexto recuperado era relevante? (context relevance, L2)
7 if triad_scores["context_relevance"] < umbral:
8 return {"componente": "retrieval",
9 "arreglo": "revisar chunking / índice / reranker"}
10 # Paso 3: ¿la respuesta está grounded en el contexto? (groundedness, L2)
11 if triad_scores["groundedness"] < umbral:
12 return {"componente": "generacion",
13 "arreglo": "revisar prompt / modelo"}
14 # Paso 4: ¿responde a la pregunta? (answer relevance, L2)
15 if triad_scores["answer_relevance"] < umbral:
16 return {"componente": "generacion",
17 "arreglo": "respuesta irrelevante: revisar prompt"}
18 return {"componente": "ninguno", "arreglo": "—"}Self-explanation: ¿por qué el orden trayectoria → contexto → generación, y no al revés? Porque los errores se propagan: un fallo upstream contamina todo lo de abajo (corpus A.6). Si el agente no llamó a la tool, medir groundedness no tiene sentido —no hay contexto que evaluar—. El primer fallo de la cadena manda (corpus A.3).
Verificación: el test del fallo inyectado
Una suite que localiza tiene que demostrarlo. Inyecta un fallo conocido de cada tipo y comprueba que cae en el cajón correcto.
1# Fallo de retrieval: contexto irrelevante, todo lo demás bien.
2caso_A = ({"context_relevance": 0.2, "groundedness": 0.9, "answer_relevance": 0.8}, True)
3# Fallo de generación: buen contexto, respuesta no grounded.
4caso_B = ({"context_relevance": 0.9, "groundedness": 0.2, "answer_relevance": 0.8}, True)
5# Fallo de orquestación: nunca llamó a la tool.
6caso_C = ({"context_relevance": 0.0, "groundedness": 0.0, "answer_relevance": 0.0}, False)
7
8assert localizar(*caso_A)["componente"] == "retrieval"
9assert localizar(*caso_B)["componente"] == "generacion"
10assert localizar(*caso_C)["componente"] == "orquestacion"
11print("La suite localiza los tres tipos de fallo.")Si los tres asserts pasan, tu suite no solo puntúa: localiza. Ese es el corazón del checkpoint.
6.6Hazlo tú: el checkpoint C3
La práctica de esta lección es el checkpoint. No hay ejercicio separado: montas la suite real sobre tu dataset de Aurora.
El entregable
Checkpoint C3. Diseña y ejecuta la suite del agente de Aurora: (a) RAG triad (context relevance, groundedness, answer relevance) con Ragas/DeepEval; (b) métricas IR del retriever (precision@k/recall@k, MRR/NDCG); (c) evaluación de trayectoria (tool-call correctness, outcome > transcript). Entregable: suite ejecutable + informe que, ante un fallo, dice en qué componente está.
Andamiaje: hazlo por capas
Sigue el orden del worked example, sin saltarte la verificación de cada capa:
- Capa RAG. Carga 3-5 casos de tu dataset de N1 en
SingleTurnSampley corre la triad. Comprueba a mano que un caso de "buen contexto, mala respuesta" da groundedness baja con context relevance alta. - Capa IR. Sobre los qrels de esos mismos casos, calcula precision@k y recall@k con el
kque pasa tuconsultar_politica. Justifica por escrito por qué eseky qué métrica priorizas para el objetivo de Aurora. - Capa trayectoria. Convierte 2-3 trazas a formato de mensajes y córrelas por
agentevals. Incluye al menos un caso "no llamó a la tool" para ver elscore: False. - Informe. Une los scores con tu función
localizar. Ejecuta el test del fallo inyectado de los tres tipos.
Pregunta de interrogación elaborativa
Antes de leer la respuesta: ¿por qué la suite reutiliza el juez de N2 en lugar de un juez nuevo para la triad? Piénsalo.
Porque groundedness y answer relevance son juicios subjetivos —deciden si un claim se atribuye al contexto, si una respuesta contesta la pregunta—. Un juez sin validar puede tener un TPR del 60% y marcar como "grounded" respuestas que no lo están. Tu suite heredaría ese error en silencio. El juez de N2, con TPR/TNR ≥90% (corpus E.3), es la garantía de que el vértice mide lo que dice medir. Reutilizarlo no es comodidad: es lo que hace fiable a la triad.
6.7Comprueba: la rúbrica C3
Esta es la barrera de maestría del nivel. Tu suite se evalúa por cinco dimensiones. Léelas, autoevalúate con honestidad y usa el feedback formativo de cada una para cerrar brechas antes de darte por aprobado.
Rúbrica C3
- RAG triad — 3 vértices, interpretación correcta de qué fallo detecta cada uno.
- Retrieval — métricas IR apropiadas, k justificado.
- Agente — tool-call/argument correctness + outcome > transcript.
- Localización — la suite distingue retrieval/generación/orquestación.
- Accionabilidad — las métricas guían una mejora concreta.
Feedback formativo por dimensión
Dimensión 1 — RAG triad. Si tus tres vértices corren y sabes decir, para un caso dado, cuál bajó y qué componente delata, dominas el medidor central del nivel —reutilizarás esta lectura en cada diagnóstico—. La brecha más común: confundir context relevance con groundedness. Si las mezclas, recuerda: context relevance mira el contexto recuperado; groundedness mira si la respuesta se atribuye a ese contexto. Siguiente paso si dudas: vuelve a N3·L2 y corre la triad sobre trace A (mal contexto) y trace B (buen contexto, mala respuesta) hasta ver el patrón distinto en cada una.
Dimensión 2 — Retrieval.
Si elegiste la métrica IR y justificaste el k por escrito —por cuántos chunks pasa consultar_politica—, tienes el criterio que separa medir de adivinar. La brecha típica: usar precision@k cuando el objetivo es cobertura. Si Aurora debe cubrir devoluciones y sus excepciones, un precision@k alto con recall@k bajo significa que traes chunks buenos pero te dejas fuera alguno necesario. Siguiente paso: para tu objetivo concreto, escribe qué métrica priorizas y por qué; si no puedes justificarlo en una frase, revisa N3·L3.
Dimensión 3 — Agente.
Si tu evaluación de trayectoria distingue "outcome correcto, trayectoria mala" de "todo bien", aplicaste outcome > transcript de verdad. La brecha frecuente: tratar "strict" como el modo por defecto y castigar rutas válidas alternativas. Recuerda por qué outcome > transcript NO significa "ignora la trayectoria". Un agente que escala de más logra el outcome, pero por mala ruta. Eso lo delata tool-call correctness, no el outcome (corpus D.4). Siguiente paso: corre un caso con "superset" y otro con "strict"; observa cuándo cada modo acierta y cuándo es injustamente rígido.
Dimensión 4 — Localización.
Si el test del fallo inyectado pasa para los tres tipos, tu suite localiza —no solo puntúa—. Esta es la dimensión que define el nivel. La brecha que más cuesta: que dos componentes fallen a la vez (retrieval mediocre y generación floja) y no sepas a cuál atribuirlo. El árbol lo resuelve: el primer fallo de la cadena manda, porque contamina lo de abajo (corpus A.3, A.6). Siguiente paso: inyecta un caso con context_relevance 0,3 y groundedness 0,3 y verifica que tu localizar lo manda a retrieval, no a generación.
Dimensión 5 — Accionabilidad. Si cada veredicto de tu informe apunta a un arreglo concreto —retrieval → chunking/índice; generación → prompt/modelo; orquestación → descripción de tools—, la suite gobierna y no solo informa. La brecha sutil: emitir el score sin el arreglo. Una métrica solo sirve si guía una mejora concreta. Siguiente paso: para cada veredicto de tu informe, escribe la línea de "qué cambiarías"; si alguna queda en blanco, esa métrica no es accionable todavía.
Para aprobar el gate: necesitas las cinco dimensiones cumplidas, con la 4 (localización) demostrada por el test del fallo inyectado. Si una falla, no es un suspenso: es el punto exacto por donde seguir. Vuelve a la lección que la cubre y reintenta.
6.8Conecta
Vuelve por última vez a las zapatillas.
En N3·L1, ante ese fallo, solo tenías el veredicto del juez de N2: "groundedness fail". Sabías que estaba mal, no dónde. Hoy, ante el mismo fallo, tu suite corre, lee la traza y responde en segundos. El contexto era correcto (context relevance alta); el modelo lo ignoró (groundedness baja). El culpable es la generación, y el arreglo es el prompt, no el índice. El agujero negro de N0 es ahora un informe que se autogenera.
Has cerrado el arco completo del curso hasta aquí. N0 hizo visible el agente; N1 lo etiquetó; N2 validó el juez; N3 mide por tramos usando las tres cosas. Cada nivel alimentó al siguiente.
Y abre el último tramo. Esta suite vive hoy en un notebook: la corres tú, cuando te acuerdas. En N4 la conviertes en el gate de CI que falla el build ante una regresión. El principio de N4 lo adelanta el corpus: un eval que no puede fallar el build no gobierna nada. La fiabilidad se demuestra en CI, no en un notebook.
¿Dónde lo aplicarías en tu trabajo? Piensa en cualquier sistema RAG + tools que hayas tocado. Si hoy llegara una queja sobre una respuesta concreta, ¿podrías decir en qué componente nació el fallo —retrieval, generación u orquestación— sin leer la traza a mano? Si la respuesta es "no", ya sabes qué suite te falta.
6.9Reflexiona
Tómate dos minutos. Estas preguntas consolidan más que releer.
- ¿Qué hace que una suite localice en vez de solo puntuar? Dilo con tus palabras.
- ¿Por qué el árbol de diagnóstico empieza por la trayectoria y no por groundedness?
- ¿Qué dimensión de la rúbrica C3 te costó más demostrar? Anótalo. Esa es tu señal de qué lección del nivel reabrir.
Referencia rápida
- Suite que localiza ≠ suite que puntúa: la primera atribuye cada fallo a un componente con un arreglo; la segunda da un número agregado sin diagnóstico.
- Cuatro propiedades: cubre los tres componentes · reutiliza el juez validado de N2 · corre sobre el dataset de N1 · su informe mapea fallo → componente → arreglo.
- Andamiaje: capa RAG (triad) → capa trayectoria → informe de localización. Prueba cada capa antes de sumar la siguiente.
- APIs: triad con
ragas.metrics.collections(Faithfulness,AnswerRelevancy,ContextUtilization— sin reference); IR a mano sobre qrels; trayectoria conagentevals(create_trajectory_match_evaluator, modo"superset"). - Orden del árbol: trayectoria → context relevance → groundedness → answer relevance. El primer fallo de la cadena manda (corpus A.3, A.6).
- Rúbrica C3 (5 dimensiones): triad · retrieval (k justificado) · agente (tool-call + outcome > transcript) · localización · accionabilidad.
- Verificación obligatoria: inyecta un fallo de cada tipo (retrieval/generación/orquestación) y comprueba que cae en el cajón correcto.