SEXTANTEcursos técnicos de IA
métodobackward-design
árbitroel dato
Entrar
N5 · Producción y experimentación/L5

Drift y degradación

Objetivo de maestría

definirás qué monitorizar en producción y montarás un monitor de drift (input, output y semántico, con PSI) que dispare una alerta — incluido el caso en que el proveedor cambie el modelo bajo tus pies. Sin esto, te enteras de las regresiones por el tuit de un cliente, no por tu dashboard.


5.1La mañana en que nada cambió y todo empeoró

Es lunes. Aurora —la tienda online ficticia que es tu banco de pruebas todo el curso— lleva tres semanas estable en producción.

Abres el dashboard con el café. La groundedness que mide tu juez online (la que montaste en L2) ha caído del 0,91 al 0,78 durante el fin de semana. Los escalados a humano se han duplicado. El soporte humano está saturado.

Repasas el historial de despliegues. No hubo ninguno. Nadie tocó el prompt. Nadie tocó el código. El último merge fue hace nueve días y pasó el gate de CI del N4 en verde.

Y aun así, el sistema responde peor que el viernes.

¿Qué cambió, si tú no cambiaste nada? Dos sospechosos plausibles, ninguno bajo tu control:

  • La distribución de preguntas. El sábado arrancó la campaña de rebajas. Los clientes ya no preguntan por horarios de entrega; preguntan por reembolsos, cancelaciones y precios tachados. Tu agente nunca vio ese mix.
  • El proveedor. Anthropic pudo actualizar el modelo que sirve tu endpoint. Tu código llama a la misma API, pero el modelo del otro lado ya no es exactamente el del viernes.

Sin un monitor que vigile esto, no puedes distinguir entre los dos. Peor: ni siquiera sabrías que algo se rompió hasta que el tuit del N0·L1 vuelve a aparecer en tu timeline. Te enteras por Twitter, no por el dashboard. Eso es exactamente lo que un sistema observado no debería permitir.


5.2Qué vas a poder hacer

Al terminar esta lección sabrás:

  • Enumerar las cinco familias de métricas que se monitorizan en un sistema LLM en producción y qué fallo detecta cada una.
  • Distinguir tres tipos de drift —de input, de output y semántico— y razonar cuál explica una caída de calidad dada.
  • Calcular el PSI (Population Stability Index) entre dos ventanas temporales y decidir, con su límite heurístico explícito, si hay shift significativo.
  • Identificar qué campo de span permite detectar que el proveedor cambió el modelo sin que tú toques el código.

Necesitas saber antes:

  • El sampling de evals online de N5·L2 (evalúas el 5–20% del tráfico, no el 100%).
  • Las señales de prod de L2/L3/L4: scores del juez online, feedback del usuario, métricas de experimento.
  • Los campos de span del N0·L4, en especial modelo+versión, finish_reason, latencia y coste.
  • Python a nivel de leer funciones, diccionarios y un bucle con numpy.

Esta lección no arregla la caída. La hace visible y diagnosticable. Arreglarla —convertir el fallo en un caso nuevo del dataset— es el trabajo de L6, el cierre del flywheel.


5.3Recupera

Antes de seguir, responde mentalmente. No mires lo de abajo hasta tener una respuesta.

  1. En L2 configuraste evals online con sampling al 5%. ¿Qué métrica de prod te da hoy ese juez, y sobre cuántas trazas?
  2. En N0·L4 instrumentaste cada span de generación. ¿Qué campos guardaste que te dejen reconstruir con qué modelo se generó una respuesta?
  3. El agente "empeoró" sin que tú cambiaras nada. En un servicio web normal, si el código no cambió y el comportamiento sí, ¿qué suele haber cambiado?

La respuesta a la 3 es el entorno: los datos de entrada, una dependencia externa, el tráfico. En software clásico esa intuición ya la tienes. En un sistema LLM hay dos entornos que se mueven solos. Uno es la distribución de inputs que te mandan los usuarios. El otro es el modelo que sirve tu proveedor. Ninguno aparece en tu git log. Por eso necesitas vigilarlos aparte.


