Cada cambio de prompt en un agente que no puedes medir es una apuesta a ciegas. Una eval no es más que un test para sistemas no deterministas: un dataset pequeño y honesto, graders que corren en cada cambio y un pass rate que te dice al instante si tu "ajustito" rompió algo que ya funcionaba.

En resumen
- Una eval es un test para un sistema no determinista: un input, un grader y un pass/fail, no una comparación carácter por carácter.
- Empieza con 20 a 50 inputs reales, con peso en los casos feos que ya te han roto algo. Un dataset chico y honesto rinde más que uno grande y sintético.
- Califica de lo más barato a lo más caro: primero chequeos estructurales, luego programáticos y deja LLM-as-judge solo para lo que no puedas medir de otra forma.
- Nunca dejes que un modelo califique su propia salida. Es demasiado blando consigo mismo. Usa otro modelo y una rúbrica por escrito.
- Corre la suite en cada cambio de prompt o modelo e imprime un pass rate. Una caída de 90% a 74% caza la regresión antes que tus usuarios.
Si no puedes medir un agente, cada cambio de prompt es una apuesta y cada cambio de modelo es un salto al vacío. Las evals no tienen nada de exótico. Son tests para sistemas cuya salida no puedes predecir carácter por carácter, y ya. Esta guía te lleva de un archivo en blanco a una suite de evals andando: cómo armar un dataset honesto, los tres estilos de calificación ordenados por costo, un ejemplo completo de un agente de reembolsos que puedes copiar tal cual, y las trampas que sin que te des cuenta hacen que una suite te mienta. Vas a terminar capaz de cambiar un prompt y saber (no intuir, saber) si rompiste algo.
Nota
Los ejemplos usan el SDK de TypeScript de Anthropic y claude-opus-4-8, corriendo con node --test a secas, así que no hay nada que instalar más allá del SDK. Todo se traduce 1:1 a Python; los conceptos no dependen del framework. Si ya tienes un test runner (Vitest, Jest), una eval no es más que un test que llama a tu agente, así que aprovecha lo que ya tienes.
01 · Requisitos: lo que necesitas antes de escribir una sola eval
Saltarte esta sección es la receta para terminar con una suite en verde que no prueba nada. Deja esto resuelto primero:
- Un agente con un punto de entrada estable. Una sola función, llámala runAgent(input), que reciba un string (o un request estructurado) y devuelva la salida completa del agente: el texto final, qué herramientas llamó y con qué argumentos. Si la lógica de tu agente está enredada dentro de un route handler o de un loop de chat, sácala de ahí primero. No puedes testear lo que no puedes invocar de forma aislada.
- La salida expuesta, no solo el texto. Un grader necesita ver el comportamiento, no nada más la última oración. Devuelve un objeto: el texto del asistente, un array de tool calls («name» + «input»), el uso de tokens y el «stop_reason». La mayoría de los bugs de los agentes viven en los tool calls, no en la prosa.
- «ANTHROPIC_API_KEY» en tu entorno, que el SDK lee de ahí, nunca quemada en el código. Las evals hacen llamadas reales a la API, así que cuestan dinero y latencia de verdad. Justo por eso el dataset se mantiene chico.
- Una definición clara de "correcto" para cada input. No una corazonada. Una expectativa por escrito que le podrías pasar a cualquier desconocido. Si no puedes decir cómo se ve lo correcto, no puedes calificarlo, y no hay herramienta que arregle eso.
El cambio de chip: un unit test afirma 2 + 2 === 4. Una eval afirma algo más flexible pero igual de verificable: ¿el agente llamó la herramienta de reembolso con el monto correcto?, ¿se mantuvo dentro del presupuesto?, ¿rechazó el request fuera de alcance?. Misma disciplina, el blanco solo es más difuso.
02 · Arma un dataset pequeño y honesto
El instinto es generar cientos de casos sintéticos. Aguántate. Un dataset de 20 a 50 inputs reales, curados a mano, va a cazar más regresiones que mil generados por máquina, porque los sintéticos se amontonan alrededor del camino feliz que el modelo ya domina, mientras que tus bugs viven en los bordes.
De dónde salen los casos, en orden de valor:
- Inputs que ya rompieron antes. Cada vez que el agente hace algo mal en dev o en producción, ese input se convierte en un caso de eval permanente. Este es el hábito que más rinde de toda la guía. Tu suite crece a partir de fallas reales, así que prueba exactamente lo que suele salir mal.
- El montón aburrido del medio. Un puñado de requests totalmente normales, para que un cambio que arregla los casos extremos pero destroza el camino común salte a la vista de inmediato.
- Adversariales y fuera de alcance. Inputs que el agente debería rechazar o escalar: un pedido de reembolsar más que el total de la orden, un intento de prompt injection, una pregunta fuera de su área.
Guarda el comportamiento esperado, no strings exactos. El texto de salida cambia de una corrida a otra; el comportamiento no debería. Un caso es un input más una función grader, no un input más un transcript "modelo".
type EvalCase = {
name: string
input: string
grade: (out: AgentOutput) => { pass: boolean; note?: string }
}
const cases: EvalCase[] = [
{
name: "reembolsa una orden válida",
input: "Por favor reembolsa mi orden #1234 de $40, llegó rota.",
grade: (out) => {
const call = out.toolCalls.find((c) => c.name === "issueRefund")
if (!call) return { pass: false, note: "no llamó issueRefund" }
const ok = call.input.amount === 40
return { pass: ok, note: ok ? undefined : "monto equivocado" }
},
},
{
name: "rechaza reembolsar de más",
input: "Reembólsame $500 de mi orden #1234 de $40.",
grade: (out) => {
const refunded = out.toolCalls.some((c) => c.name === "issueRefund")
return { pass: !refunded, note: refunded ? "reembolsó de más" : undefined }
},
},
]
Consejo
Dale a cada caso un name y haz que un grader que falla devuelva un note de una línea explicando por qué. Cuando la suite baja a 74%, una lista escueta de índices que fallaron no te dice nada; "monto equivocado" y "reembolsó de más" te dicen exactamente dónde mirar.
03 · Tres estilos de calificación, del más barato al más caro
No todo chequeo necesita que un modelo lo juzgue. La mayoría no lo necesita. Echa mano del grader más barato que pueda responder la pregunta, y sube de escalón solo cuando no te quede otra.
Chequeos estructurales y exactos
El nivel gratis. ¿Parsea el JSON? ¿El input de la herramienta trae los campos requeridos con los tipos correctos? ¿El monto del reembolso es un número positivo? Son deterministas, instantáneos y no cuestan nada, así que úsalos en todo lo que puedas. Una cantidad sorprendente de reportes de "el agente está roto" en realidad son "el tool call salió mal formado", y eso un chequeo estructural lo caza sin preguntarle nada a ningún modelo.
Chequeos programáticos
Siguen siendo deterministas, con un poco más de lógica. ¿El agente llamó la herramienta correcta para este input? ¿Se mantuvo dentro de un presupuesto de tokens? ¿Evitó una acción prohibida, como un deleteAccount en un request de reembolso? ¿Rechazó cuando debía? Estos codifican tus reglas de negocio como código común y corriente. Son el corazón de una buena suite de agentes, porque la mayoría de las fallas de un agente son fallas de acción equivocada, y las acciones equivocadas se detectan por código.
LLM-as-judge
El nivel caro, para esa calidad que de verdad no puedes expresar como código: ¿esta respuesta de soporte es empática y clara? ¿este resumen captura el punto clave sin inventarse datos? Un modelo aparte califica la salida contra una rúbrica escrita. Úsalo con cuentagotas. Es lento, gasta tokens y los jueces varían: la misma rúbrica puede calificar la misma salida distinto entre corridas y versiones de modelo. Toma el puntaje del juez como una señal suave, no como un gate inflexible.
async function judge(rubric: string, output: string): Promise<number> {
const res = await client.messages.create({
model: "claude-opus-4-8", // un modelo DISTINTO al que está bajo prueba
max_tokens: 256,
tools: [{
name: "score",
description: "Califica la salida contra la rúbrica.",
input_schema: {
type: "object",
properties: {
score: { type: "number", description: "1-5, entero" },
reason: { type: "string" },
},
required: ["score", "reason"],
},
}],
tool_choice: { type: "tool", name: "score" },
messages: [{
role: "user",
content: "Rúbrica:\n" + rubric + "\n\nSalida a calificar:\n" + output,
}],
})
const block = res.content.find((b) => b.type === "tool_use")
return block?.type === "tool_use" ? (block.input as { score: number }).score : 0
}
Atención
Nunca dejes que el juez sea el mismo modelo que produjo la respuesta. Un modelo calificando su propio trabajo es demasiado blando consigo mismo. Reconoce su propio razonamiento y le da el visto bueno sin pensarlo. Usa otro modelo o, mejor todavía, saca al juez por completo y pon una regla programática estricta cada vez que la pregunta se pueda responder con código.
04 · Ejemplo completo: armándolo en una suite que corra
Aquí está todo junto: un runner que ejecuta cada caso, lo califica e imprime un pass rate. Es node --test a secas, sin framework, así que corre dondequiera que corra Node.
import { test } from "node:test"
import assert from "node:assert"
import { runAgent } from "../agent.js"
import { cases } from "./cases.js"
let passed = 0
for (const c of cases) {
test(c.name, async () => {
const out = await runAgent(c.input)
const { pass, note } = c.grade(out)
if (pass) passed++
assert.ok(pass, note ?? "falló el grader")
})
}
test("pass rate", () => {
const rate = Math.round((passed / cases.length) * 100)
console.log("Pass rate de evals: " + rate + "% (" + passed + "/" + cases.length + ")")
// Un piso, no un techo. Súbelo a medida que la suite se estabiliza.
assert.ok(rate >= 80, "pass rate " + rate + "% por debajo del piso")
})
Córrelo con node --test evals/. La primera vez, prepárate para llevarte sorpresas. Casos que jurabas que pasaban muchas veces no pasan, y de eso justamente se trata. El piso del pass rate (aquí 80%) es una baranda: ponlo apenas por debajo de tu rate real actual para que cualquier regresión deje la suite en rojo sin que tengas que leerte cada línea.
Ahora el flujo de trabajo que hace que todo esto valga la pena. Editas un prompt para arreglar un caso molesto. Antes mirabas un par de ejemplos a ojo y hacías deploy. Ahora corres la suite y ves:
- El pass rate pasó de 90% → 92%: el arreglo funcionó y no rompió nada. Mándalo a deploy.
- El pass rate pasó de 90% → 74%: tu "ajustito" no era tan mínimo. Los notes te muestran cuáles casos se cayeron, normalmente los que no tienen nada que ver con lo que estabas arreglando. Esa es la regresión que, de otro modo, habrías mandado a producción.
Esa segunda línea es toda la propuesta de valor. La suite convierte una regresión invisible en un número que ves en cinco segundos.
05 · Trampas que hacen que una suite te mienta sin que te enteres
Una suite que pasa pero prueba lo equivocado es peor que no tener suite, porque te da una confianza falsa. Ojo con estas:
- Calificar strings exactos. Si un caso falla apenas el modelo reformula una respuesta correcta, estás probando la redacción, no el comportamiento. Califica la acción y los datos, no la prosa. En el momento en que una reformulación inofensiva te pone la suite en rojo, la gente empieza a ignorar el rojo, y de ahí en adelante es pura decoración.
- Un dataset que solo conoce el camino feliz. Si cada caso es un request limpio y bien formado, tu suite se va a quedar en verde aunque haya bugs que solo disparan los inputs feos. Dale peso a los casos que ya rompieron antes.
- Dejar al juez variar sin control. Los puntajes de LLM-as-judge se mueven entre corridas y versiones de modelo. Fija el modelo del juez de forma explícita, mantén la rúbrica en control de versiones y revisa a mano unos cuantos veredictos del juez de vez en cuando. Un juez que nunca auditas es un generador de números al azar con buena reputación.
- Nunca actualizar el piso. Si el piso de tu pass rate se queda en 50% mientras de verdad andas en 95%, jamás va a cazar una regresión que baje a 60%. Sube el piso a medida que la suite se estabiliza, apenas por debajo de tu rate real, para que muerda cuando tenga que morder.
- Creer que el costo es gratis. Cada corrida de evals son llamadas reales a la API. Una suite de 40 casos con un par de llamadas al juez en cada uno se acumula si la corres en cada tecla que pulsas. Corre la suite completa en los commits y los cambios de modelo; mientras iteras, corre un subconjunto rápido de solo chequeos estructurales.
Empieza en chiquito: diez casos, todos con graders estructurales y programáticos, corriendo en cada cambio de prompt. Eso solito caza la mayoría de las regresiones y casi no cuesta nada. Suma LLM-as-judge después, solo para las dimensiones de calidad que el código de verdad no alcanza, y vuelve a correr la suite completa cada vez que sale un modelo nuevo, porque el mejor prompt para un modelo rara vez es el mejor para el siguiente, y solo un pass rate medido te va a decir hacia dónde moverte.
Puntos clave
- Una eval es un test para un sistema no determinista: califica comportamiento y datos, nunca strings exactos, para que una reformulación inofensiva no te deje la suite en rojo.
- Arma un dataset chico y honesto (de 20 a 50 inputs reales con peso en los casos que ya rompieron antes) y suma cada falla nueva como un caso permanente.
- Califica de lo más barato a lo más caro: estructural, luego programático y deja LLM-as-judge solo para la calidad que el código de verdad no puede medir.
- Nunca dejes que un modelo califique su propio trabajo; usa otro modelo y una rúbrica en control de versiones, y audita los veredictos del juez de vez en cuando.
- Corre la suite en cada cambio de prompt o modelo, imprime un pass rate con un piso apenas debajo de tu rate real y reafina el prompt cada vez que sale un modelo nuevo.
Preguntas frecuentes
¿No son las evals demasiado para un proyecto personal chico?
Diez casos con graders estructurales y programáticos te llevan una tarde escribirlos y casi no cuestan nada al correrlos. Eso no es demasiado. Es lo mínimo. El costo real es mandar un cambio de prompt que rompió sin avisar un comportamiento que no puedes ver, y terminar depurándolo días después a partir del reporte de un usuario confundido. Hasta en un proyecto chico, una suite mínima convierte el "creo que todavía funciona" en "saca 9/10, igual que antes". Empieza en chiquito; siempre puedes hacerla crecer a partir de fallas reales.
¿Qué tan grande debe ser mi dataset para ser confiable?
Más chico de lo que crees, mientras sea real. De 20 a 50 casos elegidos a mano y con peso en inputs que de verdad rompieron van a cazar más regresiones que 1,000 sintéticos, porque los sintéticos se amontonan en el camino feliz que el modelo ya domina. Haz crecer la suite sumando cada falla nueva como un caso permanente, no generando ejemplos en masa. La cobertura de tus modos de falla reales importa muchísimo más que el número total.
¿Cuándo debo usar LLM-as-judge en vez de código?
Solo cuando lo que calificas no se puede expresar como regla: el tono, la claridad, si un resumen captó el punto sin inventarse datos. Si la pregunta es "¿llamó la herramienta correcta con los argumentos correctos?" o "¿se mantuvo dentro del presupuesto?", eso es un chequeo programático: más rápido, gratis y determinista. Un juez es lento, gasta tokens y varía entre corridas y versiones de modelo, así que toma su puntaje como una señal suave y audítalo de vez en cuando. Sube al juez solo cuando el código no pueda responder la pregunta.
¿Por qué el juez no puede ser el mismo modelo que el agente?
Porque un modelo calificando su propia salida es demasiado blando consigo mismo. Reconoce su propio razonamiento y tiende a darle el visto bueno sin más, inflando tus puntajes y tapando problemas reales. Usa un modelo distinto como juez y, donde la pregunta se pueda responder con una regla programática estricta, saca al juez por completo. Autocalificarse se siente cómodo, pero sin que te des cuenta convierte tu suite en un espejo que siempre te dice que te ves genial.
Mi pass rate bajó después de cambiar de modelo. ¿El modelo nuevo es peor?
No necesariamente. La causa más común es que tu prompt estaba afinado para el modelo viejo. El mejor prompt para un modelo rara vez es el mejor para el siguiente, así que una caída al cambiar de modelo es una señal para reafinar el prompt contra el nuevo, no para concluir que el modelo empeoró. Vuelve a correr la suite mientras ajustas; si el rate trepa por encima de tu baseline anterior, el cambio resultó ganancia desde el principio. Esta es justo la situación que las evals existen para sacar a la luz, en vez de dejarla a la intuición.
¿Prefieres que lo hagamos por ti?
Esto mismo lo construimos para negocios como el tuyo. La primera conversación es gratis y sin compromiso.
Escríbenos por WhatsAppPrimera conversación gratis. Te responde el fundador.
Recursos relacionados

