IA 6 jul 2026 · 8 min de lectura

Evals para features con LLM: cómo sé que no rompí nada

Yohangel Ramos

Yohangel Ramos

Tech Lead · Senior Fullstack Developer

La primera vez que cambié un prompt en producción en JXBS, lo hice como cambiaba cualquier otra cosa: lo probé con dos o tres ejemplos a mano, me pareció mejor y lo desplegué. A la semana un usuario reportó que el matching devolvía basura en un caso que antes funcionaba. No había roto el código: había roto el comportamiento del modelo, y no tenía forma de saberlo porque no medía nada. Los evals son la pieza que convierte "trabajar con LLMs" de artesanía supersticiosa en ingeniería. Este artículo es el mínimo viable de evals que uso hoy en cada feature que lleva un modelo dentro.

Un LLM no tiene tests unitarios, y eso es el problema

Cuando escribes una función normal, un test unitario fija su contrato: entra esto, sale aquello, y si alguien lo rompe el pipeline te avisa. Con un feature basado en LLM ese contrato se difumina. El mismo prompt con el mismo input puede dar respuestas distintas, y un cambio aparentemente inofensivo —reordenar dos frases del system prompt, subir de una versión del modelo a la siguiente— puede degradar casos que ni sabías que dependían de ese detalle.

El error mental es tratar el prompt como código estable y el modelo como una dependencia fija. No lo son. El prompt es un parámetro que ajustas constantemente y el modelo es una dependencia que cambia debajo de ti sin avisar. Sin una batería de casos que corras en cada cambio, estás desplegando a ciegas y descubriendo las regresiones a través de los usuarios. Que es la peor forma de descubrirlas.

El eval mínimo: un dataset dorado y una métrica que te importe

No hace falta una plataforma de evals para empezar. Hace falta un archivo con casos representativos y una función que puntúe. Un caso es un input real, la salida esperada (o una propiedad que la salida debe cumplir) y opcionalmente por qué está ahí. Yo los saco de tres sitios: ejemplos que sé que funcionan, casos límite que me preocupan, y sobre todo los bugs reales que reporta la gente —cada regresión de producción se convierte en un caso de eval para que no vuelva a pasar.

type EvalCase = {
  name: string;
  input: MatchInput;
  // Una aserción sobre la salida, no una igualdad exacta:
  // con LLMs comparar strings palabra por palabra es inútil.
  expect: (output: MatchOutput) => boolean;
};

const cases: EvalCase[] = [
  {
    name: 'candidato senior con stack exacto sale primero',
    input: seniorReactExacto,
    expect: (out) => out.ranked[0].id === 'cand_42',
  },
  {
    name: 'no inventa habilidades que no están en el CV',
    input: cvSinKubernetes,
    expect: (out) => !out.ranked[0].reasons.includes('Kubernetes'),
  },
];

Fíjate en que no comparo la salida entera carácter a carácter. Con un LLM eso está condenado a fallar por diferencias irrelevantes de redacción. Lo que compruebo son propiedades: que el candidato correcto quede arriba, que no aparezca una habilidad alucinada, que el formato sea parseable. Cada aserción codifica algo que de verdad me importa del comportamiento, no la forma exacta del texto.

Puntuar lo subjetivo: cuándo uso un LLM como juez

Muchas salidas no se pueden verificar con un if. "¿Es esta explicación del match clara y fundamentada?" no es una propiedad booleana obvia. Para eso uso un segundo LLM como evaluador: le paso el input, la salida y una rúbrica concreta, y le pido una nota con justificación. Funciona sorprendentemente bien para detectar degradaciones grandes, y es mucho más barato que revisar cientos de salidas a mano.

Pero elegí usar LLM-como-juez con los ojos abiertos, no como bala de plata. Tiene sesgos conocidos: tiende a premiar respuestas largas, favorece el estilo sobre el fondo, y si le pides que puntúe su propio modelo se vuelve indulgente. Lo trato como un filtro ruidoso, no como la verdad: sirve para cazar caídas grandes de calidad entre versiones, no para afinar si algo pasó de 8,2 a 8,4. Para las propiedades que sí puedo verificar con código, uso código, que es determinista y gratis.

💡 Cada bug de LLM que llega a producción es un caso de eval que te faltaba. Antes de arreglar el prompt, añade el caso que falla a tu dataset. Así el arreglo queda blindado y esa regresión concreta no puede volver nunca.

Ejecutar los evals donde duele: antes de desplegar

Un dataset de evals que corres cuando te acuerdas no sirve de nada. El valor aparece cuando se ejecuta automáticamente en cada cambio que toque el prompt, el modelo o la lógica de orquestación. Lo enganché al pipeline como un paso más: si la tasa de aciertos sobre el dataset cae por debajo de un umbral, el despliegue se para igual que si fallara un test.

async function runEvals(cases: EvalCase[]) {
  const results = await Promise.all(
    cases.map(async (c) => ({
      name: c.name,
      passed: c.expect(await runFeature(c.input)),
    })),
  );

  const passed = results.filter((r) => r.passed).length;
  const rate = passed / results.length;

  console.table(results);
  if (rate < 0.9) {
    throw new Error(`Evals por debajo del umbral: ${rate}`);
  }
}

El umbral no es 100% a propósito. Con LLMs hay casos genuinamente ambiguos donde un fallo ocasional es tolerable, y exigir verde perfecto te lleva a sobreajustar el prompt a tu dataset. Prefiero un umbral alto pero realista y vigilar la tendencia: si la tasa lleva tres versiones bajando, hay un problema aunque cada versión pasara el corte.

Dónde pongo el esfuerzo hoy

Si tuviera que dar un solo consejo a alguien que mete su primer LLM en un producto, sería este: monta los evals antes de pulir el prompt, no después. Es tentador dedicar el tiempo a la parte creativa de redactar instrucciones ingeniosas, pero sin una forma de medir estás optimizando a ciegas. Elegí invertir en el arnés de evaluación antes que en el prompt perfecto porque el arnés se paga solo en la primera regresión que caza, y su valor crece con cada caso que le añades. El prompt lo reescribiré diez veces; los evals me dicen cuál de esas diez versiones es mejor.

Yohangel Ramos

Escrito por Yohangel Ramos

Senior Fullstack Developer y Tech Lead. Construyo con React, Next.js, Nest.js y AWS — y escribo sobre lo que aprendo en el camino.

Hablemos →

Sigue leyendo

IA

Embeddings más baratos: recortar dimensiones y cuantizar sin perder recall

IA

Qué va a pasar con el desarrollo de software: predicciones con fechas (julio 2026)