7 min de lectura
Las evals son los unit tests de la IA
No desplegamos código sin tests. ¿Por qué desplegamos IA sin evals? Guía práctica para construir harnesses de evaluación que hacen los sistemas de IA confiables.
Senior Software Engineer
7 min de lectura
No desplegamos código sin tests. ¿Por qué desplegamos IA sin evals? Guía práctica para construir harnesses de evaluación que hacen los sistemas de IA confiables.
Senior Software Engineer
La mayoría de los equipos agregan guardrails después del primer incidente. Para entonces, datos ya se filtraron o el agente acumuló $2,000 en una factura de API. Esta es la guía para construirlos desde el día uno.
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 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:
Organizo las evals en cinco dimensiones:
¿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.
¿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ó.
¿La salida incluyó toda la información importante? Respuestas parciales erosionan la confianza rápido.
¿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.
¿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.
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"],
},
]
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
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)
Combina ambos enfoques en una 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
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í:
npx promptfoo eval y listo.La parte más difícil no es técnica. Es cultural. Lo que aplico en los equipos con los que trabajo:
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.