Módulo 3: El eval como fitness function

5. Atrapar regresiones al cambiar el prompt o el modelo

Descripción

Al terminar esta lección vas a ver la compuerta de la lección 4 haciendo el trabajo para el que de verdad existe: proteger la calidad en el momento del cambio. Un componente de IA no se degrada solo, sentado en producción; se degrada cuando alguien lo toca —ajusta el prompt para arreglar un caso, o lo baja a un modelo más barato para ahorrar (el cascade del módulo 2)—. Ese cambio puede mejorar la calidad, dejarla igual, o empeorarla —una regresión—, y el problema es que a simple vista los tres se ven idénticos: el componente sigue respondiendo, en un tono parecido, y nada avisa que empeoró. El eval es lo que hace visible la diferencia. Vas a correrlo contra un baseline —el score del componente en producción hoy, 0.80— y contra dos cambios: un prompt nuevo que sube el score a 1.00 (mejora → aceptar) y un modelo más barato que lo baja a 0.60 (regresión → rechazar). El mismo componente, el mismo eval-set, y el score te dice de un vistazo cuál cambio ayudó y cuál rompió.

Esto importa porque la regresión silenciosa es una de las formas más caras de fallar con IA. Un cambio de prompt se siente inofensivo —"es solo texto, ¿qué puede salir mal?"— y un cambio de modelo se justifica solo —"ahorramos la mitad del costo"—. Sin una compuerta, los dos llegan a producción sin que nadie mida si la calidad sobrevivió, y la caída se descubre semanas después, por quejas de clientes, cuando ya nadie recuerda qué cambio la causó. El eval convierte ese riesgo invisible en un número visible antes del deploy: corres el eval sobre el cambio, comparas contra el baseline, y si el score bajó, lo sabes hoy, no en un mes. Y hay una conexión directa con el módulo 2 que se cierra aquí: el cascade abarataba mandando queries al modelo barato, y la pregunta que quedó abierta —"¿y si el barato responde peor?"— es exactamente una pregunta de regresión que el eval responde. La compuerta de costo (módulo 2) y la de calidad (este módulo) trabajan juntas: una te deja abaratar, la otra verifica que abaratar no rompió la calidad.

Conexión con el módulo: esta lección pone la compuerta en movimiento. La lección 4 la montó estática —una versión, un veredicto—; aquí la corres sobre versiones sucesivas para detectar el cambio entre ellas. La idea nueva es la comparación contra un baseline: no basta preguntar "¿este score es bueno?", hay que preguntar "¿este score es peor que el de antes?". Esa comparación es el núcleo de la detección de regresiones, y es lo que la lección 6 va a automatizar en CI —para que ningún cambio llegue a producción sin que el eval lo compare contra el baseline primero—. En una frase: aquí la compuerta deja de ser una foto y se vuelve un guardián del cambio.

La analogía: el análisis de sangre contra tu histórico

Piensa en un chequeo médico anual. El doctor te pide un análisis de sangre y mira, digamos, tu colesterol. Pero fíjate en lo que no hace: no mira tu número aislado y dice "180, es un número, está bien". Lo que hace es sacar tu histórico —tu colesterol del año pasado era 160— y comparar: subió 20 puntos. Ese cambio respecto a tu línea base es la señal, más que el número absoluto. Un valor que en sí mismo parece aceptable puede ser una alarma si empeoró frente a tu histórico, porque revela una tendencia. Y al revés: para saber si un tratamiento nuevo funcionó, el doctor no adivina —te vuelve a medir y compara contra el valor previo—. La medición contra el baseline es cómo distingues "mejoró", "igual" y "empeoró", que a ojo son indistinguibles.

Un componente de IA es igual. El score del eval es tu "análisis de sangre" de la calidad, y el baseline es tu histórico: el score que tenía en producción antes del cambio. Cuando alguien ajusta el prompt o cambia el modelo, no te preguntas solo "¿el score nuevo es bueno?" —te preguntas "¿el score nuevo empeoró respecto al baseline?"—. Si bajó, el cambio es una regresión, aunque el score siga por arriba de algún mínimo. Si subió, el cambio es una mejora, y lo aceptas con evidencia, no con fe. Sin el baseline, estás mirando un número suelto sin saber si tu componente va mejorando o empeorando con cada cambio —como un paciente que solo conoce su colesterol de hoy y no tiene idea de si está subiendo—. El eval corrido contra un baseline es el chequeo que convierte cada cambio en un "mejor, igual o peor" medido.

