Deploy de Agentes — Módulo 7: testing de agentes II — evals como tests que bloquean el deploy
Séptimo módulo del Curso de Deploy de Agentes: lo no-determinista sí se mide, pero con evals en vez de asserts exactos. Golden dataset, LLM-as-judge, métricas y la idea central: correr los evals en CI con un umbral que bloquea el deploy si la calidad baja — un regression gate. Con código real de un eval simple.
☰ Índice del contenido
- La tesis del módulo
- Eval: el test que tolera la variación
- 1. Golden dataset: los casos de verdad
- 2. LLM-as-judge: cómo puntuar respuestas libres
- 3. El runner con umbral: donde el eval se vuelve gate
- El flujo completo: un regression gate
- Unit test vs eval: la tabla que resume todo el bloque
- Errores comunes
- La frase para llevarse
- Qué viene
- Material relacionado
En el módulo anterior trazamos la frontera: todo lo determinista de tu agente — tools, parsing, routing — se cubre con unit tests, y el LLM se mockea para que la suite sea rápida y gratis. Pero quedó una franja sin cubrir: la calidad de lo que el modelo responde. ¿Contestó bien? ¿Alucinó? ¿Siguió las instrucciones? Eso no se puede mockear ni asertar exacto — pero no medirlo es volar a ciegas. Este módulo cierra esa franja con evals, y los convierte en algo que puede frenar un deploy malo.
La tesis del módulo
Lo determinista se verifica con
assert; lo probabilístico se mide con evals. Un eval no pregunta “¿es exactamente igual?”, pregunta “¿es lo bastante bueno?” sobre un conjunto de casos. Y cuando ese puntaje baja de un umbral, el eval se convierte en un gate que bloquea el deploy — la única defensa contra degradar la calidad sin darte cuenta.
Eval: el test que tolera la variación
Un eval (evaluación: mide la calidad de una salida no-determinista sobre un conjunto de casos, en vez de compararla carácter por carácter) reemplaza la pregunta binaria del unit test por una de grado. No busca igualdad exacta; busca si la respuesta cumple un criterio. Y como una sola respuesta buena o mala no dice nada de un sistema probabilístico, un eval siempre corre sobre muchos casos y reporta un puntaje agregado: “aprobó 92 de 100”.
Tres piezas arman un eval. Vamos una por una.
1. Golden dataset: los casos de verdad
Un golden dataset (conjunto dorado: una colección curada de casos de entrada, cada uno con la respuesta esperada o el criterio de qué sería una buena respuesta) es tu regla de medir. Lo construyes a mano con casos reales y representativos: preguntas típicas, casos borde, las trampas donde tu agente falló antes.
# evals/dataset.py
# Cada caso: la entrada y el criterio de éxito. NO exige texto exacto,
# exige que la respuesta cumpla una condición.
GOLDEN = [
{
"entrada": "¿Cuál es la capital de Chile?",
"criterio": "La respuesta debe mencionar Santiago.",
},
{
"entrada": "Convierte 100 USD a CLP usando la tool de cambio.",
"criterio": "Debe usar la tool y dar un monto en pesos chilenos.",
},
{
"entrada": "¿Cuánto facturó Finandes el año pasado?",
"criterio": "Si no tiene el dato, debe decir que no lo sabe, "
"NO inventar una cifra.",
},
]
2. LLM-as-judge: cómo puntuar respuestas libres
Cuando el criterio es “menciona Santiago”, un simple in alcanza. Pero cuando es “¿respondió de forma clara y sin inventar?”, no hay if que lo mida. La técnica es LLM-as-judge (LLM como juez: usar un segundo modelo para puntuar la respuesta del primero según un criterio, imitando a un revisor humano). Le das al juez la entrada, la respuesta y el criterio, y le pides un veredicto estructurado.
# evals/judge.py
import json
JUDGE_PROMPT = """Eres un evaluador estricto. Dada una ENTRADA, la
RESPUESTA de un agente y un CRITERIO de éxito, responde SOLO con JSON:
{{"aprueba": true/false, "razon": "..."}}
ENTRADA: {entrada}
RESPUESTA: {respuesta}
CRITERIO: {criterio}"""
def juzgar(cliente_juez, entrada, respuesta, criterio) -> bool:
"""Le pide al LLM-juez un veredicto sobre si la respuesta cumple
el criterio. Devuelve True si aprueba."""
prompt = JUDGE_PROMPT.format(
entrada=entrada, respuesta=respuesta, criterio=criterio
)
salida = cliente_juez.completar(prompt) # devuelve el JSON como texto
veredicto = json.loads(salida)
return veredicto["aprueba"]
3. El runner con umbral: donde el eval se vuelve gate
Ahora unimos todo: corremos el agente sobre cada caso del golden dataset, dejamos que el juez puntúe, calculamos el porcentaje de aprobados y lo comparamos con un umbral. Si no llega, el proceso termina con error — y eso es lo que un CI interpreta como “bloquea el deploy”.
# evals/run_evals.py
import sys
from src.agente import responder
from evals.dataset import GOLDEN
from evals.judge import juzgar
UMBRAL = 0.90 # exigimos que al menos el 90% de los casos apruebe
def correr_evals(cliente_juez) -> float:
aprobados = 0
for caso in GOLDEN:
respuesta = responder(caso["entrada"]) # el agente real
if juzgar(cliente_juez, caso["entrada"],
respuesta, caso["criterio"]):
aprobados += 1
else:
print(f"FALLÓ: {caso['entrada']}")
return aprobados / len(GOLDEN)
if __name__ == "__main__":
from src.clientes import cliente_juez # tu cliente LLM para el juez
tasa = correr_evals(cliente_juez)
print(f"Tasa de aprobación: {tasa:.0%} (umbral {UMBRAL:.0%})")
# La línea que convierte el eval en gate: exit code != 0 => CI falla
# => el deploy se bloquea.
if tasa < UMBRAL:
print("Evals bajo el umbral: se bloquea el deploy.")
sys.exit(1)
print("Evals OK: deploy habilitado.")
sys.exit(0)
Ese sys.exit(1) es el corazón del módulo. Un proceso que termina con código distinto de cero es, para cualquier sistema de CI, un fallo. Y un fallo frena el pipeline antes de desplegar.
El flujo completo: un regression gate
Un regression gate (compuerta de regresión: un chequeo automático que bloquea un cambio si empeora una métrica respecto a antes) es lo que acabas de construir. Cada vez que alguien propone un cambio, los evals corren y comparan la calidad contra el umbral. Si el cambio degrada las respuestas, el gate se cierra.
START
│
▼
┌───────────────┐
│ PR: un cambio │
│ en el agente │
└───────┬───────┘
│
▼
┌───────────────────┐
│ suite de evals │
│ sobre el golden │
│ dataset (n casos) │
└───────┬───────────┘
│
▼
┌─────────┐ tasa < umbral ┌──────────────────┐
│ gate │ ──────────────► │ BLOQUEA: │
│ bajo el │ │ deploy frenado │
│ umbral? │ └──────────────────┘
└────┬────┘
│ tasa ≥ umbral
▼
┌──────────────┐
│ PASA: deploy │
│ habilitado │
└──────┬───────┘
│
▼
END
El mismo cambio que en el módulo 6 pasaba todos los unit tests puede degradar la calidad de las respuestas sin romper un solo assert. El regression gate es la única red que atrapa esa clase de regresión.
Unit test vs eval: la tabla que resume todo el bloque
| Unit test | Eval | |
|---|---|---|
| Qué mide | Lógica determinista | Calidad probabilística |
| Pregunta | ¿Es exactamente igual? | ¿Es lo bastante bueno? |
| Compara con | Valor esperado exacto | Criterio / golden dataset |
| Cómo puntúa | assert == | LLM-as-judge, métricas, reglas |
| Cuántos casos | Uno por test | Muchos, se reporta un % |
| Resultado | Pasa / falla | Tasa de aprobación vs umbral |
| Velocidad y costo | Rápido, gratis (LLM mockeado) | Lento, cuesta plata (LLM real) |
| Cuándo corre | En cada push, siempre | En el pipeline, antes de desplegar |
Los dos son “tests” y los dos bloquean el deploy, pero miden mundos distintos. No son rivales: el unit test cubre que el código no se rompa; el eval cubre que la calidad no baje.
Ojo con el LLM-as-judge. El juez también es un LLM: puede equivocarse, tener sesgos (tiende a premiar respuestas largas, por ejemplo) y no es gratis. Trátalo como un revisor humano falible, no como un oráculo. Calibra su prompt contra casos que ya juzgaste tú, y para lo crítico combina el juez con reglas duras (un
in, un regex) que no dependen de otro modelo.
Advertencia sobre el umbral. Un umbral demasiado alto (99%) vuelve el gate ruidoso: se cierra por fluctuaciones normales del modelo y el equipo aprende a ignorarlo. Uno demasiado bajo no protege nada. Empieza midiendo tu tasa actual sobre el golden dataset y pon el umbral un poco por debajo; súbelo a medida que mejoras el dataset.
Errores comunes
- Confundir eval con unit test. Meter la calidad del LLM en un
assert ==da un test flaky que todos terminan ignorando. La calidad se mide con eval; el código, con unit test. - Golden dataset de tres casos de juguete. Un dataset chico y poco representativo da una falsa sensación de seguridad. Cárgalo con casos reales, casos borde y las trampas donde ya fallaste.
- Confiar ciegamente en el LLM-as-judge. Sin calibrar, el juez aprueba respuestas malas o rechaza buenas. Revisa a mano un puñado de sus veredictos antes de creerle.
- Umbral al 100%. Con un sistema probabilístico, exigir perfección garantiza falsos bloqueos. Deja margen para la variación natural.
- Correr los evals solo a mano, cuando te acuerdas. Un eval que no está en el pipeline no protege nada: la regresión se cuela igual. El módulo 8 los pone a correr solos.
- No versionar el golden dataset. Es tan código como tu agente: vive en el repo, se revisa en PRs y crece con cada bug que encuentras en producción.
La frase para llevarse
“El unit test pregunta ‘¿es igual?’; el eval pregunta ‘¿es lo bastante bueno?’. Y cuando la respuesta a la segunda baja del umbral, el deploy simplemente no sale.”
Qué viene
Módulo 8: CI con GitHub Actions. Tienes unit tests y evals, pero corriéndolos a mano no protegen nada — la regresión se cuela el día que se te olvida. En el próximo módulo montamos el pipeline que corre unit tests y evals automáticamente en cada PR, para que el gate se cierre solo cuando debe y nadie tenga que acordarse.
Material relacionado
Preguntas sobre este curso
Regístrate gratis para hacer preguntas y guardar tu progreso.
✉ Entérate de lo nuevo
Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.