5.4El concepto: qué se mueve cuando tú no te mueves

Empecemos por lo concreto —qué mirar— y subamos hacia la idea general —por qué se mueve—.

Qué monitorizar: las cinco familias

Un sistema LLM en producción se vigila con cinco familias de métricas (Braintrust, What is LLM monitoring, 9 feb 2026):

  • Latencia — TTFT (time-to-first-token), y los percentiles p50 / p90 / p99 de la respuesta completa.
  • Coste — tokens de entrada y salida, coste por request.
  • Fiabilidad — error rate y refusal rate (con qué frecuencia el modelo rechaza responder).
  • Calidad — los scores de tu juez online, tasa de alucinación, faithfulness.
  • Seguridad — toxicidad, prompt injection.

La regla de loggeo de esa misma fuente tiene dos partes. Registra el 100% de las métricas básicas —latencia, coste y error rate son baratas—. Aplica sampling del 10–20% para trazas y evals, que son caras. Es coherente con el 5% que ya usas en tus evals online de L2: las métricas básicas no pasan por el juez, así que las cuentas todas.

Una advertencia de honestidad: esta fuente es un vendor (tier 2) y ofrece las familias, no umbrales prescriptivos. Te dice qué mirar, no a partir de qué número alarmarte. El umbral lo fijas tú con tu propio histórico.

Tres tipos de drift

Llega el término central. Drift —deriva— es el cambio en la distribución de datos a lo largo del tiempo, sin que tú toques el sistema. La analogía: como un río que poco a poco cambia de cauce sin que nadie mueva una piedra. El límite de la analogía: el drift puede ser brusco (una campaña de rebajas de un día), no solo gradual.

Hay tres tipos (OpenObserve, LLM monitoring best practices, abr 2026; tier 3, confianza media):

  • Drift de input — cambia la distribución de lo que entra. Los clientes empiezan a preguntar otras cosas (rebajas → más reembolsos).
  • Drift de output — cambia la distribución de lo que sale. Sube el refusal rate, cambia la longitud media de respuesta, se disparan los escalados.
  • Drift semántico — cambia el significado de las entradas o salidas, detectable agrupando embeddings (clustering). Aparecen clusters de preguntas que antes no existían. Concreto: si pasas tus inputs por un modelo de embeddings y los agrupas, verás aparecer un cluster "reembolsos" que la semana pasada no existía — eso es drift semántico.

Es común confundir drift de input con degradación del modelo. La diferencia clave: en el drift de input el modelo responde igual de bien que siempre, pero le llegan preguntas para las que tu sistema no estaba preparado. La caída de calidad es real; la causa no es el modelo. Tratar lo uno como lo otro te lleva a reentrenar o cambiar de prompt cuando lo que necesitabas era ampliar tu KB de reembolsos.

PSI: poner número al shift de input

Para decidir "¿cuánto se ha movido la distribución de inputs?" se usa el PSI (Population Stability Index) —índice de estabilidad de población—. Compara dos distribuciones (la de referencia y la actual) y devuelve un número que crece cuanto más se separan.

Detente aquí, porque viene la única fórmula de la lección. No es difícil de leer si la tomas por partes. Divides los datos en buckets (rangos), miras qué fracción cae en cada bucket en cada ventana, y sumas la diferencia ponderada por un logaritmo. Lee primero la fórmula entera, luego cada símbolo.

Para cada bucket i, con a_i = fracción en la ventana actual y e_i = fracción en la de referencia:

text
1PSI = Σ (a_i − e_i) · ln(a_i / e_i)

La interpretación por rangos (futureagi, 2026; tier 4, confianza media):

  • PSI < 0,1 → sin shift relevante.
  • 0,1 ≤ PSI ≤ 0,2 → shift moderado, vigílalo.
  • PSI > 0,2 → shift significativo, investiga.