Ejemplo trabajado: mejora, sin cambio y regresión

Vamos a tomar el agente de soporte de Mercado con un baseline en producción de 0.80 y a pasar dos cambios por el eval, comparando cada uno contra ese baseline. El primer cambio es un prompt nuevo que, además de lo que ya respondía, ahora acierta las preguntas del cupón y del vendedor —sube a 1.00, una mejora—. El segundo es bajar a un modelo más barato (el cascade del módulo 2) que responde bien menos casos —cae a 0.60, una regresión—. La regla de veredicto compara el score del cambio contra el baseline: si cae bajo el umbral es regresión, si sube es mejora, si queda igual es sin cambio.

# Leccion 05 — atrapar regresiones al cambiar el prompt o el modelo
# Todo SIMULADO. Cero red, cero API, cero claves. Salida determinista.

# (Reusa EVAL_SET, GOLD, POOR_ANSWER, make_agent, run_eval de las lecciones 3-4.)
EVAL_SET = [
    {"id": "q1",  "question": "donde esta mi pedido",        "must_contain": "tracking"},
    {"id": "q2",  "question": "como devuelvo un producto",   "must_contain": "devolucion"},
    {"id": "q3",  "question": "cuanto tarda el envio",       "must_contain": "3 a 5 dias"},
    {"id": "q4",  "question": "puedo pagar en cuotas",       "must_contain": "cuotas"},
    {"id": "q5",  "question": "el producto llego roto",      "must_contain": "reembolso"},
    {"id": "q6",  "question": "como cambio mi direccion",    "must_contain": "perfil"},
    {"id": "q7",  "question": "no recibi mi factura",        "must_contain": "correo"},
    {"id": "q8",  "question": "quiero cancelar mi pedido",   "must_contain": "cancelar"},
    {"id": "q9",  "question": "el cupon no funciona",        "must_contain": "vigencia"},
    {"id": "q10", "question": "como contacto a un vendedor",  "must_contain": "mensajes"},
]
GOLD = {
    "q1": "Puedes ver el tracking de tu pedido en tu perfil.",
    "q2": "Para una devolucion, entra a tu pedido y pulsa Devolver.",
    "q3": "El envio estandar tarda de 3 a 5 dias habiles.",
    "q4": "Si, puedes pagar en cuotas sin interes con tarjeta.",
    "q5": "Lamentamos eso; puedes pedir un reembolso desde el pedido.",
    "q6": "Cambia tu direccion en la seccion Perfil, Direcciones.",
    "q7": "Te reenviamos la factura al correo de tu cuenta.",
    "q8": "Puedes cancelar el pedido si aun no fue enviado.",
    "q9": "Revisa la vigencia del cupon; quiza ya expiro.",
    "q10": "Escribe al vendedor desde la seccion Mensajes.",
}
POOR_ANSWER = "Lo siento, no tengo informacion sobre eso."

def make_agent(competent_ids):
    def agent(question, case_id):
        return GOLD[case_id] if case_id in competent_ids else POOR_ANSWER
    return agent

def run_eval(agent):
    passed = sum(
        case["must_contain"] in agent(case["question"], case["id"]).lower()
        for case in EVAL_SET
    )
    return passed / len(EVAL_SET)

ALL_IDS = {c["id"] for c in EVAL_SET}
BASELINE = 0.80          # el score del agente EN PRODUCCION hoy (el "histórico")
THRESHOLD = 0.80         # el umbral de la compuerta

# Los candidatos a desplegar: cada uno es una version del agente tras un cambio.
BASE_IDS    = {"q1","q2","q3","q4","q5","q6","q7","q8"}   # produccion hoy -> 0.80
PROMPT2_IDS = ALL_IDS                                     # prompt nuevo   -> 1.00
CHEAP_IDS   = {"q1","q2","q3","q4","q6","q8"}             # modelo barato  -> 0.60

