Comparativa reproducible de métricas entre modelos

Resumen

Las gráficas de un lanzamiento no te dicen nada sobre tu workload. Para saber qué modelo conviene en tu pipeline, necesitas montar tu propio harness de evaluación y medir lo que realmente importa: tokens, latencia y costo por tarea completada. Esta guía te muestra cómo hacerlo sin contaminar la muestra.

Un modelo puede arrasar en SWE-bench y gastar el triple por tarea completada en tu pipeline específico que el modelo anterior [0:07]. La única forma de saberlo es correr la comparativa tú mismo, con tus prompts.

¿Cómo eliminar variables para comparar dos modelos de forma justa?

Si cualquier condición cambia entre runs, los números son basura. La regla base es dejar el modelo como la única variable.

El prompt debe ser bit-for-bit idéntico, no aproximadamente igual [0:40]. Un cambio de una palabra puede activar el clasificador de safety en Fable 5 y provocar un fallback silencioso que contamina tu muestra. Lo mismo aplica a otros parámetros que deben quedar fijos entre runs:

  • max_tokens idéntico en ambos modelos.
  • Effort level pineado al mismo valor.
  • Thinking configurado de forma equivalente.

Y aquí viene la trampa que muerde a casi todos: el thinking. Fable 5 corre adaptive thinking siempre activo por default, mientras que Opus 4.8 no piensa a menos que le pases type adaptive explícitamente [1:04]. Si dejas el default en ambos, no estás comparando modelos, estás comparando configuraciones distintas.

¿Por qué un cambio de una palabra arruina mi benchmark? Porque puede disparar el clasificador de safety del modelo y forzar un fallback silencioso. La respuesta vendrá de otro modelo y tus métricas dejarán de medir lo que crees.

¿Cómo separar dominios sensibles en el eval set?

Tareas de ciberseguridad y biología van a un bucket aparte porque Fable 5 las rutea a Opus de todas formas [1:20]. Compararlos ahí no mide nada sobre Fable 5 en sí. Si mezclas estos dominios con el resto, estás promediando manzanas con peras.

¿Qué métricas debo medir en un harness de evaluación?

Tres variables sostienen todo el análisis. Cada una tiene su propia trampa.

¿Cómo medir tokens consumidos sin subestimar el gasto?

El objeto usage te da output_tokens, que es el total facturado, y dentro de output_tokens_details encuentras thinking_tokens [1:36]. Si output_tokens es 348 y thinking_tokens es 312, estás pagando 312 tokens de razonamiento invisible más solo 36 de texto útil [1:47]. Si solo miras el texto que recibes, subestimas tu gasto real.

¿Cómo medir tiempo de respuesta real?

El error clásico es mezclar latencia de red con latencia de modelo. Hay una trampa adicional con los fallbacks: cuando el modelo primario declina y el sistema intenta un fallback, tu time-to-first-byte incluye el tiempo del intento declinado más el startup del modelo fallback [2:10]. Requests con fallback parecen lentos sin que nada esté roto.

¿Cómo medir calidad y costo por tarea completada?

Para tareas con respuesta correcta, usa verdicts binarios con múltiples pasadas de grading. Para tareas abiertas, comparación pairwise. Pero la métrica que realmente importa es costo por tarea completada: costo por intento dividido por tasa de éxito [2:42]. Un modelo caro que acierta a la primera puede ser más barato que uno barato que necesita tres reintentos.

¿Cómo detectar qué modelo respondió cada turno?

Ya conoces response.model. Pero hay un escenario que esa señal sola no cubre: sticky routing. Después de un fallback, turnos posteriores pueden ser servidos por el modelo fallback durante aproximadamente una hora sin ningún fallback content block visible [3:00]. Parece Fable 5. Es Opus.

La señal confiable es usage.iterations. Cada entrada es un intento:

  • type: message con cero output tokens significa que el modelo solicitado declinó.
  • type: fallback_message te dice quién realmente sirvió.
  • stop_reason: refusal significa que todos los modelos de la cadena declinaron.

¿Qué es sticky routing? Es cuando, tras un fallback, el sistema sigue sirviendo turnos posteriores con el modelo fallback por hasta una hora sin avisarte en el contenido visible. Solo iterations te delata el cambio.

Imagina un árbitro de fútbol que anota quién pateó cada penal, incluso cuando el público no vio el cambio de jugador. Eso es iterations. Sin ese registro, tus promedios son un blend silencioso de dos modelos distintos y no lo sabes.

¿Qué buckets debo mantener separados al analizar resultados?

Con esas señales, mantienes cuatro buckets:

  1. Turnos servidos por el modelo solicitado, datos limpios.
  2. Turnos donde el modelo declinó, refusals invisibles al error monitoring.
  3. Turnos servidos por el fallback, que cuentas bajo el modelo fallback, nunca bajo el solicitado.
  4. Turnos donde hasta el fallback falló.

Artificial Analysis midió entre 8 y 9% de fallback rate en tareas pesadas [4:42]. Sin segregación, tus promedios mienten.

¿Cómo automatizar el harness en CI?

Envías los mismos prompts fijos en un schedule, scoreas con un rubric congelado y guardas las trazas. Si el score cambia, el modelo cambió, no tu setup [4:14].

Incluye un prompt de control que nunca debería ser rechazado. Es tu ancla: si el control regresa, todo el modelo cambió. Si solo los prompts de dominio regresan, el cambio es localizado.

Haz esto ahora mentalmente: ¿cuál es tu ruta más frecuente en producción? ¿Estás segregando resultados por modelo que sirvió, o estás promediando un blend que incluye fallbacks sin saberlo? Cuéntame en los comentarios cómo estás midiendo hoy y qué fallback rate observas en tus tareas más pesadas.