Módulo 3: El eval como fitness function

7. Tipos de criterio de eval

Descripción

Al terminar esta lección vas a abrir la caja que el módulo dio por sentada hasta ahora: el criterio de éxito. En todas las lecciones anteriores el criterio fue el mismo —contains, ¿la respuesta contiene la frase clave?—, porque es el más simple y dejaba ver la mecánica del gate sin distracciones. Pero contains es solo uno de varios tipos de criterio, y la elección tiene consecuencias arquitectónicas, no solo de precisión. Vas a ejecutar cuatro criterios sobre los mismos casos y ver cómo cada uno da un score distinto para la misma calidad real: exact-match (demasiado estricto, reprueba paráfrasis correctas), contains (binario, atrapa la paráfrasis pero no gradúa), LLM-as-judge (un juez que da crédito parcial, más matizado) y el umbral estadístico (que opera sobre el score agregado, no caso por caso). Y vas a encontrarte con la advertencia más importante del módulo, la que conecta este módulo con todos los demás: el LLM-as-judge es otro componente de IA, con su propia latencia, costo y no-determinación, así que juzgar la calidad con él recursa todas las propiedades de esta guía.

Esto importa porque el criterio es lo que decide qué cuenta como "una respuesta buena", y un gate no es mejor que su criterio. Un criterio mal elegido produce un score que parece una medición pero no lo es: exact-match reprueba respuestas correctas y hace el gate inútilmente estricto; un contains flojo aprueba respuestas malas y hace el gate ciego. Entender los tipos de criterio a nivel arquitectónico —qué gobierna cada uno, qué falla en cada uno, qué cuesta cada uno— es lo que te permite razonar sobre un gate en vez de confiar ciegamente en su número. Y el caso del LLM-as-judge es especialmente cargado, porque es tentador: "usemos un modelo potente para juzgar si la respuesta del modelo barato es buena" suena perfecto, hasta que recuerdas que el juez es un LLM —lento, caro, no determinista, falible— y que ahora tienes dos componentes de IA en vez de uno, con la pregunta incómoda de quién evalúa al juez.

Conexión con el módulo: esta es la última lección de tema, y cierra el módulo abriendo su pieza más íntima. Las lecciones 2 a 6 construyeron el gate completo —score (3), compuerta (4), regresión (5), CI (6)— siempre con contains como criterio fijo. Aquí generalizas ese criterio a una familia, y ves que la elección entre sus miembros es una decisión de diseño con trade-offs de precisión y de costo. La advertencia del juez-como-componente te reconecta con toda la guía: latencia y costo (módulo 2), no-determinación y contrato probabilístico (módulo 1), frontera de confianza (módulo 4) —el juez tiene todas esas propiedades—. Aquí también se marca, por última vez, la frontera dura: cómo elegir y calibrar el criterio correcto para una tarea es diseño de evals, de AI Engineering; esta lección te da el mapa de los tipos y sus consecuencias arquitectónicas, no la receta de cuál usar.

La analogía: cuatro formas de calificar la misma respuesta

Vuelve al examen, pero ahora mira quién califica y cómo. Imagina que tres estudiantes responden a "¿cuánto tarda el envío?" y quieres calificar sus respuestas. Hay varias formas, y cada una es un criterio distinto.

La primera es el escáner de opción múltiple: solo acepta la respuesta si es idéntica, letra por letra, a la clave. Es el exact-match. Rapidísimo y objetivo, pero reprueba a cualquiera que diga lo correcto con otras palabras —inútil para respuestas libres—. La segunda es un corrector con una lista de palabras clave: no exige la frase exacta, solo verifica que aparezca cierto término ("¿mencionó '3 a 5 días'?"). Es el contains. Más flexible que el escáner, pero binario y algo tosco: no distingue entre una respuesta completa y una que solo menciona la palabra de pasada. La tercera es un maestro humano con una rúbrica: lee la respuesta, entiende lo que quiso decir, y da una nota parcial —"esta menciona el tema pero no da el dato exacto, le doy 0.4 de 1"—. Es el LLM-as-judge: el criterio más matizado, capaz de crédito parcial y de entender paráfrasis, pero también el más lento, el más caro y —clave— el más subjetivo, porque el maestro puede tener un mal día y calificar distinto la misma respuesta. Y la cuarta no es una forma de calificar una respuesta, sino de agregar las notas de todas: la regla de aprobación del examen ("se aprueba con 0.80 de promedio"), que es el umbral estadístico operando sobre el score agregado.

