volver al blog

Cambiar de modelo en producción sin romper nada

llmdespliegueproduccion

El correo que te obliga a mover ficha

Llega el aviso: el modelo que usas se retira en 90 días. O peor, llega la buena noticia: hay uno nuevo, un 40 % más barato y con mejores números en los benchmarks. En ambos casos el resultado es el mismo. Tienes que tocar la pieza que menos ganas tienes de tocar.

Y la tentación es tratarlo como una actualización de dependencia: cambias el identificador del modelo en la configuración, pasan los tests, despliegas. El viernes por la tarde alguien de soporte escribe que el asistente “responde raro”. No falla. Responde raro. Que es mucho peor, porque no salta ninguna alarma.

Un modelo no es una dependencia

Cuando subes de versión una librería, el contrato es explícito: firmas de funciones, tipos, un changelog. Si algo rompe, rompe en compilación o en un test.

Un modelo no tiene ese contrato. Tiene un comportamiento, y ese comportamiento está acoplado a tu prompt de una forma que nadie documentó. Ese Responde en un único párrafo que funcionaba porque el modelo antiguo era obediente con el formato. Esa lista de ejemplos que compensaba una debilidad concreta. El temperature: 0.2 que calibraste a ojo hace ocho meses.

Nada de eso está en un package.json. Está en la interacción entre tu texto y unos pesos que acaban de cambiar por completo.

Por eso la pregunta correcta no es “¿el modelo nuevo es mejor?”, sino “¿el modelo nuevo es mejor para mis peticiones reales, con mi prompt actual?”. Los benchmarks públicos responden a la primera. Solo tu tráfico responde a la segunda.

Tres formas de hacer el cambio

EstrategiaQué hacesRiesgo para el usuarioCosteCuándo tiene sentido
Cambio directoSustituyes el modelo y despliegasAlto: el 100 % del tráfico es tu pruebaNinguno extraPrototipos, uso interno, nada crítico
ShadowLlamas a los dos modelos, sirves solo el antiguoNingunoDoble en las peticiones espejadasAntes de decidir: quieres datos, no valentía
CanaryDiriges un porcentaje pequeño al modelo nuevoAcotado al porcentajeMarginalDespués de decidir: quieres confirmar en real

Las dos últimas no compiten, se encadenan. El shadow te dice si el modelo nuevo aguanta tus casos. El canary te dice si aguanta a tus usuarios, que no es lo mismo: los usuarios reformulan, insisten, pegan cosas raras y abandonan sin avisar.

Shadow: pagar por saber

La idea es sencilla. Cuando entra una petición, la respondes con el modelo actual, como siempre. En paralelo, y sin bloquear al usuario, mandas la misma entrada al candidato y guardas las dos salidas para compararlas después.

Dos detalles importan más de lo que parece. El primero: la llamada espejo nunca puede afectar a la latencia ni al éxito de la respuesta que ve el usuario. Si el candidato tarda o falla, es problema del registro, no de la petición. El segundo: no espejes el 100 % del tráfico. Con un 5 % de una muestra representativa tienes suficiente para decidir, y no duplicas la factura.

import asyncio, random

MODELO_ACTUAL = "modelo-estable"
MODELO_CANDIDATO = "modelo-nuevo"
TASA_ESPEJO = 0.05  # 5 % del tráfico: suficiente para decidir, barato de mantener

async def responder(peticion):
    # Ruta crítica: siempre el modelo en producción. Nada la bloquea.
    respuesta = await llamar(MODELO_ACTUAL, peticion)

    if random.random() < TASA_ESPEJO:
        # Fire-and-forget: si el candidato peta o tarda, el usuario ni se entera.
        asyncio.create_task(_espejar(peticion, respuesta))

    return respuesta

async def _espejar(peticion, respuesta_base):
    try:
        # Timeout corto: esto es telemetría, no una respuesta que alguien espera.
        candidata = await asyncio.wait_for(
            llamar(MODELO_CANDIDATO, peticion), timeout=10
        )
    except Exception as e:
        registrar_comparacion(peticion, respuesta_base, None, error=str(e))
        return

    registrar_comparacion(peticion, respuesta_base, candidata)

Con unos días de esto tienes algo que ningún benchmark te da: un corpus de pares reales sobre los que medir. Y sobre todo, tienes los casos donde las dos respuestas divergen mucho, que son exactamente los que hay que mirar a mano.

Canary: soltar el 5 % y mirar de verdad

Cuando los datos del shadow acompañan, empiezas a servir el modelo nuevo a una fracción del tráfico. La parte técnica es trivial: un porcentaje en la configuración y un enrutado. La parte que se hace mal es el reparto.

Si sorteas por petición, un mismo usuario recibirá respuestas de los dos modelos en la misma conversación. Eso no es un experimento, es un fallo de coherencia. Reparte por identificador de sesión o de usuario, con un hash estable, para que quien entra en el canary se quede en él mientras dure.

Y define de antemano el umbral de reversión. No “si va mal, lo quitamos”, sino un número concreto: “si la tasa de fallo de formato supera el 2 % o la latencia p95 sube más del 30 %, volvemos”. Escrito antes de empezar, porque a las once de la noche del jueves nadie tiene el criterio frío.

Los tres números que deciden

Tras el shadow y antes del canary, mira estos tres. En este orden.

  1. Fallos duros. Salidas que no cumplen el esquema, JSON que no parsea, llamadas a herramientas con argumentos inválidos. Es lo único binario que tienes y suele ser donde primero se nota un cambio de modelo.
  2. Coste y latencia reales. El precio por millón de tokens no es el coste. Un modelo más barato que se enrolla el doble sale más caro y responde peor. Mide tokens de salida por petición, no la tarifa.
  3. Calidad en tus casos. Aquí es donde hace falta el conjunto de evaluación que llevas meses posponiendo. Treinta casos reales con su respuesta esperada valen más que cualquier tabla de benchmarks, porque están sesgados hacia tu problema. Ese sesgo es justo lo que quieres.

Si el punto tres te bloquea, esa es la señal. El cambio de modelo no es el problema: el problema es que no tienes con qué medirlo.

Lo que queda después

La ventaja de montar esto una vez es que ya no vuelves a montarlo. El enrutado por porcentaje, el registro de comparaciones y el umbral de reversión se quedan puestos. La siguiente migración deja de ser un proyecto y pasa a ser una configuración.

Empieza por lo pequeño: un porcentaje de espejo en tu endpoint más usado y una tabla donde guardar los pares. Aunque hoy no tengas que cambiar de modelo, tendrás que hacerlo. El correo llega siempre.