Módulo 3: El eval como fitness function

4. El eval como compuerta de calidad (fitness function)

Descripción

Al terminar esta lección vas a tener el corazón del módulo: el score que construiste en la lección 3 se convierte en una compuerta que gobierna el deploy. Un score solo —0.90— describe el componente pero no decide nada; es una nota sin regla de aprobación. Lo que lo convierte en una decisión es un umbral explícito: "esta feature necesita un score de al menos 0.80 para salir a producción". Con el umbral, el score se vuelve una compuerta binaria y verificable —pasa (deploy permitido) o falla (deploy bloqueado)—, exactamente como la compuerta fits(value, budget) del módulo 2 convirtió "rápido" y "barato" en veredictos. Vas a ejecutar el eval_gate sobre dos versiones del agente de soporte de Mercado: la versión A (score 0.90 ≥ 0.80) pasa —deploy permitido, en verde— y la versión B, que regresó (score 0.60 < 0.80), falla —deploy bloqueado, en rojo—. Y vas a entender por qué esa compuerta merece un nombre que ya conoces de otra guía: es una fitness function para la calidad de un componente probabilístico.

Esto importa porque el umbral es lo que le da autoridad al eval. Sin umbral, el eval es un termómetro: te dice la temperatura (0.90, 0.60) pero no toma ninguna decisión, y un equipo puede mirar un score de 0.60 y desplegar igual "porque se ve aceptable". Con umbral, el eval es una compuerta: un score bajo el umbral no pasa, punto, igual que un lote de focos con demasiados defectos no sale de la fábrica sin importar cuánta prisa haya. Ese paso —de termómetro a compuerta— es lo que hace que la calidad de la IA deje de ser negociable caso por caso y se vuelva una regla que el sistema hace cumplir. Y es la pieza que faltaba para completar el trío del módulo 2: latencia (compuerta), costo (compuerta) y ahora calidad (compuerta). Las tres tienen la misma forma —una métrica contra un umbral— y las tres deben pasar para que una feature de IA llegue a producción.

Conexión con el módulo: esta lección es el pivote. Todo lo anterior construyó hacia aquí: la lección 2 mostró por qué necesitas un score, la 3 lo fabricó, y esta lo convierte en compuerta. Todo lo que sigue usa esta compuerta: la lección 5 la corre sobre versiones sucesivas para atrapar regresiones (el score de un cambio contra un baseline), y la lección 6 la mete en CI para que bloquee el deploy automáticamente. La lección 7 abre el criterio de éxito que alimenta el score. Aquí también se hace explícita la conexión con architecture-decisions: la fitness function como concepto general vive allá; este módulo la especializa a la calidad probabilística. En una frase: aquí el score se vuelve gate, y el gate es una fitness function.

La analogía: el control de calidad que rechaza el lote

Vuelve a la fábrica de focos. Al final de la línea de producción hay una estación de control de calidad. No revisa foco por foco —sería lentísimo—; toma una muestra de cada lote, cuenta cuántos están defectuosos, y calcula la tasa de defectos. Y tiene una regla escrita, decidida de antemano: si la tasa de defectos supera el 2%, el lote entero se rechaza. No sale de la fábrica. No importa que el 98% de los focos funcione perfectamente; no importa que el cliente los esté esperando; no importa que rehacer el lote cueste dinero. El umbral es el umbral, y un lote que lo cruza no pasa. Esa regla no es una opinión del inspector de turno: está escrita, es la misma cada día, y cualquier inspector con la misma muestra da el mismo veredicto.

Traduce la estación pieza por pieza. La tasa de defectos (o su complemento, la tasa de buenos) es el score —la medición agregada de calidad—. El umbral del 2% es el umbral del gate —la línea que separa aceptable de inaceptable—. La decisión de rechazar el lote es bloquear el deploy. Y la propiedad más importante: la estación decide sola, con una regla fija, sin que nadie discuta caso por caso si "este lote se ve suficientemente bien". Esa es la diferencia entre un termómetro y una compuerta. Un termómetro mide la tasa de defectos y la reporta; una compuerta la mide y decide rechazar o dejar pasar. El eval sin umbral es el termómetro; el eval con umbral es la estación de control de calidad. Y como la estación, puede estar en la línea permanentemente, revisando cada lote —cada cambio del componente— antes de que llegue al cliente. Esta lección monta esa estación; la lección 6 la deja instalada en la línea (en CI).