Guarda las cuatro. Las tres primeras son formas de dar un veredicto a un caso —de la más rígida (exact-match) a la más matizada (juez)—; la cuarta agrega los veredictos en el score que el gate consume. Y guarda sobre todo la tercera, porque tiene una trampa: el "maestro humano" del LLM-as-judge también es un LLM. Contratar un modelo para juzgar a otro modelo no te saca del problema de la no-determinación —te mete un segundo componente probabilístico que ahora también hay que gobernar—.

Ejemplo trabajado: cuatro criterios, cuatro scores

Vamos a tomar tres respuestas del agente a "¿cuánto tarda el envío?" —una completa y correcta, una paráfrasis correcta, y una vaga— y a calificarlas con los cuatro criterios. La respuesta "gold" de referencia es "El envío tarda de 3 a 5 días hábiles". El LLM-as-judge está simulado con un stub determinista: una rúbrica fija que da crédito parcial (+0.4 si menciona el tema del envío, +0.6 si da el rango correcto). No es un modelo real —cero red, cero API— pero imita lo que un juez haría: dar una nota graduada, no un binario.

# Leccion 07 — tipos de criterio de eval
# Todo SIMULADO (incluido el juez). Cero red, cero API, cero claves. Determinista.

# Tres salidas del agente para la misma pregunta, con distinta calidad.
CASES = [
    {"id": "c1", "output": "El envio tarda de 3 a 5 dias habiles.",
     "exact": "El envio tarda de 3 a 5 dias habiles.", "contains": "3 a 5 dias"},
    {"id": "c2", "output": "Normalmente llega en 3 a 5 dias, segun tu zona.",
     "exact": "El envio tarda de 3 a 5 dias habiles.", "contains": "3 a 5 dias"},
    {"id": "c3", "output": "Suele llegar rapido, en pocos dias.",
     "exact": "El envio tarda de 3 a 5 dias habiles.", "contains": "3 a 5 dias"},
]

# --- Criterio 1: exact-match (igualdad literal contra el gold) ---
def crit_exact(o, c):    return o == c["exact"]

# --- Criterio 2: contains (la salida contiene la frase clave) ---
def crit_contains(o, c): return c["contains"].lower() in o.lower()

# --- Criterio 3: LLM-as-judge, SIMULADO con una rubrica fija (stub, NO un modelo) ---
# ADVERTENCIA ARQUITECTONICA: un juez REAL seria otro LLM -> no determinista,
# con su propia latencia, costo y falibilidad. Aqui lo simulamos con una rubrica
# determinista que da CREDITO PARCIAL (0..1), para ver la forma de su salida.
def llm_as_judge_stub(output):
    o = output.lower()
    score = 0.0
    if "envio" in o or "llega" in o:  # menciona el tema (envio/llegada)
        score += 0.4
    if "3 a 5" in o:                  # da el dato especifico y correcto
        score += 0.6
    return round(score, 2)

print(f"{'caso':<6}{'exact_match':>13}{'contains':>11}{'llm_judge':>11}")
for c in CASES:
    e  = crit_exact(c["output"], c)
    co = crit_contains(c["output"], c)
    j  = llm_as_judge_stub(c["output"])
    print(f"{c['id']:<6}{str(e):>13}{str(co):>11}{j:>11.2f}")

# --- Criterio 4: el umbral estadistico opera sobre el score AGREGADO ---
n = len(CASES)
exact_score    = sum(crit_exact(c["output"], c)    for c in CASES) / n
contains_score = sum(crit_contains(c["output"], c) for c in CASES) / n
judge_score    = sum(llm_as_judge_stub(c["output"]) for c in CASES) / n
print(f"\nscore agregado  exact={exact_score:.2f}  "
      f"contains={contains_score:.2f}  llm_judge={judge_score:.2f}")

Qué esperar. Al correrlo:

caso    exact_match   contains  llm_judge
c1             True       True       1.00
c2            False       True       1.00
c3            False      False       0.40

score agregado  exact=0.33  contains=0.67  llm_judge=0.80

