Módulo 7: Shipping Ai Safely

Postmortems de incidentes de IA: cuando "no lo pude reproducir" no cierra el caso

Descripción

El módulo 6 ya enseñó el postmortem blameless: timeline, factores contribuyentes, action items, sin culpar a ninguna persona. Ese marco sigue siendo válido para un incidente que involucra un modelo — pero un paso de la investigación cambia de forma importante, y esta lección se concentra exactamente en ese paso. Frente a un bug de código, el primer instinto de cualquier ingeniero es reproducirlo: mismos pasos, mismo input, ¿pasa lo mismo otra vez? Si sí, ya tienes el 80% del camino hacia la causa raíz. Frente a una salida mala de un modelo —sesgada, fuera de lugar, inconsistente—, ese mismo instinto puede llevarte a cerrar el caso como "no reproducible" cuando, en realidad, el problema es completamente real y va a volver a pasar.

Conexión con el módulo. Esta lección no reemplaza la estructura del postmortem del módulo 6 —timeline, factores contribuyentes, action items, todo eso sigue aplicando igual—. Lo que agrega es un paso previo, específico de los incidentes que involucran un modelo: confirmar si el componente responsable es determinista o probabilístico, antes de decidir si "no reproducible" significa "no es real" o significa, simplemente, "hace falta otro criterio para confirmarlo".

Una analogía: la fotografía y el testimonio

Un bug de código determinista es como una fotografía: la vuelves a tomar en las mismas condiciones —mismo ángulo, misma luz, mismo objeto— y sale exactamente igual, sin importar cuántas veces repitas la toma. Si un ingeniero llama a una función con el mismo input dos veces y obtiene resultados distintos, algo está mal con la función misma —hay una variable compartida, un estado que no se limpia, algo determinista que se está rompiendo—.

Una salida de un modelo probabilístico es más parecida al testimonio de un testigo. Le preguntas a la misma persona sobre el mismo evento dos veces, en dos momentos distintos, y es completamente normal que la segunda versión tenga detalles distintos de la primera —el orden en que menciona las cosas, qué detalle destaca primero, alguna palabra distinta—, aunque el corazón de lo que cuenta sea el mismo. Eso no significa que el testigo esté mintiendo, ni que su primer testimonio fuera falso. Significa que estás frente a un proceso con variación natural, y que juzgar su confiabilidad exige un criterio distinto al de "cuéntalo exactamente igual otra vez, o no te creo".

Un incidente de IA que involucra un componente probabilístico —como un paso de reordenamiento que usa un modelo de lenguaje para refinar el orden final de una lista— se investiga como el testimonio, no como la fotografía. La pregunta correcta no es "¿se repite exactamente?", es "¿con qué frecuencia aparece este patrón, y es lo bastante frecuente como para importar?".

Ejemplo trabajado: checkReproducibility() sobre un incidente real de re-ranking

Mercado incorporó, para el canary de recs-v2, un paso adicional: un modelo de lenguaje de familia vigente reordena (re-rank) el top de candidatos que genera el modelo de recomendaciones, usando el contexto reciente de navegación del comprador. Un usuario reporta que, en su sesión, el carrusel recomendaba de forma desproporcionada productos de grandes cadenas minoristas, casi sin ningún vendedor independiente —un patrón que el equipo quiere investigar como un posible sesgo introducido por ese paso de re-ranking—. Antes de nada más, confirman si el mismo request, repetido, produce siempre la misma salida:

// checkReproducibility: confirma si un incidente reportado se reproduce IGUAL
// cada vez que se repite el mismo input, o si varia -- la primera pregunta
// que hay que contestar antes de tratarlo como un bug de codigo determinista.
// Datos observados de un incidente REAL, capturados en logs (caso pedagogico).
function checkReproducibility(calls) {
  const first = calls[0].topSellerType;
  const matching = calls.filter((c) => c.topSellerType === first).length;
  const allIdentical = matching === calls.length;
  return {
    totalCalls: calls.length,
    matchingFirst: matching,
    allIdentical,
    verdict: allIdentical
      ? 'deterministico: mismo input, mismo output siempre -- tratalo como bug de codigo'
      : 'probabilistico: el mismo input produjo resultados distintos -- no basta con "reproducelo" para confirmarlo',
  };
}