Ejemplo trabajado: el eval_gate en verde y en rojo

Vamos a montar la compuerta y a pasar dos versiones del agente de soporte por ella. La compuerta es simple: corre el eval-set, obtiene el score, lo compara contra el umbral, y devuelve el veredicto —PASS (deploy permitido) o FAIL (deploy bloqueado)—. La versión A es la candidata a producción: responde bien 9 de 10 (score 0.90). La versión B es una que regresó —quizá alguien cambió el prompt o bajó el modelo para ahorrar (el cascade del módulo 2)— y responde bien solo 6 de 10 (score 0.60). El umbral de calidad de la feature es 0.80.

# Leccion 04 — el eval como compuerta de calidad (fitness function)
# Todo SIMULADO. Cero red, cero API, cero claves. Salida determinista.

# (Reusa el EVAL_SET, GOLD, POOR_ANSWER, make_agent y run_eval de la leccion 3.)
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)

# --- LA COMPUERTA: score contra umbral => decision de deploy ---
def eval_gate(agent, threshold):
    score = run_eval(agent)
    passed = score >= threshold          # la regla fija, como el 2% de la fabrica
    return score, passed

THRESHOLD = 0.80                          # el umbral de calidad de la feature
ALL_IDS = {c["id"] for c in EVAL_SET}
version_a = make_agent(ALL_IDS - {"q9"})                    # candidata: 9/10
version_b = make_agent({"q1", "q2", "q3", "q4", "q6", "q8"})  # regresion: 6/10

print("=== EVAL GATE — agente de soporte de Mercado ===")
print(f"umbral de calidad (threshold) = {THRESHOLD:.2f}\n")
print(f"{'version':<26}{'score':>7}   {'gate':<7} deploy")
for name, agent in (("A (candidata a produccion)", version_a),
                    ("B (regresion introducida)", version_b)):
    score, passed = eval_gate(agent, THRESHOLD)
    verdict = "[PASS]" if passed else "[FAIL]"
    action = "PERMITIDO (verde)" if passed else "BLOQUEADO (rojo)"
    print(f"{name:<26}{score:>7.2f}   {verdict:<7} {action}")

Qué esperar. Al correrlo:

=== EVAL GATE — agente de soporte de Mercado ===
umbral de calidad (threshold) = 0.80

version                     score   gate    deploy
A (candidata a produccion)   0.90   [PASS]  PERMITIDO (verde)
B (regresion introducida)    0.60   [FAIL]  BLOQUEADO (rojo)

Aquí está la estación de control de calidad funcionando. Léela por versión.

La versión A pasa, en verde. Su score es 0.90 —responde bien 9 de las 10 preguntas—, y 0.90 es mayor o igual que el umbral de 0.80, así que la compuerta devuelve PASS: deploy permitido. La feature es lo bastante buena para producción según la regla que el equipo fijó de antemano. Nadie tuvo que leer las diez respuestas y opinar; la compuerta comparó un número contra un umbral y dio verde.

La versión B falla, en rojo. Su score es 0.60 —responde bien solo 6 de 10—, y 0.60 es menor que el umbral de 0.80, así que la compuerta devuelve FAIL: deploy bloqueado. Y fíjate en la fuerza de esto: la versión B no está "rota" —seis de cada diez respuestas son buenas, un cliente que pregunte por su pedido queda bien atendido—. Pero no es lo bastante buena según el umbral, y la compuerta la rechaza sin negociar, igual que la fábrica rechaza el lote con 3% de defectos aunque el 97% de los focos sirva. La calidad "aceptable a ojo" no basta; hay una línea escrita, y la versión B está debajo.

Junta las dos y tienes el mecanismo central del módulo. La misma feature, dos versiones, y una compuerta que deja pasar una y bloquea la otra —automáticamente, con una regla fija, sin juicio humano caso por caso—. Esto es lo que convierte la calidad de un componente probabilístico de una opinión ("se ve bien") en un control ("pasa el gate o no entra"). Y es exactamente la forma que tenían las compuertas de presupuesto del módulo 2: una métrica (score), un umbral (0.80), un veredicto (PASS/FAIL). La calidad ya no es la excepción borrosa entre las restricciones de una feature de IA; es una compuerta más, con la misma disciplina que la latencia y el costo.