Aquí están los cuatro criterios sobre los mismos tres casos, y aquí está por qué la elección del criterio cambia el veredicto. Léelo por columna.

Exact-match (score agregado 0.33). Solo c1 pasa —es idéntica al gold—; c2 y c3 fallan. Fíjate en c2: "Normalmente llega en 3 a 5 días, según tu zona" es una respuesta correcta —da el rango exacto— y aun así exact-match la reprueba, porque no es idéntica letra por letra. El score agregado, 0.33, no mide la calidad del agente: mide qué tan idénticas son sus respuestas a una frase arbitraria. Es el escáner de opción múltiple calificando ensayos: demasiado estricto, inútil para texto libre.

Contains (score agregado 0.67). c1 y c2 pasan —ambas contienen "3 a 5 días"— y c3 falla —"en pocos días" no contiene el rango exacto—. Esto es mejor: reconoce que la paráfrasis correcta (c2) es válida, así que el score sube a 0.67 y refleja mejor la calidad real. Pero es binario: c3 recibe un cero rotundo, aunque no sea una respuesta terrible —menciona que llega en pocos días, solo que sin el dato preciso—. Contains no distingue "vaga pero encaminada" de "completamente equivocada": las dos son FALSE. Es el corrector de palabras clave: flexible, pero tosco.

LLM-as-judge (score agregado 0.80). Aquí aparece el matiz. c1 y c2 reciben 1.00 —mencionan el envío y dan el rango—, y c3 recibe 0.40 —crédito parcial: menciona que llega en pocos días (+0.4 por el tema) pero no da el rango (sin el +0.6)—. Ese 0.40 es la diferencia clave: donde contains daba un cero binario a c3, el juez reconoce que es una respuesta parcialmente buena y le da una nota graduada. El score agregado, 0.80, captura mejor la realidad —el agente responde bien, con una respuesta floja— que el 0.67 binario de contains. El juez es el maestro con rúbrica: matizado, capaz de crédito parcial y de entender la intención.

El umbral estadístico (la cuarta columna, implícita en el "score agregado"). Fíjate en que la decisión del gate no se toma caso por caso, sino sobre el agregado. Los tres scores agregados —0.33, 0.67, 0.80— son lo que un gate compararía contra un umbral. Y mira la consecuencia: el mismo agente, con la misma calidad real, pasa o falla un gate de umbral 0.75 según qué criterio elijas —con exact-match (0.33) y contains (0.67) falla, con el juez (0.80) pasa—. El criterio no es un detalle de implementación: es lo que define qué mide tu gate.

Junta las cuatro columnas y tienes la lección: el criterio de éxito es una decisión de diseño, no un dato. Un criterio demasiado estricto (exact-match) subestima la calidad; uno binario (contains) la mide grueso; uno graduado (juez) la mide fino pero —como verás— trae su propio costo. Y el umbral opera sobre el agregado, así que la elección del criterio y el valor del umbral tienen que pensarse juntos.

Profundización: el LLM-as-judge es otro componente de IA

La recursión de propiedades. Esta es la idea más importante de la lección, y la que conecta el módulo 3 con toda la guía. El LLM-as-judge es tentador: cuando contains o exact-match se quedan cortos —porque la calidad es demasiado matizada para una regla mecánica—, la respuesta natural es "usemos un modelo potente para juzgar". Y funciona: un juez LLM puede evaluar relevancia, tono, corrección factual, cosas que ninguna regla de string captura. Pero mira lo que acabas de hacer. Para evaluar tu ai_component (el agente), agregaste un segundo ai_component (el juez). Y el juez, por ser un LLM, tiene todas las propiedades que esta guía te enseñó a manejar: es lento y cuesta por llamada (módulo 2 —ahora pagas dos modelos: el que responde y el que juzga—); es no determinista (módulo 1 —el mismo juez puede dar notas distintas a la misma respuesta, así que tu score se vuelve ruidoso—); es falible (puede juzgar mal —aprobar una respuesta mala o reprobar una buena—); y si le pasas texto del usuario para juzgar, cruza una frontera de confianza (módulo 4). Evaluar con un LLM no te saca del problema de la no-determinación: te da un segundo componente probabilístico que también hay que gobernar. La pregunta incómoda que sigue es "¿y quién evalúa al juez?" —y la respuesta es que el juez necesita su propia calibración contra el juicio humano, que es diseño de evals (AI Engineering)—.