candidates = [
    ("baseline (produccion hoy)", BASE_IDS),
    ("cambio 1: prompt nuevo",    PROMPT2_IDS),
    ("cambio 2: modelo barato",   CHEAP_IDS),
]

print(f"baseline en produccion = {BASELINE:.2f}   umbral = {THRESHOLD:.2f}\n")
print(f"{'candidato':<28}{'score':>7}{'vs baseline':>13}   veredicto")
for name, ids in candidates:
    score = run_eval(make_agent(ids))
    delta = score - BASELINE
    if score < THRESHOLD:
        verdict = "REGRESION -> rechazar"
    elif delta > 0:
        verdict = "mejora -> aceptar"
    else:
        verdict = "sin cambio -> ok"
    print(f"{name:<28}{score:>7.2f}{delta:>+13.2f}   {verdict}")

Qué esperar. Al correrlo:

baseline en produccion = 0.80   umbral = 0.80

candidato                     score  vs baseline   veredicto
baseline (produccion hoy)      0.80        +0.00   sin cambio -> ok
cambio 1: prompt nuevo         1.00        +0.20   mejora -> aceptar
cambio 2: modelo barato        0.60        -0.20   REGRESION -> rechazar

Aquí está el chequeo médico de la calidad, y aquí está por qué el baseline es lo que da sentido a cada número. Léelo por renglón.

El baseline (0.80). Es tu histórico: el score del agente tal como está hoy en producción. Sirve de referencia —el vs baseline es +0.00 porque se compara consigo mismo—. Todo lo que sigue se mide contra esta línea.

El cambio 1: el prompt nuevo (1.00, +0.20). Alguien reescribió el prompt del agente y ahora acierta también las preguntas del cupón y del vendedor. El score subió a 1.00, +0.20 sobre el baseline: es una mejora, y el veredicto es aceptar. Y fíjate en el valor de esto: la mejora no es una impresión ("el prompt nuevo se ve mejor") sino un hecho medido —dos casos más que antes fallaban ahora pasan—. Aceptas el cambio con evidencia, y de paso el nuevo baseline sube a 1.00 para el próximo chequeo.

El cambio 2: el modelo barato (0.60, −0.20). Alguien, buscando ahorrar, bajó el agente a un modelo más barato —justo el cascade del módulo 2—. El componente sigue respondiendo, en un tono parecido, y a ojo nadie notaría nada raro. Pero el eval lo delata: el score cayó a 0.60, −0.20 bajo el baseline y por debajo del umbral. Es una regresión, y el veredicto es rechazar. Sin el eval, este cambio habría llegado a producción —"ahorramos la mitad, y las respuestas se ven bien"— y la caída de calidad se habría descubierto semanas después por quejas. Con el eval, se detecta antes del deploy, con un número que nadie puede discutir.

Junta los tres y tienes la lección: el mismo componente, tres estados —igual, mejor, peor— que a ojo son indistinguibles y que el eval separa con un número. La comparación contra el baseline es lo que convierte "aquí está un score" en "este cambio mejoró o empeoró la calidad". Y nota el cierre con el módulo 2: el cambio 2 es exactamente la optimización de costo del cascade, y el eval es quien verifica que esa optimización no salió cara en calidad. Las dos compuertas, la de costo y la de calidad, se necesitan mutuamente: abaratar sin medir la calidad es apostar, y el eval convierte la apuesta en una decisión informada.

Profundización: la regresión, el baseline y la interacción con el costo

Qué es exactamente una regresión. Una regresión es un cambio que empeora una propiedad que antes estaba bien. En software clásico es un test que estaba verde y se pone rojo por un cambio no relacionado —arreglaste A y rompiste B—. En un componente de IA es un cambio (de prompt, de modelo, de datos de contexto) que baja el score del eval. La palabra clave es baja: no es que el componente sea malo en absoluto, es que es peor que antes. Por eso la regresión solo se ve con un baseline —necesitas el "antes" para saber que hubo un "peor"—. Un componente con score 0.60 podría estar perfectamente bien si su baseline siempre fue 0.60; el problema es cuando su baseline era 0.80 y un cambio lo bajó a 0.60. El eval sin baseline mide calidad absoluta; el eval con baseline mide, además, la dirección del cambio, que es lo que importa cuando alguien toca el componente.