Profundización: por qué esto es una fitness function

El eval es una fitness function especializada. En architecture-decisions-and-tradeoffs-guide aprendes el concepto de fitness function: una prueba automatizada que verifica que una propiedad arquitectónica del sistema se mantiene con el tiempo, y que se pone roja si la propiedad se degrada, frenando el cambio. Los ejemplos clásicos son estructurales —"ninguna capa de dominio importa de la capa de infraestructura", "la latencia p95 del endpoint sigue bajo 200 ms", "ningún módulo supera las 500 líneas"—: propiedades que quieres que el sistema conserve mientras evoluciona. El eval_gate de esta lección es exactamente eso, aplicado a una propiedad nueva: la calidad de un componente probabilístico. Es una prueba automatizada (el eval-set), verifica que una propiedad se mantiene (el score sigue arriba del umbral), y se pone roja si se degrada (score < umbral → deploy bloqueado). La única diferencia con una fitness function clásica es que la propiedad no es determinista ni estructural, sino estadística —un score, no un booleano exacto—. Pero el rol arquitectónico es idéntico: gobernar que una propiedad del sistema no se degrade con los cambios. Por eso el módulo se llama "el eval como fitness function": no es una metáfora, es una identidad. El concepto general es de architecture-decisions; aquí ves su encarnación para la IA.

El umbral es una decisión de diseño y de negocio, no técnica. ¿De dónde sale el 0.80? No de la ingeniería. Igual que el cost budget del módulo 2 venía del margen que el negocio podía gastar, el umbral de calidad viene de cuánta imperfección tolera la feature. Para un agente de soporte que solo informa, un score de 0.80 puede ser aceptable —una de cada cinco respuestas imperfecta, con un humano de respaldo, es tolerable—. Para un componente que toca dinero o salud, el umbral tendría que ser muchísimo más alto, o el componente ni siquiera debería decidir solo (eso es el módulo 6, la cáscara determinista). Fijar el umbral es una conversación sobre riesgo y consecuencia, no un cálculo. La regla: el umbral se negocia con quien conoce el costo de una respuesta mala, y luego la compuerta lo hace cumplir. Un umbral que la ingeniería inventa sin pensar en las consecuencias de fallar es un cartel sin autoridad —igual que el presupuesto del módulo 2—.

Un umbral demasiado bajo es una compuerta inútil. Aquí hay un peligro sutil que conecta con la "fitness function inútil". Si pones el umbral en 0.50, casi cualquier versión lo pasa —incluida la versión B con 0.60—, y la compuerta deja de proteger: se pone verde para todo, así que nunca frena nada. Una compuerta que nunca falla no es una compuerta, es un adorno. Es el equivalente de la estación de control de calidad que acepta lotes con hasta 90% de defectos: técnicamente existe, pero no rechaza nada, así que no controla nada. El umbral tiene que estar donde de verdad separa lo aceptable de lo inaceptable —lo bastante alto para atrapar las regresiones que importan—. Lo verás en Errores comunes: un eval-set (o un umbral) que nunca falla da una falsa sensación de seguridad.

La compuerta no arregla, solo decide. Un matiz importante: el eval_gate te dice si la versión pasa, no cómo arreglarla si no pasa. Cuando la versión B falla con 0.60, la compuerta hizo su trabajo —bloqueó un deploy que habría degradado la calidad—, pero arreglar el componente (mejorar el prompt, subir de modelo, corregir los casos que fallan) es otra tarea, guiada por el detalle por caso de la lección 3. La compuerta es el guardia que no deja pasar el camión pesado; no es el mecánico que lo aligera. Separar los dos roles es clave: la compuerta protege producción, el detalle guía la corrección.

Errores comunes

Tratar el eval como termómetro y no como compuerta (de omisión). Qué pasa: el equipo corre el eval, mira el score (0.60), comenta "está un poco bajo pero se ve aceptable" y despliega igual. El eval midió, pero no decidió: no había umbral con autoridad, así que el score fue una sugerencia que se ignoró. Por qué pasa: sin un umbral fijado de antemano, cada release se negocia según la prisa del momento, y el score bajo siempre encuentra una excusa. Cómo detectarlo: si tus deploys ocurren con scores que "podrían ser mejores" pero nadie los bloquea, tienes termómetro, no compuerta. Cómo corregirlo: fija un umbral explícito antes y haz que el gate bloquee sin negociar cuando el score cae debajo —la estación de control de calidad no discute con el lote defectuoso—.