Cuándo cada criterio, a nivel arquitectónico. No hay un criterio "mejor"; hay trade-offs, y a nivel de diseño se eligen así. Exact-match sirve cuando la salida correcta es única y estructurada —un código, un identificador, un JSON con un valor fijo—; para texto libre, casi nunca. Contains sirve cuando hay una frase clave inequívoca que la respuesta correcta debe incluir —como el "3 a 5 días" del envío—; es barato, determinista y rápido, y por eso es el caballo de batalla de muchos evals. Un escalón arriba, criterios estructurales: validar que la salida cumpla un schema (es un JSON válido con los campos requeridos), que es determinista y perfecto cuando la forma importa (lo verás a fondo en el módulo 4, guardrails). Y el LLM-as-judge sirve cuando la calidad es genuinamente matizada —relevancia semántica, tono, corrección de un razonamiento— y ninguna regla mecánica la captura; es el más poderoso y el más caro, y solo se justifica cuando lo más simple no alcanza. La regla de diseño: usa el criterio más simple que capture lo que importa. No traigas un juez LLM —con su costo, latencia y ruido— si un contains determinista mide bien tu propiedad. El juez es la herramienta de último recurso, no la primera.

El costo del juez se multiplica. Un detalle que conecta con el módulo 2 y que la gente subestima: si tu eval-set tiene 500 casos y usas un LLM-as-judge, cada corrida del eval hace 500 llamadas al modelo juez —además de las 500 al modelo que estás evaluando—. Y el eval corre en cada PR (lección 6). El taxímetro del módulo 2 corre ahora por partida doble, y para el modelo juez (que suele ser un modelo potente, para juzgar bien) el precio por llamada es el premium. Un eval con juez LLM puede costar más que la feature que evalúa. Esto no es razón para no usarlo —a veces es la única forma de medir lo que importa— pero sí para usarlo con consciencia de su costo: reservarlo para las propiedades que de verdad lo necesitan, y usar criterios baratos (contains, schema) para el resto.

La frontera, por última vez. Todo lo que esta lección hace es mostrarte el mapa de los tipos de criterio y sus consecuencias arquitectónicas —qué gobierna cada uno, qué cuesta, qué falla—. Lo que NO hace —y es de AI Engineering— es enseñarte a elegir y calibrar el criterio correcto para una tarea concreta: cómo decidir qué frase clave importa en un contains, cómo escribir la rúbrica de un juez para que sus notas correlacionen con el juicio humano, cómo validar que el juez no tiene sesgos, cómo medir la relevancia de una búsqueda semántica con métricas serias. Eso es el oficio de diseñar evals, y es profundo. Este módulo te deja saber que el criterio importa y que el juez es un componente con sus propias propiedades; diseñar el criterio es la otra guía.

Errores comunes

Usar exact-match para texto libre (de criterio demasiado estricto). Qué pasa: el equipo evalúa un componente que genera texto —un agente, un resumen, una descripción— con igualdad exacta contra una respuesta modelo. El score sale artificialmente bajo (0.33 en el ejemplo) porque reprueba toda paráfrasis correcta, y el equipo concluye que el componente es malo cuando en realidad el criterio es inadecuado. Por qué pasa: exact-match es el criterio por defecto de quien viene de tests clásicos. Cómo detectarlo: si tu criterio reprueba respuestas que un humano consideraría correctas solo por no ser idénticas, es demasiado estricto. Cómo corregirlo: para texto libre, usa contains (si hay una frase clave) o un juez (si la calidad es matizada); reserva exact-match para salidas únicas y estructuradas.

Traer un LLM-as-judge sin contar su costo y su no-determinación (de sobre-ingeniería del criterio). Qué pasa: el equipo usa un juez LLM para todo el eval-set "porque es más inteligente", y descubre tarde que cada corrida cuesta el doble (dos modelos), que el eval en CI ahora es lento y caro, y que el score varía entre corridas porque el juez es no determinista —a veces bloquea un deploy bueno por ruido del juez—. Por qué pasa: el juez suena a la solución universal ("un modelo listo juzgando"), y su costo y su ruido son invisibles hasta que se acumulan. Cómo detectarlo: si usas un juez LLM para propiedades que un contains determinista mediría igual de bien, estás pagando de más y metiendo ruido. Cómo corregirlo: usa el criterio más simple que capture la propiedad; reserva el juez para lo genuinamente matizado, y recuerda que el juez es un ai_component con costo, latencia y no-determinación propios (recursa las propiedades de la guía).