// 5 llamadas identicas (mismo usuario, mismo contexto de sesion) al paso de
// re-ranking, repetidas para investigar el reporte del usuario.
const rerankCalls = [
  { call: 1, topSellerType: 'independent' },
  { call: 2, topSellerType: 'independent' },
  { call: 3, topSellerType: 'large-retailer' },
  { call: 4, topSellerType: 'independent' },
  { call: 5, topSellerType: 'large-retailer' },
];

console.log('=== checkReproducibility sobre 5 llamadas identicas al re-ranker ===\n');
console.log(checkReproducibility(rerankCalls));

Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:

=== checkReproducibility sobre 5 llamadas identicas al re-ranker ===

{
  totalCalls: 5,
  matchingFirst: 3,
  allIdentical: false,
  verdict: 'probabilistico: el mismo input produjo resultados distintos -- no basta con "reproducelo" para confirmarlo'
}

Cinco llamadas idénticas —mismo comprador, mismo contexto, mismo momento del código que arma el request— produjeron tres resultados donde el vendedor principal recomendado era independent y dos donde era large-retailer. Ningún ingeniero que solo intente reproducir el reporte del usuario una vez, o incluso dos, va a obtener una respuesta confiable: podría "confirmarlo" al primer intento, o podría "no reproducirlo" y cerrar el caso, dependiendo de cuál de las cinco llamadas le haya tocado. La conclusión correcta del ejemplo no es "el bug existe" ni "el bug no existe" — es verdict: 'probabilistico': el componente responsable puede dar respuestas distintas a la misma entrada, así que el criterio para investigar el reporte tiene que ser otro.

Cómo cambia la investigación cuando el veredicto es "probabilístico"

Confirmar que un componente es probabilístico no cierra el postmortem — lo redirige. En vez de buscar la línea de código que causa el comportamiento (el enfoque correcto para un bug determinista), la investigación de un incidente probabilístico se mueve hacia tres preguntas distintas, que sí encajan dentro de la estructura de timeline → factores contribuyentes → action items del módulo 6:

¿Con qué frecuencia aparece el patrón reportado, no solo si aparece alguna vez? En el ejemplo de esta lección, matchingFirst: 3 de 5 no te dice si el sesgo hacia grandes cadenas ocurre el 60% de las veces en general, o si esta muestra de 5 llamadas fue, por casualidad, poco representativa. Confirmar la frecuencia real necesita un volumen mayor de llamadas repetidas —el mismo principio de muestra suficiente que ya viste con shadowCompare() en la lección 4—, no cinco intentos manuales.

¿El patrón es aleatorio, o está correlacionado con algo identificable? Un componente probabilístico no significa "completamente al azar, sin ningún patrón". Podría ser que el sesgo hacia grandes cadenas aparezca más seguido para ciertas categorías de producto, o para compradores con cierto tipo de historial de navegación — información que orienta el diagnóstico, exactamente como el desglose por segmento de shadowCompare() orientó la investigación del cold-start en la lección 4.

¿Qué tan grave es el patrón cuando aparece, y con qué frecuencia mínima justifica una acción? Un componente que produce una salida claramente inaceptable el 0.1% de las veces necesita una respuesta distinta a uno que produce una salida cuestionable el 40% de las veces. Esta pregunta es, en el fondo, una decisión de severidad —el mismo concepto que el módulo 5 ya usó para clasificar incidentes—, adaptada a un contexto donde "gravedad" y "frecuencia" son dos ejes independientes que hay que evaluar juntos.

El postmortem final sigue teniendo timeline, factores contribuyentes y action items —esa estructura no cambia—, pero el contenido de "causa raíz" para un incidente probabilístico casi nunca es una línea de código específica. Es, más frecuentemente, una característica del comportamiento del modelo bajo ciertas condiciones —y el action item correspondiente rara vez es "arregla la línea X"; es más probable que sea "ajusta el paso de re-ranking para reducir la variación en este tipo de caso", "agrega una regla de negocio que garantice diversidad mínima de vendedores", o "baja el umbral de canary hasta confirmar la frecuencia real del patrón".

