Volver a la ficha del curso Construir con LLMs: API, herramientas y MCP

Módulo 1 Cuándo una respuesta del modelo cuenta como tarea terminada

El incidente: un panel de métricas perfecto que mentía

9 min

El primer panel de métricas de Nexo marcaba un 100 % de éxito. Nueve emails de renovación procesados, nueve respuestas HTTP 200, nueve tareas contadas como hechas.

Cuando alguien abrió las respuestas una a una, dentro había de todo: un texto cortado a mitad de frase, una negativa del modelo, una petición para ejecutar una herramienta, un JSON con el cliente equivocado y varias interpretaciones que nadie había comprobado. Todo eso había entrado en el panel como éxito, porque el panel solo miraba si el servidor contestaba.

Medir cada paso por separado

Si en vez de una sola cifra preguntas en cada paso del camino, sale otra historia:

Señal Casos Qué demuestra
HTTP correcto 9 de 9 El servidor recibió la petición y contestó
Respuesta terminada 6 de 9 El modelo acabó de escribir (stop_reason = end_turn)
Candidato válido 4 de 9 El JSON tiene la forma correcta, sus datos aparecen en el email y es del cliente del caso
Decisión verificada 1 de 9 Una fuente independiente confirma la interpretación
Ruta correcta 9 de 9 Cada caso acabó donde debía: éxito, revisión o rechazo

Dos palabras que vas a ver todo el módulo:

  • Candidato es lo que propone el modelo. Hasta que no se comprueba, es una propuesta y nada más.
  • Revisión es el destino de los casos que el sistema no puede dar por buenos solo. Los mira una persona.

Ojo al leer la tabla. La tasa de éxito no ha caído del 100 % al 11,1 %. El panel original contaba respuestas del servidor y la fila de decisiones verificadas cuenta renovaciones bien resueltas. Son dos preguntas distintas y el panel respondía solo a la primera.

Y fíjate en la última fila. Que solo una renovación se pueda automatizar no significa que el sistema falle. Los otros ocho casos tampoco se han perdido: tres van a revisión porque falta evidencia y cinco se rechazan con un motivo concreto. El sistema funciona bien porque cada caso llega a donde tiene que llegar, aunque automatice poco.

Regla central: una respuesta recibida no equivale a una tarea completada.

Pruébalo: haz visible el falso éxito

En la carpeta del proyecto, ejecuta:

npm run demo:01

Primero sale una tabla con los nueve casos. Cada fila tiene cuatro columnas:

  • caseId: el caso.
  • modelOutcome: cómo terminó la respuesta del modelo.
  • businessOutcome: qué decide el negocio con ella.
  • code: el motivo concreto.

Esto es lo que deberías ver, agrupado:

Casos modelOutcome businessOutcome code
case_verified model_completed task_succeeded VERIFIED
case_unverified, case_double_cancel, case_no_missing model_completed semantic_review_required SEMANTIC_EVIDENCE_MISSING
case_garbage model_completed task_not_succeeded CANDIDATE_INVALID
case_wrong_identity model_completed task_not_succeeded IDENTITY_MISMATCH
case_truncated, case_refusal, case_action truncated, refused, action_requested task_not_succeeded MODEL_NOT_COMPLETED

Lee la tabla de abajo arriba:

  • Los tres últimos ni siquiera terminaron bien: uno se cortó, otro se negó y otro pidió ejecutar una herramienta para aplicar el descuento.
  • case_garbage terminó, pero su texto no es un JSON válido.
  • case_wrong_identity es un JSON perfecto con un customer_id que no es el del caso.
  • Los tres que van a revisión tienen JSON válido, pero nadie ha confirmado la intención. Dos de ellos son trampas a propósito: «No queremos no renovar» y «No falta información». Una doble negación que se lee fácil al revés.
  • Solo case_verified tiene una confirmación independiente, hecha por una persona. En la demo esa revisión está escrita a mano en el fichero, como un registro con method: "human_review". En producción vendría de la herramienta donde tu equipo revisa los casos, con quién lo revisó y cuándo.

Debajo de la tabla sale un objeto con las métricas. Busca estas líneas:

httpSuccessRate: 1,
modelCompletionRate: 0.6666666666666666,
candidateValidRate: 0.4444444444444444,
semanticVerificationRate: 0.1111111111111111,
routingAccuracy: 1,
taskSuccessRate: 0.1111111111111111,

Son las filas de la tabla del principio, en tanto por uno. naiveHttpAndTextSuccessRate: 1 es lo que enseñaba el panel original. Las líneas de latencia y coste (p95LatencyMs, tokensPerSuccessfulTask, totalCost, costPerSuccessfulTask y el bloque pricing) las explica la lección 7. De momento, ignóralas.

Lo que te llevas

Si quieres ver de dónde sale cada cifra, abre demos/parte-1/01-incidente-runtime.ts. Los nueve casos están escritos a mano al principio del fichero, con la respuesta del modelo y el resultado que se espera de cada uno.

La decisión que tienes que interiorizar es esta: mandar a revisión un caso sin evidencia suficiente es un resultado correcto. El error sería contarlo como éxito o esconderlo de las métricas. En la siguiente lección ves cómo se representa cada uno de esos estados en el código.