Por qué “parece correcto” no es una prueba para un prompt
Ajustas el texto de un prompt que clasifica tickets, lo pruebas con los tres ejemplos que tienes abiertos en tu notebook de pruebas y lo despliegas. Una semana después, las quejas de facturación que antes caían en la cola correcta empiezan a caer en “pregunta”. No los tres casos que revisaste: los otros cuatrocientos que no revisaste. Nada falla. No hay ningún stack trace. El prompt, leído en voz alta, sigue sonando bien.
La revisión de código y unos cuantos chequeos manuales no detectan esto porque el mismo prompt con el mismo input no devuelve el mismo texto dos veces. No puedes comparar dos ejecuciones byte a byte y llamar bug a una diferencia, como harías con una función pura. Lo que sí puedes comprobar es si cada salida tiene las propiedades que te importan: la categoría correcta, un resumen por debajo del límite de longitud, ningún campo que la fuente nunca mencionó. Un eval ejecuta esos chequeos sobre un conjunto de inputs reales cada vez que cambia el prompt o el modelo, así que la regresión aparece en tu terminal en lugar de en el ticket de un cliente.
Cuáles son las partes de un LLM eval
Todo eval, sea cual sea la herramienta que lo ejecute, tiene tres partes: un dataset de inputs representativos, un assert que comprueba cada salida contra lo que ese input debería producir, y una tasa de aciertos que sigues de una ejecución a otra.
Piénsalo como la suite de pruebas de una función que devuelve una frase ligeramente distinta cada vez. El assert no puede comprobar una coincidencia exacta, así que comprueba una propiedad de la frase: si nombra la categoría correcta, si el número que contiene se puede parsear. Una vez aceptas que la prueba es “esto se cumple” y no “esto es idéntico”, escribir una es ingeniería ordinaria.
Cómo construir un dataset golden para un LLM eval
Toma un prompt que convierte un correo de soporte en bruto en un ticket estructurado:
| |
El dataset golden es un conjunto de correos reales emparejados con la respuesta que ya sabes que es correcta. Reales, no inventados para que el prompt quede bien:
| |
Entre cinco y diez casos bastan para empezar. El tamaño importa menos que de dónde vienen los casos siguientes: fallos reales de producción.
Cómo escribir asserts deterministas para la salida de un LLM
La mayor parte de lo que sale mal en este prompt no tiene nada que ver con el gusto. El modelo devuelve JSON malformado, o se inventa una cuarta categoría, o escribe cuarenta palabras cuando pediste doce. El código normal detecta todo eso, sin necesidad de un segundo modelo:
| |
Ejecútalo sobre todo el dataset y obtienes una tasa de aciertos, no un simple sí o no:
| |
Estos chequeos no cuestan nada y nunca fallan de forma errática: la misma salida mala falla de la misma forma cada vez. Escríbelos primero. Detectan la mayoría de las regresiones reales, como un cambio de esquema o un resumen que se duplicó de longitud sin que nadie se diera cuenta.
Cuándo usar LLM-as-judge y cómo calibrarlo
Categoría y urgencia son objetivas, y el dataset ya contiene la respuesta correcta. La calidad del resumen no. “Reembolso necesario, cargo duplicado” y “El cliente tiene una pregunta de facturación” son ambos JSON válido, ambos de menos de 12 palabras, y solo uno de los dos describe de verdad el correo. Ninguna regex detecta eso, así que un segundo modelo lo evalúa contra una rúbrica:
| |
Antes de confiar en ese veredicto sobre todo el dataset, ejecuta el judge sobre 15-20 casos que una persona ya haya evaluado a mano y cuenta con qué frecuencia coinciden. Un judge que discrepa de las personas una de cada tres veces es un assert débil disfrazado con la autoridad de un modelo.
Hay dos sesgos que vale la pena vigilar. Los judges tienden a puntuar más alto las respuestas más largas y elaboradas aunque una más corta sea igual de correcta. Y un modelo que evalúa sus propias salidas las puntúa más favorablemente de lo que lo haría un modelo independiente. Usa un modelo distinto como judge cuando puedas, y mantén el prompt acotado: una pregunta sí/no por llamada, no cinco puntuaciones en una sola petición.
| Assert determinista | LLM-as-judge | |
|---|---|---|
| Coste por caso | Gratis | Una llamada extra al modelo |
| Qué detecta | Esquema, formato, hechos ya presentes en el dataset | Calidad subjetiva, relevancia, tono |
| Repetibilidad | Misma salida, mismo veredicto | Algo de deriva incluso a temperatura 0 |
| Coste de configuración | Escribir el chequeo y listo | Necesita calibración previa con ejemplos evaluados por personas |
En términos de suite de pruebas, los chequeos deterministas son tus pruebas unitarias: corren en cada cambio y fallan por una razón concreta, no por capricho. El judge se parece más a un chequeo de aceptación, el que de otra forma haría una persona leyendo cada salida. Los compromisos entre esos niveles son los mismos que se tratan en pruebas unitarias vs de integración vs end-to-end.
Cómo ejecutar LLM evals en CI para detectar regresiones
Un eval que solo se ejecuta cuando alguien se acuerda no detecta nada. Pon el script junto al prompt que prueba (evals/triage.py al lado de prompts/triage.py) y ejecútalo en CI con cada cambio en cualquiera de los dos archivos, antes del merge. Sigue la tasa de aciertos a lo largo del tiempo y haz que la build falle cuando caiga por debajo de una base que hayas fijado. Una caída del 95% al 88% es una regresión que merece bloquear, aunque la mayoría de los casos sigan pasando.
Mantén un conjunto de regresión dentro del dataset: cada caso que fue en su momento un bug reportado, fijado de forma permanente tras la corrección. Para un prompt, esto te da el ciclo descrito en qué es realmente el TDD. Cambia el prompt, vuelve a ejecutar la suite, mira exactamente qué asserts se dieron la vuelta, corrige, repite. Alimenta el dataset con los nuevos fallos de producción a medida que ocurren, para no estar probando solo los inputs para los que se diseñó el prompt.
Cómo evaluar pipelines de tool calling y RAG
Las mismas tres partes se aplican aquí también. Para un ciclo de tool calling, el dataset empareja cada input con el nombre de la herramienta correcta y sus argumentos, y el assert comprueba que el modelo eligió esa herramienta con los campos requeridos rellenados. La mecánica del propio ciclo está en function calling y tool use en LLM.
Una pipeline RAG necesita dos puntuaciones separadas. ¿El retrieval trajo el chunk que contiene la respuesta? ¿La generación usó ese chunk correctamente? Si las puntúas juntas, no puedes saber cuál de las dos mitades se rompió. Mira cómo funciona RAG para ver qué pipeline estás dividiendo en dos.
Qué errores evitar al construir un LLM eval
Construir el dataset con casos que ya pasan. Cinco ejemplos elegidos porque el prompt los maneja bien pasarán siempre y no te dirán nada sobre el input que todo lo romperá el mes que viene. Cada fallo real que te llegue debe entrar en el dataset ese mismo día.
Comprobar una coincidencia exacta sobre una salida variable. summary == "Reembolso necesario por cargo duplicado" falla en el momento en que el modelo cambia una sola palabra, incluso cuando la nueva frase es igual de correcta. Comprueba la propiedad, no el texto: el número de palabras, la categoría, si aparece un hecho requerido.
Saltarse la calibración del judge. Un judge que nunca has comparado ni siquiera con una docena de ejemplos evaluados por personas te entrega sus propios sesgos como si fueran la verdad, incluido el que favorece su propia salida.
Volver a ejecutar solo el caso que acabas de corregir. Así es como un bug antiguo reaparece sin que nadie se dé cuenta. El conjunto de regresión existe precisamente para evitarlo.
Cuándo no vale la pena construir una suite de LLM eval
Un prompt que vas a ejecutar una sola vez, o que una persona revisa en cada llamada, no necesita un arnés de pruebas. Pon el chequeo de validez JSON en línea si es barato y sigue adelante. La suite se amortiza cuando un prompt se despliega repetidamente a usuarios reales o esperas modificarlo más de un par de veces. Ese es el momento en que “se veía bien en mis tres ejemplos” deja de ser suficiente.
Para empezar: cinco inputs reales, un assert determinista por cada propiedad que te importe, y un judge LLM solo para la dimensión que el código no puede comprobar. Ejecútalo antes de tu próximo cambio de prompt, no después de la queja.