Las evals son los unit tests de la IA
No desplegamos código sin tests. ¿Por qué desplegamos IA sin evals?
Ningún backend engineer que conozco aceptaría un PR sin cobertura de
tests. Es el piso, no el techo. Pero en la industria, equipos enteros
lanzan funcionalidades con LLMs a producción con nada más que un presentimiento.
Alguien abre el playground, escribe unos prompts, escanea la salida y
dice "se ve bien." Esa es toda la garantía de calidad.
Llevo dos años reemplazando esa esperanza con ingeniería. La disciplina
que hizo confiable al software tradicional, tests automatizados con
criterios claros de pass/fail, funciona para sistemas de IA también.
Las herramientas son distintas. El modelo mental es el mismo.
El anti-patrón del "Vibe Check"
El patrón es siempre igual: el equipo construye un pipeline RAG, lo
prueba con cuatro o cinco prompts manuales, la salida se lee bien, y lo
lanzan. Dos semanas después, llegan los tickets de soporte. El modelo
alucina detalles de políticas, cita documentos que no existen y responde
con confianza cosas incorrectas.
Llamo a esto el Vibe Check Anti-Pattern: evaluar un sistema
no-determinístico con mentalidad determinística. Revisaste cinco inputs y
se veían bien, así que asumiste que todos se verían bien.
Por qué falla estructuralmente:
- Los LLMs son no-determinísticos. El mismo prompt puede producir
salidas distintas. Una revisión manual no dice casi nada sobre la
distribución de respuestas posibles.
- Los cambios de prompt se propagan de forma impredecible. Ajustas el
system prompt para un edge case y tres casos más regresan. Sin cobertura
automatizada, no lo sabrás hasta que un usuario lo reporte.
- Los edge cases aparecen a escala. Tus cinco prompts de prueba
representan tu imaginación. Producción representa miles de usuarios con
miles de formas de preguntar.
- La revisión humana no escala. Incluso revisando veinte ejemplos antes
de cada deploy, es una fracción mínima del espacio de inputs.
Qué miden las evals
Organizo las evals en cinco dimensiones:
Faithfulness (la aserción central)
¿La salida se mantiene fiel al contexto proporcionado? Si tu sistema RAG
recupera un documento que dice reembolsos en 30 días y el modelo dice 60,
eso es un fallo de faithfulness. No importa qué tan fluida suene la
respuesta. Está mal. Faithfulness es tu assertEqual.
Relevance (el test de integración)
¿La salida responde la pregunta del usuario? Una respuesta puede ser fiel
al contexto pero no tener nada que ver con lo que se preguntó.
Completeness (la cobertura)
¿La salida incluyó toda la información importante? Respuestas parciales
erosionan la confianza rápido.
Latency (el test de rendimiento)
¿Cuánto tardó el pipeline completo? Mido p50, p95 y p99 a través de
todo el pipeline (retrieval, reranking, generación), no solo la
llamada al LLM.
Cost (la economía unitaria)
¿Cuánto costó producir esa respuesta? Si tu respuesta promedio cuesta
$0.12 en llamadas API y tu margen por interacción es $0.08, tienes un
funcionalidad que pierde dinero en cada request. Mido cost-per-response como
métrica de primer nivel.
Construye tu primer eval harness
Paso 1: Define tus test cases
Piensa en estos como fixtures de pytest: inputs estructurados con
propiedades esperadas:
# eval_cases.py
EVAL_CASES = [
{
"input": "What is the refund policy?",
"context": "Refunds are available within 30 days of purchase. "
"Original receipt required. No refunds on digital goods.",
"expected_substrings": ["30 days", "receipt"],
"expected_not_present": ["60 days", "no refund policy"],
"tags": ["policy", "factuality"],
},
]
Paso 2: Runner basado en aserciones
El eval más básico: checks determinísticos contra la salida del LLM.
Atrapa ~60% de lo que necesitas, es rápido, barato y no requiere
llamadas LLM adicionales:
# eval_runner.py
from openai import OpenAI
client = OpenAI()
def run_llm(prompt: str, context: str) -> str:
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content":
"Answer using ONLY the provided context."
f"\n\nContext:\n{context}"},
{"role": "user", "content": prompt},
],
temperature=0,
)
return response.choices[0].message.content
def eval_deterministic(cases):
results = []
for case in cases:
output = run_llm(case["input"], case["context"])
passed = all(
s.lower() in output.lower()
for s in case["expected_substrings"]
)
no_hallucination = all(
s.lower() not in output.lower()
for s in case["expected_not_present"]
)
results.append({
"input": case["input"],
"output": output,
"passed": passed and no_hallucination,
})
return results
Paso 3: LLM-as-Judge para scoring matizado
El substring matching no captura tono, completeness ni si la respuesta
realmente ayuda. Para eso, uso un segundo LLM como juez:
import json
def judge_faithfulness(question, context, answer):
rubric = (
"Score faithfulness 0.0-1.0.\n"
"1.0 = every claim supported by context\n"
"0.0 = answer contradicts or fabricates\n\n"
f"QUESTION: {question}\n"
f"CONTEXT: {context}\nANSWER: {answer}\n\n"
'Respond JSON: {"score": <float>, "reason": "<str>"}'
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": rubric}],
temperature=0,
)
return json.loads(response.choices[0].message.content)
Paso 4: Gate de pass/fail para CI
Combina ambos enfoques en un suite que sale con código non-zero si falla
, tu pipeline de CI lo trata exactamente como un test fallido:
def run_eval_suite(cases, threshold=0.85):
results = []
for case in cases:
output = run_llm(case["input"], case["context"])
judgment = judge_faithfulness(
case["input"], case["context"], output
)
results.append({
"input": case["input"],
"score": judgment["score"],
"passed": judgment["score"] >= threshold,
})
pass_rate = sum(1 for r in results if r["passed"]) / len(results)
print(f"Eval Results: {pass_rate:.0%} pass rate")
if pass_rate < threshold:
raise SystemExit(f"FAILED: {pass_rate:.0%} < {threshold:.0%}")
return results
El ciclo virtuoso de confiabilidad
Cuando introduje eval harnesses rígidos para reemplazar vibe checks en un
sistema de content retrieval, el efecto inmediato fue predecible:
atrapamos regresiones antes que los usuarios. Las alucinaciones bajaron.
Pero el efecto de segundo orden cambió el negocio: la confianza del
usuario aumentó y las impresiones crecieron 482%. Cuando la gente
confía en la salida, usa más el sistema. Cuando lo usa más, lo comparte.
La confiabilidad creó un ciclo virtuoso que ninguna funcionalidad nueva podría haber
producido.
El ciclo funciona así:
- Mide faithfulness, relevance y completeness con evals automatizadas.
- Enforce umbrales: no deploy si el score baja del baseline.
- Observa la mejora en métricas de engagement.
- Colecta los nuevos edge cases que revela el tráfico de producción.
- Agrega esos casos a tu eval suite y repite.
Frameworks y herramientas
- RAGAS: Ideal para pipelines RAG. Métricas de faithfulness, answer
relevancy, context precision y context recall. Python-native, ligero.
- DeepEval: Experiencia tipo pytest. 60+ métricas, CI/CD compatible.
Menor fricción si tu equipo ya vive en pytest.
- promptfoo: Enfoque declarativo YAML para comparar variantes de
prompts y red-teaming.
npx promptfoo eval y listo.
- Harnesses custom: Cuando las métricas estándar no capturan lo que
importa en tu dominio. Un AI legal necesita criterios de faithfulness
distintos a un bot de soporte.
El cambio cultural
La parte más difícil no es técnica. Es cultural. Lo que aplico en los
equipos con los que trabajo:
- Cada PR que toca un prompt incluye resultados de eval. Sin
excepciones. Si cambiaste el system prompt, muestra scores antes/después.
- Migraciones de modelo pasan por eval gates. He visto "upgrades" de
modelo causar regresiones de 15% en faithfulness porque el nuevo modelo
era más verboso y menos preciso.
- Los eval cases crecen con incidentes de producción. Cada respuesta
mala reportada se convierte en un nuevo eval case. Tu suite se vuelve
un registro vivo de cada modo de fallo.
- Dashboards, no spreadsheets. Rastrea scores en el tiempo para
detectar tendencias.
Los equipos que tratan las evals como infraestructura, siempre
corriendo, siempre creciendo, siempre bloqueando deploys, son los que
lanzan funcionalidades de IA con confianza. Despliegan en viernes. Cambian
modelos sin miedo. Iteran sabiendo que tienen red de seguridad.
Eso es lo que significa simplificar la IA para producción. No modelos a prueba
de balas. Esos no existen. Sistemas con guardrails que atrapan fallos
antes que los usuarios, bucles de retroalimentación que acumulan calidad, y disciplina
de ingeniería que trata la confiabilidad como cimiento, no como extra.
Dejamos de desplegar código sin tests hace mucho tiempo. Es hora de
exigirle lo mismo a la IA.