¿Código o juez? Mide barato primero
decidirás, para cada modo de fallo del agente de Aurora, si lo mides con una assertion determinista o con un LLM-as-judge, aplicando la regla "mide barato y determinista primero". Sin esa decisión, montas un juez caro para algo que resolvía un if —o fuerzas un regex sobre algo irreduciblemente semántico—.
1.1Tres fallos, una tentación
Sales de N1 con la taxonomía priorizada del agente de Aurora —la tienda online ficticia que es tu banco de pruebas todo el curso—. Tienes los fallos nombrados y contados. Toca medirlos a escala.
El modo de fallo nº1 es el reembolso fantasma que abrió el curso: el agente afirma algo sobre la política que el chunk recuperado no respalda. Tu primer instinto de ingeniero de evals moderno es directo: "le pongo un LLM-judge".
Pero en la misma lista hay otros dos fallos, justo debajo:
1#1 Responde sobre política sin que el chunk recuperado lo respalde (frecuencia alta)
2#2 Devuelve un order_id mal formado: "AUR-104" en vez de "AUR-1043" (frecuencia media)
3#3 Responde en inglés a un cliente que escribió en español (frecuencia media)Antes de abrir el notebook, una pregunta incómoda. ¿Vas a gastar una llamada a un modelo grande —coste y latencia— para detectar un order_id con un dígito de menos? Un regex lo caza gratis, en microsegundos, sin pedir API key.
La pregunta de esta lección no es "¿cómo construyo un juez?". Es la anterior: ¿cuáles de tus fallos necesitan un juez, y cuáles no?
1.2Qué vas a poder hacer
Al terminar esta lección sabrás:
- Clasificar un modo de fallo dado como medible por código (assertion determinista) o por juez (LLM-as-judge), y justificar la elección.
- Aplicar la regla "mide barato y determinista primero" y los tres niveles de coste de eval de Husain (L1/L2/L3).
- Distinguir un fallo verificable mecánicamente de uno que exige juicio semántico —y detectar los casos-trampa que parecen lo uno y son lo otro—.
Necesitas saber antes:
- Haber completado N1: tener una taxonomía de fallos con conteos y un dataset etiquetado (pass/fail) del agente de Aurora.
- Python a nivel de leer una función y un
re.match. - Las cuatro herramientas del agente de Aurora (de N0):
buscar_pedido,consultar_politica,escalar_a_humanoy el bucleresponder.
Esta lección no construye el juez todavía. Aquí decides si lo necesitas. Construirlo es el trabajo del resto del nivel (L2 a L6).
1.3Recupera
Antes de seguir, responde mentalmente. No mires lo de abajo hasta tener una respuesta.
- Del N1: ¿cuál fue el modo de fallo nº1 del agente de Aurora por frecuencia, y cómo lo etiquetaste —escala binaria o numérica—?
- Del N0: en una traza explotable, ¿qué campos tienes disponibles para escribir una assertion? (Piensa: salida, llamadas a tools, argumentos,
finish_reason, docs recuperados.) - El fallo "
order_idmal formado". ¿Podrías detectarlo con una sola línea de código, sin llamar a ningún modelo? ¿Cómo?
La respuesta a la 1 es el fallo de fundamentación, etiquetado binario (pass/fail) sobre tu dataset. La 2 te recuerda que ya tienes la materia prima: el dataset etiquetado de N1 es donde correrá cualquier eval que montes hoy. Y la 3 adelanta toda la lección —re.match(r"^AUR-\d{4}$", arg) decide ese fallo sin un solo token de LLM—.
1.4El concepto: barato y determinista primero
Empecemos por lo concreto —los tres fallos de Aurora— y subamos hacia la regla general.
Una assertion determinista es una comprobación de código cuyo veredicto depende solo del input: el mismo dato produce siempre el mismo pass/fail. Un re.match, un ==, un "¿está esta clave en el dict?". Es la GPU de las evals: rapidísima y barata, pero solo para lo que se reduce a una regla.
Un LLM-as-judge es un modelo que emite un veredicto sobre una salida según una rúbrica. Anthropic lo llama un grader model-based —ese es su término exacto, no "LLM-based"—. Es flexible y escalable, pero no-determinista y necesitado de calibración (Anthropic, "Demystifying evals for AI agents", 9 ene 2026). La analogía con un humano que corrige exámenes tiene un límite: el humano arrastra criterio propio estable; el modelo puede cambiar de veredicto entre dos llamadas idénticas.
Los tres niveles de coste de eval
Husain ordena las evals por coste creciente, y el coste dicta la cadencia (Hamel Husain, Your AI Product Needs Evals, 29 mar 2024):
- L1 — unit tests / assertions. Baratísimo, determinista. Corre en cada cambio.
- L2 — human + model eval / LLM-judge. Más caro y lento. Cadencia periódica.
- L3 — A/B en producción. El más caro. Solo tras cambios significativos.
El orden de coste es L3 > L2 > L1. Por eso lo barato corre siempre y lo caro de tanto en tanto. Un detalle de nomenclatura, porque confunde: estos L1/L2/L3 son niveles de coste de eval. No los confundas con pointwise/pairwise, que verás en L2 de este mismo nivel.
Tres tipos de grader
Anthropic clasifica los graders en tres familias (misma fuente, 9 ene 2026):
- Code-based — rápido, barato, objetivo. Pero frágil: solo cubre lo que reduces a regla.
- Model-based — flexible, escalable. Pero no-determinista y requiere calibración.
- Human — el patrón oro. Pero caro y lento.
No es un menú de "elige tu favorito". Es una jerarquía de coste que se combina (estrategias weighted, binary o hybrid).
La regla operativa
De ahí sale la heurística que ordena toda la lección: mide barato y determinista primero (Hamel Husain, evals-faq, ene 2026). Usa una assertion o un regex si el fallo lo permite. Reserva el LLM-judge para fallos de generalización persistentes —los que ningún if captura—, no para lo trivialmente arreglable o verificable por código.
Esto entronca con un antipatrón de N1: no montes maquinaria pesada (sintéticos, jueces) para lo que se arregla en el prompt o se verifica con una línea de código.
El criterio de decisión
Reducido a una pregunta que aplicas a cualquier fallo:
¿El fallo se define por una propiedad verificable mecánicamente —un formato, la presencia de un literal, una tool llamada, un campo nulo—? → código. ¿Se define por un juicio semántico —"¿está fundamentado?", "¿es apropiado este tono?"— que no reduces a una regla? → juez.
Es común dudar en la frontera. La sección siguiente la recorre con casos reales y, después, con dos trampas que parecen caer de un lado y caen del otro.
1.5Míralo funcionar: clasificar los tres fallos de Aurora
Vamos a clasificar los tres fallos del §1.1 en voz alta, mostrando el razonamiento. Lee primero los tres veredictos; luego analizamos por qué.
Fallo #2 — order_id mal formado → código (L1)
El fallo se define por una propiedad del formato del argumento. Eso es verificable mecánicamente.
1import re
2
3# La traza guarda los argumentos de cada tool call (lo viste en N0).
4# Assertion determinista sobre el argumento order_id de buscar_pedido:
5def assert_order_id_valido(order_id: str) -> bool:
6 return re.match(r"^AUR-\d{4}$", order_id) is not None
7
8assert_order_id_valido("AUR-1043") # True -> pass
9assert_order_id_valido("AUR-104") # False -> failDeterminista, gratis, corre en cada commit. Es un eval de nivel L1. Pedirle esto a un LLM sería pagar por un juicio que una expresión regular resuelve mejor —y de forma reproducible—.
Fallo #3 — idioma de la respuesta ≠ idioma del cliente → código
"Responder en inglés a quien escribió en español" suena a juicio, pero no lo es. El idioma de un texto se detecta sin abrir juicios semánticos: con una librería de detección de idioma o una heurística. La assertion compara dos etiquetas de idioma —la del mensaje del cliente y la de la respuesta— y exige que coincidan.
Sigue siendo código, sigue siendo barato, sigue corriendo en cada cambio (L1). No necesitas un modelo grande para decidir "esto está en inglés".
Fallo #1 — respuesta no fundamentada en el chunk → juez (L2)
Aquí cambia todo. La pregunta es: "¿cada afirmación de la respuesta está respaldada por el texto que el agente recuperó?". Ningún regex la responde.
Self-explanation —respóndela antes de leer la siguiente línea—: ¿por qué este fallo no se reduce a "la respuesta contiene la palabra reembolso"?
Porque la respuesta puede estar perfectamente bien formada, mencionar la política correcta por su nombre y aun así afirmar algo que el chunk recuperado no dice. "Fundamentado" es una relación semántica entre dos textos —la respuesta y el contexto—, no la presencia de un literal. Eso exige un grader model-based: un juez que lea ambos y juzgue el apoyo.
1import os
2import anthropic
3
4client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
5
6# Esto es un BOCETO de la idea, no el juez calibrado (eso es L2-L6).
7# Solo muestra qué entra y qué sale cuando el fallo exige juicio semantico.
8def juez_fundamentacion(respuesta: str, chunk_recuperado: str) -> dict:
9 msg = (
10 "Eres un evaluador del agente de soporte de Aurora.\n"
11 "Decide si CADA afirmacion de la respuesta esta respaldada por el "
12 "texto recuperado. Si alguna afirmacion no se apoya en el texto, es 'fail'.\n\n"
13 f"Texto recuperado:\n{chunk_recuperado}\n\n"
14 f"Respuesta del agente:\n{respuesta}"
15 )
16 r = client.messages.create(
17 model="claude-sonnet-4-6",
18 max_tokens=256,
19 messages=[{"role": "user", "content": msg}],
20 output_config={
21 "format": {
22 "type": "json_schema",
23 "schema": {
24 "type": "object",
25 "properties": {
26 "verdict": {"type": "string", "enum": ["pass", "fail"]},
27 "reason": {"type": "string"},
28 },
29 "required": ["verdict", "reason"],
30 "additionalProperties": False,
31 },
32 }
33 },
34 )
35 import json
36 return json.loads(r.content[0].text)Fíjate en el contraste con el Fallo #2. Allí la entrada era un string y la salida un booleano de una línea. Aquí la entrada son dos textos —respuesta y contexto— y la salida es un veredicto con su razón, producido por un modelo. Ese salto de coste es exactamente lo que la regla "barato primero" te obliga a justificar antes de pagarlo.
Un matiz honesto: ese boceto aún no es un juez fiable. Le falta una rúbrica precisa, validación contra humanos y auditoría de sesgos. Por eso L2 a L6 existen. Aquí solo demuestras que este fallo cae del lado del juez.
1.6Hazlo tú
Ejercicio 1 — andamiaje parcial
Ya tienes dos fallos clasificados como ejemplo. Clasifica estos dos, con la decisión ya esbozada:
- Fallo A: "el agente nunca llamó a
buscar_pedidoantes de afirmar el estado de un pedido". Pista: la traza registra qué tools se invocaron. ¿La propiedad "¿se llamó a esta tool?" es verificable mecánicamente o exige juicio semántico? - Fallo B: "la respuesta es técnicamente correcta pero el tono es seco y poco empático para un cliente molesto". Pista: ¿reduces "tono empático" a una regla, o necesitas un juicio sobre el sentido del texto?
Ver la respuesta razonada
- Fallo A → código (L1). "¿Aparece
buscar_pedidoen la lista de tool calls de la traza?" es uninsobre una lista. Determinista, gratis, corre en cada commit. - Fallo B → juez (L2). "Tono empático" es un juicio semántico abierto. No lo reduces a un literal ni a un patrón sin perder casos. Cae del lado del grader model-based.
Ejercicio 2 — autónomo
Te llega esta mini-taxonomía nueva del agente de Aurora. Etiqueta cada fallo como código o juez y justifica el criterio en una frase. Dos son trampas: uno parece juez y es código; otro parece código y es juez.
1F1 El agente prometió un reembolso (la politica dice que ese producto no es reembolsable)
2F2 La respuesta tiene un JSON de salida malformado que rompe el parser del front
3F3 La respuesta es correcta pero contradice lo que el agente dijo dos turnos antes
4F4 El agente escalo a humano cuando la KB tenia la respuesta (escalado de mas)Antes de ver la solución, una pregunta de interrogación elaborativa. Respóndela tú primero: ¿por qué "nunca prometer un reembolso" puede caer del lado del código, pese a sonar a juicio?
Ver la respuesta razonada
- F1 → código (trampa "parece juez, es código"). Tu política tiene una regla dura: "el agente no debe prometer reembolso para productos no reembolsables". Si la prohibición es un patrón literal o una promesa detectable ("he tramitado tu reembolso"), una assertion la caza. No necesitas que un modelo juzgue cómo de buena es la respuesta —necesitas detectar que apareció una promesa prohibida—. Por eso la pregunta de interrogación lleva aquí: la frontera no la marca cómo suena el fallo, sino si lo reduces a una regla.
- F2 → código (L1). "JSON malformado" es
is-jsonpuro.json.loadslanza o no lanza. Determinista. - F3 → juez (trampa "parece código, es juez"). "Contradice lo dicho dos turnos antes" suena a comparar strings, pero la contradicción es semántica: dos frases distintas pueden decir lo mismo, y dos casi-idénticas contradecirse. Exige juicio model-based sobre el sentido.
- F4 → juez (L2). "¿Estaba justificado el escalado?" depende de si la KB podía responder —un juicio sobre el contenido—, no de un campo de la traza. Cae del lado del juez.
1.7Comprueba
Sin pistas. Te describo un modo de fallo. Decide código o juez, justifica con la regla "barato y determinista primero", y di en qué nivel de coste cae (L1/L2/L3).
El fallo: el agente de Aurora a veces responde con una latencia altísima —más de 30 segundos— porque entra en un bucle de tool calls redundantes, llamando a
consultar_politicacinco veces seguidas con la misma query.
Ver la respuesta razonada
Código, nivel L1. Tienes dos propiedades verificables mecánicamente en la traza: la latencia total (un número) y la lista de tool calls (¿cuántas veces aparece consultar_politica con la misma query?). Una assertion comprueba "latencia < umbral" y "ninguna tool se repite con argumentos idénticos". Determinista, barato, corre en cada commit. Pagar un LLM-judge para esto sería romper la regla: lo barato y determinista lo resuelve.
Feedback formativo:
- Si dijiste código/L1 y supiste nombrar los dos campos de la traza: dominas el criterio central —la frontera la marca si el fallo se reduce a una propiedad verificable, no cómo suena—. Reutilizarás esta decisión en cada checkpoint del curso.
- Si saltaste al juez por defecto: es el error nº1 —saltar al juez caro porque es la herramienta de moda—. Releé el §1.4: la latencia y el conteo de tool calls son números en la traza; un
iflos lee. El siguiente paso: ante cualquier fallo, pregúntate primero "¿hay un campo en la traza que ya lo decide?" antes de pensar en un modelo. - Si dudaste entre L1 y L2: la pista es la cadencia. Si la comprobación es barata y la quieres en cada cambio, es L1. El LLM-judge (L2) corre periódicamente porque cuesta. La latencia la quieres vigilar en cada commit → L1.
1.8Conecta
Vuelve a los tres fallos del §1.1. Dos se cierran aquí mismo: el order_id mal formado y el idioma cruzado se miden con código barato y determinista, en cada commit. No vuelven a aparecer en este nivel porque no necesitan más.
El fallo nº1 —la respuesta no fundamentada— es el que cae del lado del juez. Y esa decisión, lejos de cerrar el trabajo, lo abre. Has obtenido el permiso para construir un juez; no el juez.
El resto del Nivel 2 construye ese juez:
- En L2 lo diseñas: las cuatro decisiones que lo definen (modo, referencia, escala, modelo).
- En L3 lo validas contra ≥100 etiquetas humanas, con TPR/TNR y κ —porque un juez sin validar sigue siendo otra vibe—.
- En L4 le auditas y domas los sesgos.
- En L5 lo alineas con tu criterio hasta superar el umbral de acuerdo.
- En L6 lo integras y produces el informe de validación: el checkpoint C2.
Y ese juez no muere en N2: en N3 es el motor de groundedness de la RAG triad; en N4 corre en el gate de CI. Calíbralo bien una vez, reutilízalo todo el curso.
¿Dónde lo aplicarías en tu trabajo? Piensa en cualquier sistema LLM que hayas tocado. ¿Cuántos de sus fallos estás midiendo —o pensando medir— con un juez caro que un if resolvería más barato y más fiable? ¿Y al revés: cuántos estás intentando cazar con regex frágiles que en realidad exigen juicio?
Decidir medir con juez no es el final. Es el permiso para empezar a construirlo bien.
1.9Reflexiona
Tómate dos minutos. Estas preguntas consolidan más que releer.
- ¿Qué propiedad debe tener un fallo para que un
re.matchlo decida, en lugar de un modelo? - Con tus palabras: ¿por qué "mide barato y determinista primero" no es tacañería, sino ingeniería de cadencia (L1/L2/L3)?
- ¿Qué sigue sin estar claro? Si es "¿cómo diseño exactamente el juez del fallo nº1?", es la pregunta correcta —la responde L2—.
Referencia rápida
- Regla raíz: mide barato y determinista primero; reserva el juez para fallos de generalización persistentes (Hamel Husain, evals-faq, ene 2026).
- Tres niveles de coste (Husain, 2024): L1 assertions/unit tests (cada cambio) → L2 human+model eval / LLM-judge (periódico) → L3 A/B en producción (tras cambios grandes). Coste L3 > L2 > L1 → dicta la cadencia.
- Tres graders (Anthropic, 9 ene 2026): code-based (rápido/barato/objetivo, frágil) · model-based (flexible/escalable, no-determinista, requiere calibración) · human (patrón oro, caro/lento).
- Criterio de decisión: propiedad verificable mecánicamente (formato, literal, tool llamada, campo nulo) → código. Juicio semántico no reducible a regla (fundamentación, tono) → juez.
- Trampas de la frontera: "no prometer reembolso" → código (patrón literal); "contradice lo dicho antes" → juez (semántico).
- No usar nunca: un LLM-judge para lo que un regex caza, ni un regex para lo que exige juicio semántico.