Un umbral demasiado flojo: la compuerta que nunca falla (de sobre-permisividad). Qué pasa: para que "nada se atore", el equipo pone el umbral en 0.50, y entonces casi toda versión pasa —incluida una regresión seria—. La compuerta existe en el papel pero se pone verde para todo, así que da una falsa sensación de seguridad: "tenemos eval gate" mientras el gate no protege de nada. Por qué pasa: un umbral bajo evita fricción y falsas alarmas, y es tentador cuando el gate "molesta". Cómo detectarlo: si tu eval gate nunca ha bloqueado un deploy, sospecha que el umbral es demasiado bajo, no que todos tus cambios son buenos. Cómo corregirlo: pon el umbral donde de verdad separe aceptable de inaceptable —lo bastante alto para atrapar la versión B (0.60) que un umbral de 0.80 sí bloquea—.

Confundir la compuerta con el arreglo (de alcance). Qué pasa: la versión B falla el gate y el equipo espera que la compuerta "resuelva" el problema, o se frustra porque "el eval no arregla nada". La compuerta bloqueó el deploy —hizo su trabajo—, pero mejorar el componente es otra tarea que la compuerta no hace. Por qué pasa: se espera que una herramienta que detecta también corrija, como un test que se auto-repara. Cómo detectarlo: si te quejas de que el eval "solo dice que está mal pero no lo arregla", confundiste el rol. Cómo corregirlo: usa la compuerta para decidir (bloquear el deploy malo) y el detalle por caso (lección 3) para guiar la corrección (qué casos arreglar); son dos roles distintos y complementarios.

Ejercicios

Ejercicio 1 — Aplica la compuerta. El umbral de calidad de una feature es 0.85. Tres versiones dan estos scores: V1 = 0.92, V2 = 0.85, V3 = 0.83. Para cada una, di el veredicto de la compuerta (PASS/FAIL, deploy permitido/bloqueado) y explica el caso de V2 y V3, que están al filo.

Ver solución

Con la regla score >= threshold y umbral 0.85:

  • V1 = 0.92: 0.92 ≥ 0.85 → PASS, deploy permitido. Con holgura.
  • V2 = 0.85: 0.85 ≥ 0.85 → PASS, deploy permitido. Justo en el umbral. La convención >= (mayor o igual) hace que el valor exacto del umbral pase —es una decisión de diseño: podrías usar > (estricto) y entonces V2 fallaría—. Lo importante es que la regla sea explícita y consistente.
  • V3 = 0.83: 0.83 < 0.85 → FAIL, deploy bloqueado. Por dos centésimas debajo del umbral, no pasa. Y así debe ser: la línea es la línea. Si dejaras pasar 0.83 "porque está cerca", el umbral dejaría de tener autoridad —mañana alguien pediría pasar 0.82 "porque está cerca de 0.83"—.

La moraleja: la compuerta es binaria y no negocia con la cercanía. Estar "casi" arriba del umbral es estar debajo. Eso es lo que la hace una compuerta y no una sugerencia.

Ejercicio 2 — El umbral inútil. Un equipo presume: "tenemos eval gate en producción, con umbral 0.40". Revisas el historial y el gate nunca ha bloqueado un deploy en seis meses. Explica qué está mal, por qué un umbral de 0.40 hace la compuerta inútil, y cómo se conecta esto con la idea de la "fitness function inútil".

Ver solución

Lo que está mal: el umbral es tan bajo que la compuerta no puede fallar. Con 0.40, un componente que responde bien menos de la mitad de los casos igual pasa; para bloquear un deploy, una versión tendría que ser catastróficamente mala (peor que responder bien 4 de cada 10). En la práctica, ninguna versión real cae tan bajo, así que el gate se pone verde para todo. Que "nunca haya bloqueado un deploy en seis meses" no es señal de que todos los cambios fueron buenos —es señal de que el gate no está midiendo nada útil—.