Confiar en el score sin mirar el criterio (de opacidad). Qué pasa: el equipo ve un score alto (0.95) y confía en él sin preguntar con qué criterio se calculó. Resulta que el criterio era un contains flojo ("la respuesta menciona 'días'"), que aprueba respuestas vagas, así que el 0.95 no significa que el componente sea bueno —significa que el criterio es permisivo—. Por qué pasa: el score es un número limpio que invita a confiar, y el criterio que lo produjo queda escondido detrás. Cómo detectarlo: si no puedes decir con qué criterio se calculó un score, no sabes qué mide ese score. Cómo corregirlo: mira siempre el criterio junto al score; un número alto con un criterio flojo es la "compuerta inútil" de la lección 4 disfrazada de buena noticia.

Ejercicios

Ejercicio 1 — Califica con cada criterio. Una respuesta del agente a "¿puedo devolver un producto?" es: "Sí, aceptamos devoluciones dentro de los 30 días". El gold es "Puedes hacer la devolución en un plazo de 30 días". Di el veredicto bajo (a) exact-match contra el gold, (b) contains con la frase clave "30 días", y (c) un LLM-as-judge que da +0.5 si menciona que se puede devolver y +0.5 si da el plazo. Comenta qué criterio refleja mejor la calidad real.

Ver solución
  • (a) Exact-match: "Sí, aceptamos devoluciones dentro de los 30 días" ≠ "Puedes hacer la devolución en un plazo de 30 días" → FALSE. Reprueba una respuesta claramente correcta solo por redactarse distinto.
  • (b) Contains "30 días": la respuesta contiene "30 días" → TRUE (1). Reconoce que la información clave está.
  • (c) LLM-as-judge: menciona que se puede devolver (+0.5) y da el plazo de 30 días (+0.5) → 1.00. Reconoce que la respuesta es completa.

Cuál refleja mejor la calidad real: la respuesta es correcta y completa, así que el juez (1.00) y el contains (TRUE) la miden bien, y el exact-match (FALSE) la mide mal —es el criterio inadecuado aquí, porque el texto es libre—. Entre contains y juez, para esta respuesta dan el mismo veredicto positivo; el juez se distinguiría en una respuesta parcial (por ejemplo "sí, puedes devolver" sin el plazo daría contains=FALSE pero juez=0.5, crédito parcial). La moraleja: exact-match está fuera de lugar para texto libre; contains y juez coinciden en los casos claros y se separan en los matizados.

Ejercicio 2 — El juez que hay que gobernar. Un equipo propone: "Para evaluar el agente de soporte, usaremos el modelo más potente como juez: le pasamos la pregunta del cliente y la respuesta del agente, y el juez da una nota de 0 a 1. Corremos esto sobre 800 casos en cada PR." Enumera al menos tres propiedades de esta guía que el juez, por ser un LLM, arrastra al eval, y qué problema causa cada una.

Ver solución

El juez es otro ai_component, así que arrastra sus propiedades:

  1. Costo (módulo 2). Cada corrida hace 800 llamadas al modelo juez (además de las 800 al agente), y el juez es el modelo más potente, con precio premium. En cada PR. El eval con juez puede costar más que la feature evaluada —el taxímetro corre por partida doble—.
  2. Latencia (módulo 2). 800 llamadas al juez tardan; el paso de eval en CI se vuelve lento, y un CI lento frena a todo el equipo. Quizá haya que sacarlo del pipeline por PR y correrlo en una etapa aparte.
  3. No-determinación (módulo 1). El juez es un LLM, así que puede dar notas distintas a la misma respuesta en corridas distintas. El score del eval se vuelve ruidoso: puede bloquear un deploy bueno por variación del juez, no del agente. El umbral tiene que tener margen para el ruido.

(Un cuarto, si el texto del cliente es adversario: frontera de confianza (módulo 4) —una respuesta o una pregunta maliciosa podría intentar manipular al juez—.)

