Módulo 4: Medir latencia con honestidad
Módulo 4: Medir latencia con honestidad
Descripción
El Módulo 3 cerró la primera mitad de la disciplina de medir: cada run de Reservo ya tiene un costo en centavos, calculado con una fórmula fija sobre tokens estimados. Pero si vuelves a mirar un CostReport de la lección 08 de ese módulo, vas a notar algo que falta: ningún campo dice cuánto tardó el run. cost_cents responde "¿cuánto consumió?"; ninguna otra pregunta de negocio —"¿el cliente esperó demasiado?", "¿qué herramienta se sintió lenta?"— tiene respuesta todavía.
Este módulo cierra esa segunda mitad. Pero antes de escribir una sola línea de código, hay que resolver un problema que no existía en el Módulo 3 de la misma forma: medir tiempo, de verdad, rompería la reproducibilidad de toda esta guía. El costo se estima con una fórmula determinista (len(texto) // 4 más un precio fijo) que da el mismo número en tu máquina, en la mía, y en la de cualquiera que corra el mismo código. El tiempo real que tarda una función en ejecutarse no funciona así — depende de qué tan cargada está tu CPU en este instante, de qué otros procesos compiten por ella, de factores que ni tú ni yo controlamos. Si esta guía midiera latencia con el reloj real, cada "Qué esperar" de este módulo sería una mentira: un número que nunca vas a poder reproducir exactamente en tu propia terminal.
La solución de esta guía —adelantada desde el Módulo 1, y puesta en práctica de verdad a partir de aquí— es modelar la latencia: declarar, en un diccionario fijo, cuánto "tarda" cada herramienta, y sumar esos valores fijos en vez de cronometrar nada. Este módulo no es un ejercicio de honestidad a medias. Cada una de sus ocho lecciones dice, explícitamente, la misma frase: esto está modelado, no medido; en producción de verdad se mide con el reloj real; aquí se modela para que el ejemplo sea reproducible y el foco esté en el análisis, no en el cronómetro.
Regla dura de este módulo (la más estricta de toda la guía)
Los Módulos 1 a 3 ya prohibieron random, datetime.now() y uuid4() en cualquier dato de esta guía. Este módulo agrega la prohibición más importante de las ocho: ningún bloque de código ejecutado de este módulo usa time.time() ni time.perf_counter(). Ninguno. Ni siquiera para "solo mostrar cómo se vería". Cuando esta guía necesite nombrar cómo se mide latencia en producción de verdad, lo va a hacer en prosa, citando el nombre de la función — nunca dentro de un bloque de código que después se presenta como ejecutado.
La razón no es estética. Es la misma razón que llevó a esta guía a prohibir random desde el Módulo 1: cualquier fuente de datos que no sea determinista rompe la promesa central de esta guía —que puedes correr exactamente el mismo código que ves aquí y obtener exactamente el mismo resultado—. time.perf_counter() es, precisamente, una fuente de datos no determinista: llámalo dos veces seguidas en la misma máquina y vas a obtener dos números distintos, ninguno de los dos reproducible por otra persona.
En su lugar, este módulo trabaja con un dato fijo que ya conoces del Módulo 1:
TOOL_LATENCY_MS = {
"list_rooms": 40,
"get_quote": 25,
"book_room": 120,
"cancel_booking": 90,
}
Estos cuatro números no midieron nada — son una declaración de diseño, tan deliberada como las anclas de precio (Focus basic 3h = 7500) que ya conoces. Y son, a propósito, plausibles: book_room (una escritura, con efectos reales) es la más cara de las cuatro; get_quote (un cálculo en memoria, sin tocar ningún estado) es la más barata. Esta guía no inventa esos números en cada lección — los fija una sola vez, aquí, y los reusa sin cambiarlos hasta el cierre del Módulo 8.
Dónde estamos en el ecosistema
Agentes en producción — operar el agente de Reservo
├── Módulo 1: Por qué operar es distinto de construir
├── Módulo 2: Logging estructurado y trazado de un run
├── Módulo 3: Medir costo y tokens por run
├── Módulo 4: Medir latencia con honestidad ← ESTÁS AQUÍ
│ → qué latencia se mide (por tool y total), el problema de
│ honestidad modelado-vs-reloj-real, TOOL_LATENCY_MS como
│ dato fijo, la latencia total de un run, percentiles p50/p95
│ sobre un lote, la latencia como señal operacional
├── Módulo 5: Evals de regresión como gate de producción
├── Módulo 6: Fallos a escala — backoff, circuit breakers y rate limits
├── Módulo 7: Versionado y rollout seguro
└── Módulo 8: Proyecto — el agente de Reservo en producción
Este es el segundo y último módulo de la capa medir (Módulos 3-4), la segunda de las cuatro disciplinas de esta guía: observar → medir → gatear → endurecer+versionar. Con este módulo cerrado, las dos señales de "cuánto" —costo y latencia— quedan completas, y el Módulo 5 va a usar ambas, junto a la tasa de fallo por herramienta del Módulo 1, como los umbrales de un gate de regresión determinista.
La analogía central de este módulo: el cronómetro de práctica
Un entrenador que prepara a un equipo para una carrera de postas no espera al día de la competencia para empezar a entrenar los cambios de testigo. Usa un cronómetro de práctica, en la pista de entrenamiento, con condiciones controladas: la misma distancia, el mismo punto de entrega, sin viento variable ni pista mojada. Ese cronómetro de práctica no mide "la carrera real" —el día de la competencia habrá viento, nervios, una pista distinta—; mide algo más útil para entrenar: qué tramo del relevo es el que más tiempo consume, de forma consistente, corrida tras corrida, para que el equipo sepa dónde enfocar la práctica.
Ese es exactamente el rol de TOOL_LATENCY_MS en este módulo. No es el cronómetro real de producción —ese cronómetro existe, se llama time.perf_counter(), y se menciona en cada lección de este módulo sin usarse nunca en código—. Es el cronómetro de práctica: condiciones fijas, reproducibles, diseñadas para que puedas entrenar la pregunta que sí importa —¿qué herramienta domina el total?, ¿qué percentil resume mejor la experiencia real?— sin que el ruido de una medición real (una CPU ocupada en este instante exacto) se interponga en el aprendizaje.
El caso que sigue acompañando la guía: Reservo, sin reconstruir nada
Las cuatro tools son las mismas de siempre —list_rooms(), get_quote(room, tier, hours), book_room(room, tier, hours, member), cancel_booking(id)—, y las dos anclas de precio del Módulo 3 siguen intactas: Focus basic 3h = 7500 centavos, Focus pro 3h = 6000 centavos. Este módulo no declara ninguna tool nueva, no toca reservo_tools.py, reservo_contracts.py, reservo_robust.py ni reservo_agent.py, y tampoco toca observability/run_logger.py (Módulo 2) ni observability/cost_calculator.py (Módulo 3) — los importa, tal como quedaron, y construye al lado un artefacto nuevo: observability/latency_model.py.
- Lección 02 define con precisión qué latencia mide este módulo: la de cada tool call individual, y la latencia total de un run, como dos preguntas relacionadas pero distintas.
- Lección 03 es el corazón del módulo: el problema de honestidad modelado-vs-reloj-real, con la reproducibilidad confirmada, ejecutada, sobre el mismo run corrido dos veces.
- Lección 04 fija
TOOL_LATENCY_MScomo dato — de dónde sale cada número, por québook_roomes el más caro, y cómo se consulta por tool call individual. - Lección 05 construye
total_run_latency_ms, la suma sobre un run completo, con el matiz que ya adelantó el Módulo 1: untool_userechazado por validación nunca llega a ejecutar la tool real, así que no le agrega ni un milisegundo al total. - Lección 06 escala a un lote de doce runs reales y calcula p50 y p95 — la primera vez que esta guía usa percentiles, no solo promedios.
- Lección 07 trata la latencia como una señal operacional más, identifica qué tool domina el total de un lote, y traza la frontera con
sre-and-incident-response-guide. - Lección 08 cierra el módulo con
observability/latency_model.pycompleto, ejecutado sobre el mismo lote de doce runs, en un reporte de latencia integral.
Como en cada módulo de esta guía: identificadores y código en inglés; prosa y comentarios, en español; dinero, siempre en centavos int; latencia, siempre en milisegundos int.
Prerequisitos
Conocimiento requerido:
- ✅ Haber completado el Módulo 1 de esta guía, en especial la lección 05 (
TOOL_LATENCY_MS,estimate_run_latency_ms, calculados por primera vez sobre el run canónico de Ana). Este módulo retoma esa función y la desarrolla a fondo — no la vuelve a explicar desde cero. - ✅ Haber completado (o conocer bien)
agent-fundamentals-and-tool-calling-guideM8:run_reservo_agent, y el formato dehistory(turnosuser/assistant, bloquestool_use/tool_result/text, el campois_error). - ✅ Python: funciones, diccionarios, listas, comprensión básica de
sorted()y del módulostatistics.
Recomendado:
- ✅ Haber sentido, alguna vez, la frustración de un promedio de latencia que "se ve bien" en un dashboard mientras algunos usuarios reales siguen quejándose de que el sistema es lento — esa tensión entre el promedio y la cola es, con precisión, lo que la lección 06 de este módulo resuelve con percentiles.
NO requerido:
- ❌ No necesitas una API key ni conexión a internet: la decisión del modelo sigue siendo concepto, y toda la ingeniería de este módulo corre 100% local, con aritmética entera pura.
- ❌ No necesitas medir latencia real en ningún momento de este módulo.
time.perf_counter()se nombra, en prosa, varias veces — nunca se ejecuta. - ❌ No necesitas saber de infraestructura, balanceadores de carga, ni de cómo un proveedor de observabilidad (Datadog, New Relic) calcula sus percentiles internamente — los principios que aprendes aquí son los mismos, con cualquier herramienta.
Entorno:
- ✅ Python 3.14.0 con su librería estándar (
statistics,json,dataclasses,math). Nada que instalar. - ✅ El directorio
observability/de los Módulos 2 y 3, conrun_logger.pyycost_calculator.pyya construidos.
Roadmap del módulo
Lección 01 — Introducción al módulo (esta)
El límite exacto que deja el Módulo 3 —costo completo, ningún campo de tiempo—, la regla dura sobre time.time()/time.perf_counter(), la analogía del cronómetro de práctica, y el mapa de las ocho lecciones.
Lección 02 — Qué latencia estamos midiendo
Dos preguntas relacionadas pero distintas: la latencia de una tool call, y la latencia total de un run — y por qué ninguna de las dos vivía en history antes de este módulo.
Lección 03 — El problema de honestidad: modelada vs. reloj real
La lección central: por qué medir con el reloj real rompería la reproducibilidad de esta guía, qué se gana y qué se pierde al modelar, y la confirmación ejecutada de que el mismo run da la misma latencia, siempre.
Lección 04 — TOOL_LATENCY_MS como dato fijo
La estructura del diccionario, de dónde salen sus cuatro números, y cómo se consulta la latencia de una tool call individual.
Lección 05 — Latencia total del run
total_run_latency_ms, ejecutada sobre el run canónico de Ana, con el matiz heredado del Módulo 1: un tool_use rechazado por validación no aporta latencia, porque nunca llega a ejecutar la tool real.
Lección 06 — Percentiles: p50 y p95
El promedio esconde a los que esperan más. p50 y p95, calculados a mano y con statistics, sobre un lote real de doce runs — con la analogía del cliente número 95 de cada 100.
Lección 07 — La latencia como señal operacional
Qué tool domina el total de un lote, cómo leer esa señal, y la frontera con sre-and-incident-response-guide (latencia de infraestructura) y con el Módulo 6 de esta misma guía (qué hacer cuando una tool es lenta de forma consistente).
Lección 08 — Mini-proyecto: un reporte de latencia
observability/latency_model.py completo, ejecutado sobre el lote de doce runs de las lecciones anteriores, con un reporte integral: por tool, por run, y percentiles del lote.
Mapa de progresión
Lección 01 (esta) → El límite del Módulo 3, la regla dura, el cronómetro de práctica
Lección 02 → Latencia por tool call vs. latencia total del run
Lección 03 → Modelada vs. reloj real: el problema de honestidad
Lección 04 → TOOL_LATENCY_MS, el dato fijo
Lección 05 → total_run_latency_ms + el matiz del tool_use rechazado
Lección 06 → p50 y p95, sobre un lote real de doce runs
Lección 07 → La latencia como señal, la frontera con SRE
Lección 08 → Mini-proyecto: el reporte de latencia completo
Dificultad: ⭐⭐ ──────────────────▶ ⭐⭐⭐
Qué lograrás en este módulo
Al completar las 8 lecciones, podrás:
- Distinguir la latencia de una tool call individual de la latencia total de un run, y calcular ambas sobre datos reales.
- Explicar, con precisión, por qué esta guía modela la latencia en vez de medirla con el reloj real — y por qué esa decisión no es pereza, sino la misma disciplina de reproducibilidad que ya viste con
randomydatetime.now(). - Usar
TOOL_LATENCY_MScomo un dato de diseño fijo, citado una sola vez, reusado sin cambios en el resto de la guía. - Calcular la latencia total de un run, aplicando correctamente el matiz de que un
tool_userechazado por validación no aporta ningún milisegundo. - Calcular p50 y p95 sobre un lote de runs, a mano y con
statistics, y explicar por qué el promedio solo no basta para saber si un sistema "se siente lento". - Identificar qué tool domina la latencia total de un lote, y trazar la frontera entre esta guía (latencia de los pasos de un agente) y
sre-and-incident-response-guide(latencia de infraestructura).
El antes y después
ANTES del módulo:
→ "medir latencia es poner un cronómetro, ya sé cómo hacerlo"
→ "el promedio de latencia ya me dice si el sistema es rápido"
→ "la latencia de un run es un solo número, no tiene desglose"
→ "un tool_use que falló no debería importarle a la latencia,
¿o sí?"
DESPUÉS del módulo:
→ saber CUÁLES señales importan (qué tool domina, qué percentil
usar) es el trabajo real; poner un cronómetro es trivial
→ el promedio esconde a los usuarios que peor la pasan -- p95 es
la cifra que un negocio real necesita para prometer algo
honesto
→ la latencia total de un run es la suma de sus tool calls
REALMENTE ejecutadas, con desglose por herramienta
→ un tool_use rechazado por validación nunca llega a la tool --
y por eso nunca le agrega tiempo al run, sin importar cuántos
intentos inválidos haya en la traza
Trampas a evitar al cursar este módulo
1. "Este módulo va a usar time.perf_counter() para que el ejemplo sea más realista"
No. En ningún bloque de código ejecutado de este módulo aparece time.time() ni time.perf_counter(). Cuando una lección necesita nombrar cómo se mide latencia real, lo hace en una frase de prosa —"en producción, esto se envuelve con time.perf_counter() alrededor de cada llamada"— nunca dentro de un bloque que después se presenta como ejecutado.
2. "Si la latencia está modelada, entonces es inventada, y no vale la pena tomarla en serio"
Modelada no es lo mismo que inventada. TOOL_LATENCY_MS es un dato de diseño, tan real y tan citado como el pricing fijo de claude-sonnet-5 del Módulo 3 — la diferencia es que uno modela dinero y el otro modela tiempo, y ambos lo hacen con la misma honestidad explícita sobre qué representan y qué no.
3. "La latencia de un run es solo un número — no tiene sentido desglosarla"
Sí lo tiene, y es precisamente lo que la lección 07 de este módulo demuestra con datos reales: en un lote de doce runs, una sola tool (book_room) es responsable de más de la mitad de todos los milisegundos del lote, aunque representa apenas una cuarta parte de las llamadas totales. Sin el desglose por tool, esa señal es invisible.
4. "Con doce runs ya tengo suficiente para calcular un p95 confiable"
No, y la lección 06 lo confirma con números reales: con una muestra tan chica, el p95 casi siempre termina siendo, literalmente, el run más lento del lote — no una estimación robusta de "el 95% de los casos". Esta guía usa doce runs porque es lo que cabe en una lección, no porque sea una muestra estadísticamente sólida; la lección 06 es explícita sobre esa limitación.
5. "El Módulo 5 (evals de regresión) no necesita nada de este módulo"
Sí necesita. El gate de regresión del Módulo 5 va a comparar la latencia de una versión nueva del agente contra un umbral fijo — y ese umbral se construye, con precisión, sobre el mismo total_run_latency_ms y las mismas nociones de p50/p95 que este módulo desarrolla. Sin este módulo, ese gate no tendría ninguna cifra de latencia contra la cual comparar.
Cómo trabajar este módulo
- Corre cada ejemplo tú mismo. La promesa central de este módulo es que la latencia modelada es reproducible — confírmalo con tus propios ojos, no solo leyendo el "Qué esperar".
- No busques
time.perf_counter()en ningún bloque de código ejecutado. Si lo ves, no es de este módulo — la única vez que aparece en toda la guía es nombrado en prosa. - El mini-proyecto (lección 08) es la síntesis. Ahí vas a correr
observability/latency_model.pycompleto sobre el mismo lote de doce runs que acompaña a las lecciones 06 y 07 — la misma correlación de datos que ya viste construirse pieza por pieza.
Tiempo estimado:
Lección 01 (esta) → 20 min lectura
Lección 02 → 20 min + correr el ejemplo
Lección 03 → 25 min + correr el ejemplo
Lección 04 → 20 min + correr el ejemplo
Lección 05 → 25 min + correr el ejemplo
Lección 06 → 35 min + correr el ejemplo
Lección 07 → 25 min + correr el ejemplo
Lección 08 → 35 min + armar el mini-proyecto completo
Total: ~3.5 horas
Evidencia de éxito
Antes de avanzar al Módulo 5 (Evals de regresión como gate de producción), deberías poder:
- ✅ Explicar por qué esta guía modela la latencia en vez de medirla con el reloj real, sin dudar ni una vez sobre si
time.perf_counter()aparece en algún código ejecutado de este módulo (no aparece). - ✅ Calcular la latencia de una tool call individual y la latencia total de un run, con el matiz correcto sobre los
tool_userechazados. - ✅ Calcular p50 y p95 sobre un lote de runs, a mano y con
statistics, y explicar la diferencia entre ambos. - ✅ Identificar qué tool domina la latencia total de un lote, con evidencia numérica, no intuición.
- ✅ Trazar la frontera entre la latencia de los pasos de un agente (esta guía) y la latencia de infraestructura (
sre-and-incident-response-guide).
Resumen
- Este módulo mide la latencia del agente de Reservo, por tool call y total del run, sobre la misma base del Módulo 1 —
TOOL_LATENCY_MS— desarrollada a fondo. - Regla dura, la más estricta de la guía: ningún bloque de código ejecutado usa
time.time()nitime.perf_counter(); ambos se nombran, solo en prosa, como la forma real de medir en producción. - La analogía central es el cronómetro de práctica: condiciones fijas y reproducibles, diseñadas para entrenar qué señales importan, no para replicar la carrera real.
- Las piezas del módulo: qué latencia se mide (L02), el problema de honestidad (L03),
TOOL_LATENCY_MScomo dato (L04), la suma total con su matiz (L05), percentiles p50/p95 (L06), la latencia como señal operacional (L07), y el reporte completo del mini-proyecto (L08).
Siguiente lección: 02 — Qué latencia estamos midiendo. Antes de escribir ninguna fórmula, distinguimos con precisión dos preguntas que suenan parecidas pero no lo son: ¿cuánto tardó esta tool call?, y ¿cuánto tardó el run completo?
Recursos adicionales
- Anthropic — Building effective agents — Sobre por qué la latencia percibida de un sistema agentic depende de cuántos pasos —y cuáles— necesitó dar, no solo de qué tan rápido responde el modelo.
- Python —
statistics—statistics.medianystatistics.quantiles, el núcleo de la lección 06 de este módulo. - Python —
time— La referencia detime.perf_counter(), nombrada en varias lecciones de este módulo y nunca ejecutada en su código. - Python 3.14 — What's New — La versión con la que se ejecuta toda la ingeniería de este módulo.