La conexión con la fitness function inútil: una fitness function debe poder ponerse roja; si está calibrada de modo que siempre pasa (un umbral flojo, una condición trivial), no verifica nada —da la ilusión de control sin control—. Es peor que no tener gate, porque genera confianza falsa: el equipo cree que la calidad está protegida cuando no lo está. La corrección: subir el umbral a donde de verdad separe aceptable de inaceptable (para este agente, algo como 0.80), de modo que una regresión seria como la versión B (0.60) se bloquee. Una compuerta que nunca falla no es una compuerta.

Ejercicio 3 — Las tres compuertas juntas. Recuerda del módulo 2 que una feature de IA pasa tres compuertas: latency budget, cost budget y (ahora) eval gate. Una versión de la búsqueda semántica de Mercado da: latencia 250 ms (budget 800 ms), costo $2,100/mes (budget $3,000/mes), score de calidad 0.72 (umbral 0.85). ¿Se despliega? Explica qué aporta cada compuerta y por qué las tres son necesarias.

Ver solución

No se despliega. Las tres compuertas deben pasar, y esta versión falla la de calidad:

  • Latency budget: 250 ms ≤ 800 ms → PASS. Es rápida.
  • Cost budget: $2,100/mes ≤ $3,000/mes → PASS. Cabe en el margen.
  • Eval gate: 0.72 < 0.85 → FAIL. No es lo bastante buena.

El resultado global es bloqueado: basta con que una compuerta falle. La feature es rápida y barata, pero devuelve resultados poco relevantes (una de cada cuatro búsquedas falla el criterio de calidad), y desplegar eso sería servir una búsqueda veloz, económica y mala.

Qué aporta cada compuerta y por qué las tres son necesarias: la latencia mide si responde a tiempo, el costo si cabe en el margen, y el eval si la respuesta es buena. Son propiedades ortogonales: una feature puede ser buena en dos y mala en la tercera, como esta. Si solo tuvieras las compuertas de latencia y costo (módulo 2), habrías desplegado una búsqueda rápida y barata sin darte cuenta de que da resultados malos —el error del ejercicio 3 de la lección 3 del módulo 2—. El eval gate es la tercera compuerta, la que este módulo aporta, y sin ella el trío queda incompleto. Una feature de IA lista para producción pasa las tres.

Resumen y siguiente paso

En esta lección montaste el corazón del módulo: el score se convirtió en una compuerta al compararlo contra un umbral. Con la analogía del control de calidad de fábrica viste la diferencia entre un termómetro (mide y reporta) y una compuerta (mide y decide): la estación rechaza el lote que cruza el 2% de defectos con una regla fija, sin discutir caso por caso, igual que el eval_gate bloquea la versión cuyo score cae bajo el umbral. Ejecutaste la compuerta sobre dos versiones del agente de soporte: la A (0.90 ≥ 0.80) pasó en verde —deploy permitido— y la B, que regresó (0.60 < 0.80), falló en rojo —deploy bloqueado—, sin que un humano juzgara a ojo. Y entendiste por qué esto merece el nombre de fitness function: es una prueba automatizada que gobierna que una propiedad del sistema —la calidad de un componente probabilístico— no se degrade con los cambios, exactamente el rol de una fitness function de architecture-decisions, especializada a la IA.

Antes de avanzar deberías poder: explicar por qué un score necesita un umbral para volverse compuerta; justificar de dónde sale el umbral (riesgo y consecuencia, no cálculo técnico); reconocer por qué un umbral demasiado bajo hace la compuerta inútil; y articular por qué el eval gate es una fitness function y cómo completa el trío de compuertas del módulo 2.

Lo que sigue es poner la compuerta a trabajar donde de verdad importa: en el momento del cambio. Un componente no se rompe solo; se rompe cuando alguien cambia el prompt o el modelo. En la lección 5 vas a usar el eval para atrapar una regresión: partiendo de un baseline (0.80), verás que un prompt nuevo sube el score (mejora → aceptar) y que bajar a un modelo más barato —el cascade del módulo 2— lo hace caer (regresión → rechazar). El eval detecta la caída que "a ojo" no verías. Es el paso de "tengo una compuerta" a "la compuerta me protege de que un cambio degrade la calidad sin que nadie lo note".

Recursos