Las evals son los unit tests de la IA
No desplegamos codigo sin tests. ¿Por que desplegamos IA sin evals? Guia practica para construir harnesses de evaluacion que hacen los sistemas de IA confiables.
Las evals son los unit tests de la IA
No desplegamos codigo sin tests. ¿Por que desplegamos IA sin evals?
Ningun backend engineer que conozco aceptaria un PR sin cobertura de tests. Es el piso, no el techo. Pero en la industria, equipos enteros lanzan features con LLMs a produccion con nada mas que un presentimiento. Alguien abre el playground, escribe unos prompts, escanea la salida y dice "se ve bien." Esa es toda la garantia de calidad.
Llevo dos anos reemplazando esa esperanza con ingenieria. La disciplina que hizo confiable al software tradicional -- tests automatizados con criterios claros de pass/fail -- funciona para sistemas de IA tambien. Las herramientas son distintas. El modelo mental es el mismo.
El anti-patron del "Vibe Check"
El patron 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 despues, llegan los tickets de soporte. El modelo alucina detalles de politicas, cita documentos que no existen y responde con confianza cosas incorrectas.
Llamo a esto el Vibe Check Anti-Pattern: evaluar un sistema no-deterministico con mentalidad deterministica. Revisaste cinco inputs y se veian bien, asi que asumiste que todos se verian bien.
Por que falla estructuralmente:
- Los LLMs son no-deterministicos. El mismo prompt puede producir salidas distintas. Una revision manual no dice casi nada sobre la distribucion de respuestas posibles.
- Los cambios de prompt se propagan de forma impredecible. Ajustas el system prompt para un edge case y tres casos mas regresan. Sin cobertura automatizada, no lo sabras hasta que un usuario lo reporte.
- Los edge cases aparecen a escala. Tus cinco prompts de prueba representan tu imaginacion. Produccion representa miles de usuarios con miles de formas de preguntar.
- La revision humana no escala. Incluso revisando veinte ejemplos antes de cada deploy, es una fraccion minima del espacio de inputs.
Que miden las evals
Organizo las evals en cinco dimensiones:
Faithfulness (la asercion central)
¿La salida se mantiene fiel al contexto proporcionado? Si tu sistema RAG
recupera un documento que dice reembolsos en 30 dias y el modelo dice 60,
eso es un fallo de faithfulness. No importa que tan fluida suene la
respuesta -- esta mal. Faithfulness es tu assertEqual.
Relevance (el test de integracion)
¿La salida responde la pregunta del usuario? Una respuesta puede ser fiel al contexto pero no tener nada que ver con lo que se pregunto.
Completeness (la cobertura)
¿La salida incluyo toda la informacion importante? Respuestas parciales erosionan la confianza rapido.
Latency (el test de rendimiento)
¿Cuanto tardo el pipeline completo? Mido p50, p95 y p99 a traves de todo el pipeline -- retrieval, reranking, generacion -- no solo la llamada al LLM.
Cost (la economia unitaria)
¿Cuanto costo producir esa respuesta? Si tu respuesta promedio cuesta $0.12 en llamadas API y tu margen por interaccion es $0.08, tienes un feature que pierde dinero en cada request. Mido cost-per-response como metrica 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 mas basico -- checks deterministicos contra la salida del LLM. Atrapa ~60% de lo que necesitas, es rapido, 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 codigo 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 flywheel de confiabilidad
Cuando introduje eval harnesses rigidos 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 cambio el negocio: la confianza del usuario aumento y las impresiones crecieron 482%. Cuando la gente confia en la salida, usa mas el sistema. Cuando lo usa mas, lo comparte. La confiabilidad creo un flywheel que ningun feature nuevo podria haber producido.
El ciclo funciona asi:
- Mide faithfulness, relevance y completeness con evals automatizadas.
- Enforce umbrales -- no deploy si el score baja del baseline.
- Observa la mejora en metricas de engagement.
- Colecta los nuevos edge cases que revela el trafico de produccion.
- Agrega esos casos a tu eval suite y repite.
Frameworks y herramientas
- RAGAS: Ideal para pipelines RAG. Metricas de faithfulness, answer relevancy, context precision y context recall. Python-native, ligero.
- DeepEval: Experiencia tipo pytest. 60+ metricas, CI/CD compatible. Menor friccion si tu equipo ya vive en pytest.
- promptfoo: Enfoque declarativo YAML para comparar variantes de
prompts y red-teaming.
npx promptfoo evaly listo. - Harnesses custom: Cuando las metricas estandar 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 mas dificil no es tecnica -- 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/despues.
- Migraciones de modelo pasan por eval gates. He visto "upgrades" de modelo causar regresiones de 15% en faithfulness porque el nuevo modelo era mas verboso y menos preciso.
- Los eval cases crecen con incidentes de produccion. 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 features de IA con confianza. Despliegan en viernes. Cambian modelos sin miedo. Iteran sabiendo que tienen red de seguridad.
Eso es lo que significa Hardened AI en la practica. No modelos a prueba de balas -- esos no existen. Sistemas con guardrails que atrapan fallos antes que los usuarios, feedback loops que acumulan calidad, y disciplina de ingenieria que trata la confiabilidad como cimiento, no como extra.
Dejamos de desplegar codigo sin tests hace mucho tiempo. Es hora de exigirle lo mismo a la IA.