Caveat innegociable: estos cortes son un heurístico de ML clásico de una fuente tier 4, marcado [verificar] en la API del nivel. No son una ley física ni una recomendación de fuente primaria. Funcionan como punto de partida; el umbral real lo calibras contra tu histórico, igual que cualquier alerta. Citarlos como "el estándar dice >0,2" sería exactamente el tipo de cifra sin fuente sólida que este curso combate.

Señales de comportamiento: lo que el usuario te grita sin score

Hay drift que no necesita PSI para detectarse. Dos señales de comportamiento del usuario delatan insatisfacción o deriva (corpus E.4; fuente tier 3, confianza media, [verificar]):

  • Retries (rephrasing) — el cliente reformula la misma pregunta. Lo viste en L3 como feedback implícito; aquí, un repunte agregado de retries es señal de drift.
  • Refusal rate — si el agente empieza a rechazar o escalar más que la semana pasada, algo se movió en el input o en el modelo.

Ambas son las mismas señales de L3, ahora leídas en agregado y en el tiempo, no caso a caso.

Cuando el proveedor cambia el modelo bajo tus pies

Aquí está el modo de fallo que da nombre al hook. Un model update silencioso del proveedor es drift de output puro: la salida cambia de distribución y tú no tocaste nada. Tu código llama a la misma API; el modelo del otro lado ya no es el del viernes.

¿Cómo lo detectas si no aparece en tu git log? Con el campo que guardaste en N0·L4 en cada span: modelo + versión. Si el version que devuelve el proveedor cambia entre dos ventanas, lo ves en tus propias trazas. Ese campo es tu sismógrafo del modelo.

Y tu red de seguridad ya está montada en capas anteriores:

  • El gate offline del N4 atrapa la regresión si vuelves a correr la suite contra el dataset versionado.
  • Los evals online de L2 atrapan la caída de calidad sobre tráfico real, model update o no.

Anthropic enmarca esto como trabajo de largo plazo. De sus "8 pasos" para evals de agentes, dos importan aquí. El primero: monitorizar la saturación del eval (cuando tu suite deja de discriminar porque todo la pasa). El segundo: mantener el ownership a largo plazo del sistema de evaluación (Anthropic, Demystifying evals for AI agents, 9 ene 2026). El monitor de drift no es un extra; es la parte del trabajo que nunca termina.


5.5Míralo funcionar: un monitor de drift sobre Aurora

Vamos a construir dos monitores reales: uno de input drift (PSI sobre la distribución de tipos de pregunta) y uno de output drift (refusal/escalation rate). El cálculo de PSI es matemática estándar en numpy. El reporte de la alerta lo escribimos como un score de Langfuse, con la API que ya conoces de L2/L3.

Lee primero el bloque entero. Después lo analizamos por partes.