Errores comunes

Cerrar un ticket como "no reproducible" después de un solo intento fallido. Qué pasa: un ingeniero recibe el reporte, intenta reproducirlo una vez, obtiene un resultado distinto al reportado por el usuario, y cierra el caso —sin haber confirmado si el componente responsable es siquiera determinista—. Por qué pasa: la disciplina de "reprodúcelo o no es un bug real" es correcta y valiosa para la inmensa mayoría del software, y aplicarla sin ajuste a un componente probabilístico es un error de transferencia, no de mala intención. Cómo detectarlo: si un ticket relacionado con una salida de modelo se cierra después de un único intento de reproducción, sin ninguna mención de si el componente involucrado es determinista o probabilístico, este error ya ocurrió. Cómo corregirlo: como en el ejemplo de esta lección, la primera pregunta de cualquier investigación de este tipo es checkReproducibility() —con varios intentos, no uno solo— antes de decidir si "no reproducible" significa algo.

Confundir "es probabilístico" con "no se puede investigar ni arreglar". Qué pasa: el equipo confirma que el componente responsable varía entre llamadas y concluye que, por lo tanto, no hay nada concreto que hacer —el reporte se archiva como "así son los modelos"—. Por qué pasa: una vez que se acepta que algo no es 100% predecible, es tentador tratarlo como completamente fuera de control, en vez de como algo que se puede medir y acotar con las herramientas correctas. Cómo detectarlo: si el postmortem termina sin ningún action item concreto, solo con la nota "el modelo es probabilístico, no hay bug que arreglar", la investigación se detuvo demasiado pronto. Cómo corregirlo: probabilístico no significa sin patrón — significa que hace falta medir la frecuencia y la correlación del patrón (las dos preguntas de la sección anterior) antes de decidir qué acción tomar, exactamente como haría cualquier postmortem con una causa raíz más difícil de aislar.

Aplicar el mismo umbral de severidad a un incidente probabilístico que a uno determinista, sin ajustar por frecuencia. Qué pasa: el equipo clasifica un incidente donde el patrón reportado aparece en 1 de cada 500 llamadas con la misma severidad que un bug que rompe el 100% de las veces, porque "de todos modos es un problema real". Por qué pasa: una vez que se confirma que el patrón existe, es fácil tratar su sola existencia como suficiente para la máxima urgencia, sin incorporar qué tan frecuente es en la práctica. Cómo detectarlo: si dos incidentes con frecuencias muy distintas (1 en 500 contra 2 en 5) reciben exactamente la misma severidad y la misma urgencia de respuesta, falta este ajuste. Cómo corregirlo: la severidad de un incidente probabilístico depende de dos ejes, no de uno: qué tan malo es el patrón cuando aparece, y qué tan seguido aparece — y los dos necesitan medirse, no asumirse, antes de decidir la urgencia de la respuesta.

Ejercicios

Ejercicio 1 — Interpreta un resultado distinto. Si checkReproducibility() corriera sobre 8 llamadas y el resultado fuera { totalCalls: 8, matchingFirst: 8, allIdentical: true, verdict: 'deterministico...' }, ¿qué le dirías al equipo sobre cómo investigar este caso, en comparación con el ejemplo de la lección?

Ver solución

Con allIdentical: true, el veredicto es determinista: el mismo input produjo, las ocho veces, exactamente el mismo resultado. En ese caso, la investigación debería volver al enfoque tradicional de depuración de código —encontrar la condición o la línea específica que produce ese resultado, dado ese input exacto—, no el enfoque de medir frecuencia y correlación que esta lección desarrolla para el caso probabilístico. La distinción de esta lección existe precisamente para dirigir el esfuerzo de investigación hacia el camino correcto desde el principio, en vez de aplicar el mismo proceso a los dos tipos de causa.