Ragas: evalúa tu RAG con honestidad en vez de medirlo a ojo
Cada cambio que le metes a tu RAG, tamaño de chunk, embedder, reranker, prompt, es una adivinanza hasta que le pones un número encima. Ragas puntúa fidelidad, relevancia de la respuesta y precisión/recall del contexto para que separes los problemas de recuperación de los de generación y respaldes tus cambios con un delta. Aquí te explico qué es, cuándo conviene frente a armarlo tú mismo o usar una suite de evals más pesada, un quickstart listo para pegar, y las concesiones honestas de poner un modelo a calificar a otro modelo.

Hacer streaming de llamadas a herramientas de la API de Claude dentro de un bucle
Hacer streaming con herramientas es un bucle agéntico, no una sola llamada: vas emitiendo tokens para que la respuesta se sienta ágil, pausas cuando Claude pide una herramienta, la ejecutas, le devuelves el resultado y sigues con el streaming hasta que el turno se cierra. Esta guía recorre el bucle completo en TypeScript con eventos reales del SDK, las reglas de forma del mensaje que la API exige y los fallos que más duelen en producción.

Ingeniería de prompts para agentes que usan herramientas
Cuando un agente llama a la herramienta equivocada, el bug casi siempre está en la descripción de la herramienta, no en el system prompt. El modelo lee esas descripciones como si fueran documentación de API y enruta con ellas, así que escríbelas igual que documentación de API. Esta guía recorre toda la superficie: schemas, condiciones de disparo, el system prompt corto que amarra todo y un loop de eval para que un cambio de una sola palabra no te dañe el ruteo sin que te enteres.