python
1import numpy as np
2from langfuse import get_client
3
4langfuse = get_client()
5
6# --- Monitor 1: input drift sobre el tipo de pregunta ---
7
8# Categorías de pregunta de Aurora (las del sistema-hilo del curso).
9CATEGORIAS = ["pedido", "politica", "escalado", "otro"]
10
11def psi(referencia: np.ndarray, actual: np.ndarray, eps: float = 1e-6) -> float:
12    """PSI entre dos distribuciones discretas ya normalizadas.
13
14    referencia/actual: fracción de tráfico por categoría (suman 1).
15    eps evita ln(0) y división por 0 en buckets vacíos.
16    """
17    ref = np.clip(referencia, eps, None)
18    act = np.clip(actual, eps, None)
19    return float(np.sum((act - ref) * np.log(act / ref)))
20
21# Ventana de referencia: la semana antes de las rebajas.
22ref = np.array([0.55, 0.30, 0.10, 0.05])   # pedido, politica, escalado, otro
23# Ventana actual: el fin de semana de rebajas.
24act = np.array([0.20, 0.55, 0.20, 0.05])   # se dispara "politica" (reembolsos)
25
26valor_psi = psi(ref, act)
27
28# Umbral HEURÍSTICO (ML clásico, tier 4) — calíbralo con tu histórico.
29UMBRAL_PSI = 0.2
30hay_shift = valor_psi > UMBRAL_PSI
31
32print(f"PSI = {valor_psi:.3f}  -> shift significativo: {hay_shift}")
33
34# --- Monitor 2: output drift sobre refusal/escalation rate ---
35
36def escalation_rate(trazas: list[dict]) -> float:
37    """Fracción de turnos donde el agente llamó a escalar_a_humano."""
38    if not trazas:
39        return 0.0
40    escalados = sum(1 for t in trazas if t["tool"] == "escalar_a_humano")
41    return escalados / len(trazas)
42
43esc_referencia = 0.10   # 10% de escalados la semana base
44esc_actual = 0.21       # 21% este fin de semana
45
46delta = esc_actual - esc_referencia
47UMBRAL_ESCALADO = 0.05  # heurístico propio: +5pp dispara revisión
48dispara = delta > UMBRAL_ESCALADO
49
50# --- Reportar la alerta como score de sesión de monitoring en Langfuse ---
51if hay_shift or dispara:
52    langfuse.create_score(
53        name="drift-alert",
54        value=valor_psi,
55        session_id="monitoring-rebajas-2026-06",
56        data_type="NUMERIC",
57        comment=(
58            f"input PSI={valor_psi:.3f} (>{UMBRAL_PSI} heuristico); "
59            f"escalation {esc_referencia:.0%}->{esc_actual:.0%} (delta {delta:+.0%})"
60        ),
61    )
62    langfuse.flush()

Ahora la pregunta de auto-explicación. Antes de leer el análisis, responde: ¿por qué el input drift puede explicar la caída de groundedness del hook sin que el modelo haya empeorado?

Lo que pasa, paso a paso:

  • El monitor 1 calcula PSI = 0,57 entre la semana base y el fin de semana de rebajas. Supera de sobra el umbral heurístico de 0,2. La distribución de preguntas se movió fuerte: de un 30% de consultas de política a un 55%. El modelo no cambió; el mix de preguntas sí.
  • El monitor 2 ve los escalados subir del 10% al 21%, un delta de +11 puntos. Es output drift: el comportamiento de salida cambió.
  • El reporte de la alerta usa create_score con session_id —la firma verificada de la API de N5—, guardando el PSI como un NUMERIC y el diagnóstico en el comment. La alerta queda en el mismo Langfuse donde viven tus trazas, no en un canal aparte que nadie mira.

¿Y la groundedness? Si el 55% de las preguntas ahora son sobre reembolsos y tu KB de reembolsos es flaca, el paso RAG recupera contexto pobre —como el envios-12 del N0·L1— y la groundedness cae. El modelo responde igual de bien que siempre sobre el contexto que le das. El contexto es el que empeoró, porque le preguntan otra cosa. Esa es la cadena que el PSI te deja ver y que un dashboard de "groundedness a secas" te ocultaba.

Un matiz honesto: este monitor clasifica la causa probable (input drift), no la arregla ni la confirma sola. Para confirmar que es el proveedor y no el input, comparas el campo version de los spans entre ventanas. Eso es el ejercicio que sigue.


5.6Hazlo tú

Ejercicio 1 — andamiaje parcial

Tienes el psi() de arriba funcionando. Te falta el monitor que distingue model update del proveedor de los otros dos drifts. Completa los dos huecos:

python
1def detectar_model_update(spans_ref: list[dict], spans_act: list[dict]) -> bool:
2    """True si el modelo+version del proveedor cambió entre ventanas.
3
4    Cada span tiene span["model"] = "claude-sonnet-4-6" y
5    span["version"] = "<id de version del proveedor>".
6    """
7    versiones_ref = {(s["model"], s["version"]) for s in spans_ref}
8    versiones_act = {(s["model"], s["version"]) for s in spans_act}
9    # Hueco 1: ¿qué versiones aparecen ahora que no estaban antes?
10    nuevas = ___________________________________
11    # Hueco 2: ¿qué condición sobre `nuevas` indica un model update?
12    return ___________________________________