Ejercicio 2 — Diseña una muestra más grande. El equipo quiere confirmar la frecuencia real del patrón de sesgo hacia grandes cadenas con más confianza que las 5 llamadas del ejemplo. ¿Qué cambiarías del enfoque de checkReproducibility() para usarlo sobre, digamos, 200 llamadas repetidas al mismo request, y qué información adicional te daría ese volumen que 5 llamadas no pueden dar?

Ver solución

La función en sí no necesita cambiar —sigue contando cuántas llamadas coinciden con la primera—, pero el volumen sí importa: con 200 llamadas en vez de 5, el porcentaje resultante (matchingFirst / totalCalls) se acerca mucho más a la frecuencia real del patrón en la población de llamadas posibles, de la misma forma en que la lección 4 mostró que 1,000 usuarios sintéticos dan una lectura más confiable de un porcentaje que 10. Con 5 llamadas, un resultado de 3 de 5 (60%) tiene un margen de error demasiado grande para decidir nada — con 200 llamadas, un resultado similar (digamos, 118 de 200, 59%) ya sostiene una conclusión razonable sobre qué tan seguido aparece el patrón en la práctica.

Ejercicio 3 — Escribe el resumen del postmortem. En 3-4 frases, y usando el vocabulario de esta lección (determinista, probabilístico, frecuencia), escribe el resumen inicial de un postmortem para el incidente de sesgo hacia grandes cadenas descrito en el ejemplo trabajado, asumiendo que una muestra de 200 llamadas confirmó una frecuencia del 35%.

Ver solución

Un resumen razonable: "Un usuario reportó que el carrusel de recomendaciones, durante el canary de recs-v2, mostraba de forma desproporcionada productos de grandes cadenas minoristas. Confirmamos, con checkReproducibility() sobre 200 llamadas idénticas, que el paso de re-ranking es probabilístico: el patrón reportado aparece en aproximadamente el 35% de las llamadas para este tipo de sesión, no de forma determinista ni completamente al azar. Este postmortem investiga las condiciones bajo las que el patrón se vuelve más frecuente, y define los cambios necesarios al paso de re-ranking antes de continuar el canary." Este resumen nombra la naturaleza probabilística del problema desde el inicio, en vez de dejarla implícita, y da un número concreto de frecuencia en vez de solo confirmar que el patrón "existe".

Resumen y siguiente paso

En esta lección construiste checkReproducibility() y la corriste sobre un incidente real de sesgo en el paso de re-ranking de Mercado: de 5 llamadas idénticas, solo 3 coincidieron, confirmando que el componente responsable es probabilístico, no determinista. Viste cómo ese único veredicto cambia el resto de la investigación —de "encuentra la línea de código" a "mide la frecuencia y la correlación del patrón"—, sin abandonar la estructura de postmortem blameless que ya construyó el módulo 6.

Antes de avanzar deberías poder: explicar la diferencia entre investigar un bug determinista y un incidente probabilístico; usar checkReproducibility() como el primer paso de cualquier investigación que involucre un modelo; y justificar por qué la severidad de un incidente probabilístico depende de dos ejes —gravedad y frecuencia—, no solo de uno.

La lección 6 cambia de tema hacia algo que casi ningún incidente de IA menciona hasta que ya es tarde: qué datos del comprador terminaron guardados en los logs que se usaron para investigar este mismo incidente, y si hacía falta guardarlos todos.

Recursos

  • Google SRE Book, Capítulo 15, "Postmortem Culture: Learning from Failure" — sre.google/sre-book/postmortem-culture. El capítulo de referencia sobre postmortems blameless que el módulo 6 ya desarrolló a fondo; esta lección adapta específicamente su paso de investigación de causa raíz al caso de un componente probabilístico. En inglés.
  • AI Incident Database — incidentdatabase.ai. Un repositorio real de incidentes de IA documentados en producción, inspirado explícitamente en las bases de datos de seguridad de aviación — la misma disciplina de aprender de fallas pasadas, aplicada a sistemas de IA. En inglés.