Por qué un cambio de prompt no es "solo texto". El instinto de tratar un cambio de prompt como inofensivo viene de que parece edición de texto, no de código. Pero en un componente de IA el prompt es parte del programa: cambiarlo cambia el comportamiento del componente en todos los casos, no solo en el que querías arreglar. Ajustas el prompt para que responda mejor sobre cupones y, sin querer, cambias cómo responde sobre envíos —porque el modelo procesa todo el prompt junto—. Esa es la razón de que un cambio de prompt necesite el eval completo, no una revisión del caso que tocaste: el efecto se reparte por todo el eval-set, y solo corriéndolo entero ves si ganaste en un lado y perdiste en otro. "Es solo texto" es precisamente el modelo mental que produce regresiones silenciosas.

La interacción con el cost budget del módulo 2. Esta es la conexión más importante de la lección. El módulo 2 te enseñó a bajar el costo con el cascade: mandar las queries fáciles a un modelo barato. Pero dejó abierta, a propósito, la pregunta de calidad: "¿y si el barato responde peor?". Ahora tienes la herramienta para responderla. Cuando propongas bajar de modelo para ahorrar, corres el eval: si el score se mantiene, abaratar salió gratis en calidad —adelante—; si el score cae (como el cambio 2, a 0.60), abaratar tuvo un costo oculto en calidad, y tienes que decidir si vale la pena o si el cascade debe mandar esas queries al modelo fuerte. La regla de diseño: ninguna optimización de costo se acepta sin correr el eval, porque el ahorro en dólares puede esconder una pérdida en calidad que solo el eval hace visible. Las dos compuertas del módulo 2 y la de este módulo forman un sistema: presupuesto y calidad se verifican juntos, no por separado.

El baseline se mueve (y eso es bueno). Un matiz: el baseline no es fijo para siempre. Cada vez que aceptas una mejora, el nuevo score se vuelve el baseline para el próximo cambio. Aceptaste el prompt nuevo (1.00); ahora el próximo cambio se compara contra 1.00, no contra 0.80. Esto es deseable: el baseline sube con las mejoras, así que la barra de "no empeorar" sube contigo. Un cambio que dé 0.90 sería una mejora frente al viejo baseline de 0.80 pero una regresión frente al nuevo de 1.00 —y así debe ser: una vez que llegaste a 1.00, volver a 0.90 es perder terreno—. El baseline vivo es lo que convierte el eval en un trinquete de calidad: cada vez que subes, no se te permite bajar de ahí sin darte cuenta.

Errores comunes

Cambiar el prompt o el modelo sin correr el eval (de proceso). Qué pasa: alguien ajusta el prompt o baja de modelo y despliega directo, confiando en que "se ve bien". El cambio arregla lo que buscaba pero rompe casos que nadie volvió a mirar —una regresión silenciosa— y se descubre semanas después por quejas de clientes, cuando ya es caro rastrear qué la causó. Por qué pasa: un cambio de prompt se siente como editar texto, y un cambio de modelo se justifica solo por el ahorro; sin una compuerta obligatoria, nada frena el deploy. Cómo detectarlo: si tus cambios de prompt o modelo llegan a producción sin un score que los compare contra el baseline, estás volando a ciegas. Cómo corregirlo: corre el eval ante todo cambio del componente y compara contra el baseline; la lección 6 lo hace obligatorio poniéndolo en CI.

Mirar el score sin baseline (de referencia). Qué pasa: el equipo corre el eval sobre un cambio, ve 0.75 y dice "está por arriba de la mitad, aceptable" —sin notar que el baseline era 0.90 y el cambio fue una caída de 0.15—. El score absoluto se veía tolerable, pero la dirección era hacia abajo. Por qué pasa: sin traer el histórico, un número suelto siempre parece "más o menos bien". Cómo detectarlo: si evalúas un cambio sin comparar contra el score anterior, no estás detectando regresiones, solo midiendo un valor absoluto. Cómo corregirlo: guarda el baseline y reporta siempre el vs baseline (el delta); una regresión es una caída respecto al histórico, no un valor bajo en abstracto.