Pista: el campo que delata el cambio es el que registraste en N0·L4 en cada span de generación. Una versión nueva en la ventana actual que no estaba en la de referencia es la firma del update.

Ejercicio 2 — autónomo

El dashboard de Aurora muestra una caída de groundedness esta semana. Tienes a mano: el juez online (L2), el PSI de input, el refusal/escalation rate, y el campo version de los spans.

Diseña desde cero un árbol de diagnóstico que distinga entre {input drift, output drift, drift semántico, model update del proveedor}. Para cada rama, escribe: qué métrica miras, qué umbral usas y —obligatorio— marca cuál de esos umbrales es heurístico. La rama semántica no usa PSI: la detectas agrupando embeddings de los inputs y buscando un cluster que la ventana de referencia no tenía.

Antes de seguir, una pregunta de interrogación elaborativa. Respóndela tú primero: ¿por qué guardar modelo+versión en cada span —y no solo "el modelo que configuramos en el código"— es lo que hace detectable un model update silencioso? Piénsalo antes de leer la línea siguiente.

Porque lo que configuras en el código es tu intención ("llama a Claude Sonnet"); lo que el span registra es lo que el proveedor realmente sirvió, versión incluida. Si el proveedor actualiza el modelo detrás del mismo nombre de endpoint, tu config no cambia pero el version del span sí. Sin ese campo, la intención y la realidad se confunden, y el update queda invisible.


5.7Comprueba

Sin pistas. Dos preguntas; necesitas las dos para considerar superado el punto.

Pregunta 1. La distribución de tipos de pregunta de Aurora pasó de [0.50, 0.35, 0.10, 0.05] (referencia) a [0.45, 0.38, 0.12, 0.05] (actual), en el orden [pedido, politica, escalado, otro]. Calcula el PSI a mano o mentalmente por orden de magnitud y decide si hay shift significativo. Indica explícitamente qué caveat aplica a tu umbral.

Pregunta 2. La groundedness cayó pero el PSI de input es 0,03 y el refusal rate no se movió. ¿Qué campo de span revisarías primero para comprobar si la causa es un model update del proveedor, y por qué ese campo?

Ver la respuesta razonada

Pregunta 1. Los cambios por bucket son pequeños (0,50→0,45; 0,35→0,38; 0,10→0,12; 0,05→0,05). El PSI sale ≈ 0,01, muy por debajo de 0,1. No hay shift relevante. Caveat obligatorio: el corte de 0,1 es un heurístico de ML clásico (tier 4), no un estándar con fuente primaria; sirve de punto de partida, lo calibras con tu histórico.

Pregunta 2. Revisas el campo version (parte de modelo+versión) de los spans de generación, comparándolo entre las dos ventanas. Si aparece una versión nueva en la ventana actual que no estaba en la de referencia, el proveedor actualizó el modelo bajo tus pies. Es la única señal que distingue un update silencioso de un drift de input —que aquí ya descartaste, porque el PSI es 0,03—.


Feedback formativo:

  • Si acertaste ambas y nombraste el caveat del PSI: dominas el núcleo de la lección —medir el shift y, sobre todo, no tratar un heurístico de blog como una ley—. Reutilizarás este criterio en C5, donde el drift es una de las cinco dimensiones de la rúbrica.
  • Si calculaste mal el PSI pero acertaste la dirección (sin shift): tu intuición es correcta; la aritmética es lo de pulir. El término (a_i − e_i)·ln(a_i/e_i) es pequeño cuando a_i y e_i están cerca. Vuelve al §5.4 y recorre la fórmula con estos cuatro buckets, uno a uno.
  • Si en la 2 dijiste "miro la groundedness otra vez": la groundedness te dice que algo cayó, no por qué. Es el síntoma, no la causa. El campo que distingue un model update de un input drift es version, porque es el único que cambia cuando el proveedor actúa y tú no. Relee §5.4, "Cuando el proveedor cambia el modelo bajo tus pies".