El problema de fondo: usar un juez LLM no elimina la no-determinación, la duplica. Ahora tienes dos componentes probabilísticos —el agente y el juez— y la pregunta "¿quién evalúa al juez?" queda abierta (el juez necesita su propia calibración contra humanos, que es AI Engineering). El juez es poderoso, pero no es gratis ni neutral: es un componente de IA más, con todo lo que eso implica en esta guía.

Ejercicio 3 — Elige el criterio más simple que sirva. Para cada propiedad a evaluar, di qué criterio usarías (exact-match, contains, schema/estructural, o LLM-as-judge) y por qué el más simple que sirve es el correcto. (a) Que el agente devuelva un JSON con los campos status y order_id. (b) Que la respuesta sobre envíos mencione el plazo "3 a 5 días". (c) Que el order_id extraído sea exactamente "MERCADO-8842". (d) Que la respuesta del agente a una queja tenga un tono empático y resuelva el problema.

Ver solución
  • (a) JSON con campos status y order_id → schema/estructural. La propiedad es de forma: validar que la salida sea un JSON válido con esos campos. Determinista, barato, perfecto para estructura. No necesitas un juez para verificar una forma.
  • (b) Menciona "3 a 5 días" → contains. Hay una frase clave inequívoca; contains("3 a 5 dias") la verifica, barato y determinista. Traer un juez aquí sería pagar de más por lo que un in resuelve.
  • (c) order_id exactamente "MERCADO-8842" → exact-match. La salida correcta es única y estructurada; la igualdad exacta es el criterio correcto, no hay paráfrasis posible de un identificador.
  • (d) Tono empático y resuelve el problema → LLM-as-judge. La propiedad es genuinamente matizada —"empático" y "resuelve" no son una frase clave ni una forma—; ninguna regla mecánica lo captura, así que aquí sí se justifica el juez, con consciencia de su costo y su ruido.

La regla que ordena: usa el criterio más simple que capture la propiedad. Estructura → schema; frase clave → contains; salida única → exact-match; calidad matizada → juez (último recurso). Escalar al juez cuando un contains bastaría es pagar costo, latencia y ruido de más sin ganar precisión.

Resumen y siguiente paso

En esta lección abriste la pieza que el módulo dio por sentada: el criterio de éxito. Con la analogía de las cuatro formas de calificar viste que exact-match (el escáner) es demasiado estricto para texto libre, contains (el corrector de palabras clave) es flexible pero binario, el LLM-as-judge (el maestro con rúbrica) da crédito parcial y matiz, y el umbral estadístico opera sobre el score agregado. Ejecutaste los cuatro sobre los mismos casos y viste el mismo agente recibir scores distintos —exact 0.33, contains 0.67, juez 0.80— según el criterio: prueba de que el criterio es una decisión de diseño, no un dato. Y te encontraste con la advertencia central del módulo: el LLM-as-judge es otro componente de IA que arrastra todas las propiedades de la guía —costo y latencia (módulo 2), no-determinación (módulo 1), falibilidad, frontera de confianza (módulo 4)—, así que evaluar con él duplica el problema en vez de resolverlo. La regla de diseño que te llevas: usa el criterio más simple que capture lo que importa, y reserva el juez para lo genuinamente matizado.

Antes de avanzar deberías poder: nombrar los tipos de criterio y qué gobierna cada uno; explicar por qué el criterio elegido cambia el score de la misma calidad real; articular por qué el LLM-as-judge es un ai_component con sus propias propiedades y qué implica eso; y aplicar la regla de "el criterio más simple que sirva".

Con esto cierra el recorrido de tema del módulo. Ya tienes el arco completo: por qué el assert exacto no sirve (2), cómo se construye el score con un eval-set (3), cómo el score se vuelve compuerta con un umbral (4), cómo la compuerta atrapa regresiones (5), cómo vive en CI bloqueando deploys (6) y qué criterios la alimentan (7). Lo que sigue es el proyecto: en la lección 8 vas a montar la compuerta de calidad del agente de soporte de Mercado de punta a punta —el eval-set, el gate con detalle por caso, la versión que pasa y la que regresa, la integración en CI— y a justificar por qué el eval es la compuerta de calidad arquitectónica que completa el trío con latencia y costo. Es el paso de "entiendo cada pieza" a "sé montar la compuerta entera con mis manos".

Recursos