Aceptar la optimización de costo sin verificar la calidad (de alcance, cruce con módulo 2). Qué pasa: el equipo baja a un modelo más barato porque "ahorra la mitad" y despliega mirando solo el cost budget, sin correr el eval. El costo bajó, sí, pero la calidad también —una regresión que la compuerta de costo no puede ver, porque solo mide dólares—. Por qué pasa: el ahorro es inmediato y visible; la pérdida de calidad es diferida e invisible sin eval. Cómo detectarlo: si aprobaste un cambio de modelo mirando solo el costo, te falta la mitad del análisis. Cómo corregirlo: toda optimización de costo pasa también por el eval —el cost budget y el eval gate juntos—; abaratar solo se acepta si el score se mantiene.

Ejercicios

Ejercicio 1 — Clasifica cada cambio. El baseline en producción es 0.85 y el umbral es 0.80. Para cada cambio, di si es mejora, sin cambio o regresión, y si se acepta o se rechaza. (a) Cambio X: score 0.90. (b) Cambio Y: score 0.82. (c) Cambio Z: score 0.78.

Ver solución

Comparando cada score contra el baseline (0.85) y el umbral (0.80):

  • (a) X: 0.90. +0.05 sobre el baseline y arriba del umbral → mejora, aceptar. Sube la calidad; el nuevo baseline pasa a 0.90.
  • (b) Y: 0.82. −0.03 bajo el baseline pero arriba del umbral (0.82 ≥ 0.80). Aquí hay matiz: no cruza el umbral, así que no es una regresión que bloquee el deploy, pero bajó respecto al baseline. Es una degradación leve: técnicamente desplegable, pero deberías preguntarte por qué el cambio costó 0.03 de calidad y si valía la pena. Un equipo estricto lo marca para revisión; uno laxo lo deja pasar por estar sobre el umbral. Lo importante: el delta negativo es una señal aunque no cruce el umbral.
  • (c) Z: 0.78. −0.07 bajo el baseline y por debajo del umbral (0.78 < 0.80) → regresión, rechazar. Empeoró y además cae bajo el mínimo aceptable. Se bloquea.

La moraleja: hay dos referencias, el baseline (¿empeoró?) y el umbral (¿es aceptable?). Z falla las dos y se rechaza sin duda; Y está en la zona gris —bajó pero sigue sobre el mínimo— y merece una mirada; X mejora y sube el baseline.

Ejercicio 2 — La regresión disfrazada de ahorro. Un compañero propone: "Bajé el agente de soporte al modelo barato. El costo mensual cayó de $3,000 a $1,400 —ahorramos 53%—. Probé cinco preguntas a mano y las respuestas se ven bien. Listo para desplegar." Identifica qué le falta a este análisis y por qué el ahorro no basta para aprobar el cambio.

Ver solución

Lo que le falta es correr el eval completo y compararlo contra el baseline de calidad. El análisis mide bien el costo (cayó 53%, la compuerta de costo del módulo 2 pasa), pero verifica la calidad de la peor forma posible: "probé cinco preguntas a mano y se ven bien". Eso es exactamente el error de "probar a ojo": cinco casos no son el eval-set, "se ven bien" no es un score, y no hay comparación contra el baseline. El modelo barato podría estar respondiendo bien esas cinco preguntas fáciles que eligió y fallando las quince difíciles que no probó —justo la regresión del cambio 2 (0.60 contra un baseline de 0.80)—.

Por qué el ahorro no basta: costo y calidad son compuertas ortogonales (lección 4, ejercicio 3). Un cambio puede mejorar una y empeorar la otra, y este cambio hace exactamente eso: baja el costo y —probablemente— baja la calidad. Aprobar mirando solo el costo es ver la mitad del cuadro. Lo correcto: correr el eval-set completo sobre el modelo barato, comparar el score contra el baseline, y solo entonces decidir. Si el score se mantiene, el ahorro es real y gratis; si el score cae, el "ahorro" tiene un costo oculto en calidad que el negocio tiene que decidir si acepta —o mandar esas queries al modelo fuerte vía cascade—.