5.8Conecta

Vuelve a la mañana del lunes con el café frío.

Con los monitores de esta lección, esa mañana cambia de guion. El dashboard ya no te muestra solo "groundedness 0,78". Te muestra: PSI de input 0,57 (rebajas), escalation rate del 10% al 21%, y version del modelo sin cambios. En treinta segundos sabes que el proveedor no tocó nada. La campaña de rebajas inundó al agente de preguntas de reembolso que tu KB no cubre bien. El agujero negro se vuelve un diagnóstico, igual que la traza explotable del N0·L1.

Esto encaja en las cinco dimensiones del checkpoint C5: el drift es la cuarta —qué monitorizas (input/output/semantic, PSI) con umbral y alerta—. Ya tienes las cuatro primeras piezas del flywheel de producción:

  • evals online sobre tráfico real (L2),
  • feedback del usuario como señal (L3),
  • un experimento decidido con datos (L4),
  • y un monitor de drift que alerta (esta lección).

Cuando un monitor dispara, tienes un fallo de producción en las manos. L6 lo convierte en un caso nuevo del dataset versionado del N1, lo pasa por el gate del N4 y cierra el flywheel. Es el checkpoint C5 y el cierre del curso. La caída de esta mañana no se queda en un susto: se vuelve una regresión permanente que protege a Aurora del mismo fallo para siempre.

¿Dónde lo aplicarías en tu trabajo? Piensa en cualquier sistema LLM que tengas en producción. Si el proveedor actualizara su modelo esta noche, ¿lo verías en tu dashboard mañana, o lo descubrirías por una queja la semana que viene? Si la respuesta es "por la queja", ya sabes qué campo de span te falta registrar y qué monitor te falta montar.


5.9Reflexiona

Tómate dos minutos. Estas preguntas consolidan más que releer.

  • ¿Qué dos entornos se mueven solos en un sistema LLM y no aparecen en tu git log?
  • Con tus palabras: ¿por qué una caída de groundedness no implica que el modelo haya empeorado?
  • ¿Qué umbral de esta lección es heurístico y cómo lo justificarías ante alguien que lo cita como "el estándar"?
  • ¿Qué sigue sin estar claro? Anótalo. Si es "cómo cierro el bucle cuando el monitor dispara", es la pregunta correcta —la responde L6—.

Referencia rápida

  • Cinco familias a monitorizar: latencia (TTFT, p50/p90/p99), coste (tokens, coste/request), fiabilidad (error/refusal rate), calidad (scores del juez, alucinación, faithfulness), seguridad (toxicidad, prompt injection). Loggea 100% de métricas básicas + sampling 10–20% para trazas/evals (Braintrust, feb 2026).
  • Tres tipos de drift: input (cambia lo que entra), output (cambia lo que sale), semántico (cambia el significado, vía clustering de embeddings) (OpenObserve, abr 2026; tier 3).
  • PSI: Σ (a_i − e_i)·ln(a_i/e_i). Cortes <0,1 / 0,1–0,2 / >0,2 son heurísticos ML clásico (tier 4) — calíbralos con tu histórico, no son un estándar.
  • Señales de comportamiento: repunte de retries (rephrasing) y de refusal/escalation rate = insatisfacción/drift (corpus E.4; [verificar]).
  • Model update silencioso: lo detectas comparando el campo version (de modelo+versión, N0·L4) entre dos ventanas. Tu red: gate offline (N4) + evals online (L2).
  • Largo plazo: monitoriza la saturación del eval y mantén ownership del sistema de evaluación (Anthropic, Demystifying evals for AI agents, 9 ene 2026, "8 pasos").
  • No usar nunca: "el 85% de proyectos IA fallan (Gartner)" ni "el 80% (McKinsey)" — cifras sin fuente primaria trazable.