Ejercicio 3 — El baseline que se mueve. El agente arranca con un baseline de 0.80. Se acepta un cambio que lo sube a 0.95 (nuevo baseline). Luego llega un cambio que da 0.88. Bajo un umbral fijo de 0.80, ¿pasa la compuerta? ¿Es una mejora o una regresión? Explica por qué el veredicto depende de contra qué compares.

Ver solución

El cambio que da 0.88 produce dos veredictos según la referencia:

  • Contra el umbral fijo (0.80): 0.88 ≥ 0.80 → pasa la compuerta. Es lo bastante bueno en términos absolutos: desplegarlo no serviría una calidad inaceptable.
  • Contra el baseline vivo (0.95): 0.88 < 0.95, una caída de −0.07 → es una regresión. Empeoró respecto a donde ya estabas.

Cómo se reconcilian: la compuerta de umbral dice "esto es aceptable para producción" (sí, 0.88 lo es), pero el análisis de regresión dice "esto es peor que lo que ya tenías" (sí, bajaste de 0.95 a 0.88). Los dos son ciertos a la vez. Un equipo maduro no despliega este cambio solo porque pasa el umbral: pregunta por qué perdió 0.07 respecto al baseline y si ese precio compra algo (¿ahorro de costo? ¿otra mejora?). Rendir 0.95 a cambio de 0.88 sin una buena razón es perder terreno ganado.

La moraleja: el umbral es un piso absoluto (¿es aceptable?), el baseline es una referencia relativa (¿mejoró o empeoró?), y necesitas los dos. Una vez que subes a 0.95, el baseline vivo protege ese logro: no te deja bajar a 0.88 sin que la comparación lo marque como regresión, aunque el umbral fijo lo dejara pasar.

Resumen y siguiente paso

En esta lección pusiste la compuerta a trabajar donde de verdad importa: en el momento del cambio. Viste que un componente de IA no se degrada solo —se degrada cuando alguien toca el prompt o el modelo— y que las tres direcciones del cambio (mejor, igual, peor) son indistinguibles a ojo pero se separan con un número. Con la analogía del análisis de sangre contra tu histórico entendiste el papel del baseline: no basta preguntar "¿este score es bueno?", hay que preguntar "¿empeoró respecto al de antes?" —esa es la definición de regresión—. Ejecutaste el eval sobre un baseline de 0.80 y dos cambios: un prompt nuevo que subió a 1.00 (mejora → aceptar) y un modelo más barato que cayó a 0.60 (regresión → rechazar), detectada antes del deploy. Y cerraste el lazo con el módulo 2: el cambio de modelo barato es la optimización de costo del cascade, y el eval es quien verifica que abaratar no salió caro en calidad —las compuertas de costo y calidad trabajan juntas—.

Antes de avanzar deberías poder: definir una regresión como un cambio que baja el score respecto a un baseline; explicar por qué un cambio de prompt no es "solo texto" y necesita el eval completo; articular por qué toda optimización de costo debe pasar por el eval; y distinguir el papel del umbral (piso absoluto) del papel del baseline (referencia relativa).

Lo que sigue es hacer que esta protección sea obligatoria y automática. Hasta ahora corres el eval a mano y decides; pero un proceso que depende de que alguien se acuerde de correr el eval fallará el día que alguien tenga prisa. En la lección 6 vas a meter el eval en CI: un paso del pipeline que corre el eval en cada cambio, produce un exit code, y bloquea el deploy si el score cae bajo el umbral —igual que un test rojo bloquea el merge—. Vas a ver dos PRs pasar por el pipeline: uno mejora el prompt (exit 0 → MERGE) y otro baja de modelo y regresa (exit 1 → BLOCK). Es el paso de "corro el eval cuando me acuerdo" a "el pipeline no me deja desplegar una regresión aunque quiera".

Recursos