Módulo 4: Medir latencia con honestidad
El problema de honestidad: modelada vs. reloj real
Descripción
Esta es la lección que justifica, con precisión, la regla dura que la lección 01 ya adelantó: por qué esta guía —y este módulo en particular— nunca usa time.time() ni time.perf_counter() en ningún bloque de código que después se presenta como ejecutado. No es una limitación técnica —nada le impediría a esta guía envolver cada llamada con un cronómetro real—; es una decisión deliberada, con una razón concreta y verificable: un cronómetro real rompería la promesa central de esta guía, que dice, en cada "Qué esperar" de las 64 lecciones que la componen, "esto es lo que vas a obtener si corres este código en tu propia máquina".
Conexión con el módulo
Las lecciones 02 y 04 de este módulo usan TOOL_LATENCY_MS como si fuera obvio que un dato fijo es la elección correcta. Esta lección es la que justifica esa elección, con el problema expuesto en detalle y con una confirmación ejecutada de que la latencia modelada es, de verdad, reproducible — la propiedad exacta que un cronómetro real no podría ofrecer.
Analogía: la báscula de la panadería, no el reloj de la cocina
Una panadería que necesita estandarizar sus recetas no usa un cronómetro para decidir "cuánto tarda" hacer un pan — usa una báscula, y declara la receta en gramos: 500g de harina, 10g de sal, 7g de levadura. Esos números son fijos, están escritos en la receta, y son los mismos hoy, mañana, y dentro de un año — no dependen de qué tan rápido amasa el panadero de turno, ni de si el horno de hoy está un poco más caliente que el de ayer. Si la panadería midiera sus recetas con un cronómetro en vez de una báscula —"amasa durante el tiempo que amasó Juan la semana pasada"— cada lote saldría distinto, porque el tiempo que tarda un humano en amasar depende de factores que la receta no puede controlar.
TOOL_LATENCY_MS es la báscula de esta guía. No mide "cuánto tardó en tu máquina, en este instante" —eso sería el cronómetro, y varía—; declara, como una receta, cuánto "pesa" en tiempo cada tool, de forma fija. La lección 04 va a mostrar los cuatro números exactos; esta lección se queda en el problema de fondo: por qué una báscula (un dato fijo) es la herramienta correcta para esta guía, y un cronómetro (el reloj real) no lo es.
El problema, con precisión: qué pasaría si esta guía usara el reloj real
Imagina, por un momento, que la función que suma la latencia de un run no consultara TOOL_LATENCY_MS, sino que envolviera cada llamada a una tool con time.perf_counter(), así (esto es un ejemplo ilustrativo, para razonar sobre el problema — ningún bloque de código de este módulo hace esto de verdad):
En prosa, no en código ejecutado: la idea sería marcar un instante justo antes de llamar a la función real de la tool (
start = time.perf_counter()), llamarla, marcar otro instante justo después (end = time.perf_counter()), y restar los dos para obtener cuánto tardó esa llamada específica, en milisegundos.
Con esa idea (nunca implementada en esta guía), tres corridas del mismo guion, en la misma máquina, un minuto después una de la otra, podrían dar algo parecido a esto:
CONCEPTO ILUSTRATIVO -- no es salida real de ningun codigo de esta guia:
corrida 1: list_rooms=1.8ms get_quote=0.3ms book_room=2.1ms total=4.2ms
corrida 2: list_rooms=3.1ms get_quote=0.4ms book_room=1.9ms total=5.4ms
corrida 3: list_rooms=1.2ms get_quote=0.9ms book_room=6.7ms total=8.8ms
Tres números de "latencia total" distintos, para exactamente el mismo guion, en la misma máquina. Ninguno de los tres es "el número correcto" — cada uno es una fotografía honesta de cuánto tardó esa corrida específica, en ese instante específico, compitiendo por CPU con lo que sea que tu sistema operativo estuviera haciendo en ese momento. Eso es exactamente lo que hace que un cronómetro real sea correcto para producción —ahí, sí quieres saber cuánto tardó de verdad cada corrida, con toda su variabilidad— y incorrecto para el "Qué esperar" de una lección —aquí, necesitas que el número que ves en esta página sea el mismo número que vas a ver en tu terminal, sin excepción.
Lo que se gana al modelar, confirmado con código real
TOOL_LATENCY_MS, en cambio, no varía nunca — porque no mide nada, declara algo. Confírmalo corriendo exactamente el mismo run dos veces, en el mismo proceso:
import reservo_agent as ra
TOOL_LATENCY_MS = {
"list_rooms": 40,
"get_quote": 25,
"book_room": 120,
"cancel_booking": 90,
}
def total_run_latency_ms(history):
latency_ms = 0
tool_use_name = {}
for turn in history:
if turn["role"] != "assistant" or not isinstance(turn["content"], list):
continue
for block in turn["content"]:
if block["type"] == "tool_use":
tool_use_name[block["id"]] = block["name"]
for turn in history:
if turn["role"] != "user" or not isinstance(turn["content"], list):
continue
for block in turn["content"]:
if block["type"] == "tool_result" and not block.get("is_error"):
name = tool_use_name.get(block["tool_use_id"])
latency_ms += TOOL_LATENCY_MS.get(name, 0)
return latency_ms
script_sofia = [
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_01", "name": "list_rooms", "input": {}}]},
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_02", "name": "get_quote",
"input": {"room": "Boardroom", "tier": "pro", "hours": 1}}]},
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_03", "name": "book_room",
"input": {"room": "Boardroom", "tier": "pro", "hours": 1, "member": "Sofía"}}]},
{"stop_reason": "end_turn", "content": [
{"type": "text", "text": "Reservé Boardroom pro por 1 hora para Sofía. Total $64.00. Confirmación #1."}]},
]
for intento in range(1, 4):
final, history = ra.run_reservo_agent("Reserva Boardroom pro 1h para Sofía", script_sofia)
latency_ms = total_run_latency_ms(history)
print(f"intento {intento}: latencia total = {latency_ms} ms")
Qué esperar:
intento 1: latencia total = 185 ms
intento 2: latencia total = 185 ms
intento 3: latencia total = 185 ms
185, 185, 185 — exactamente el mismo número, tres veces, sin ninguna variación, aunque cada intento vuelve a llamar a book_room de verdad y crea una reserva nueva (con un booking_id distinto cada vez, porque el estado de BOOKINGS sí avanza entre llamadas). El booking_id cambia; la latencia modelada, no — porque TOOL_LATENCY_MS no depende de qué booking_id resultó, solo de qué tools se llamaron. Compáralo con los tres números ilustrativos de la sección anterior (4.2ms, 5.4ms, 8.8ms), que nunca coincidían entre sí. Esa diferencia —cero variación contra variación real— es, en una palabra, el problema de honestidad que esta lección resuelve: la latencia modelada es reproducible por diseño, porque es un dato declarado, no una medición.
Qué se pierde al modelar, dicho con la misma honestidad
Sería deshonesto presentar esto como una decisión sin costo. TOOL_LATENCY_MS modela cada tool con un solo número fijo, y eso oculta, a propósito, varias cosas que sí importan en un sistema real:
- La variabilidad de una misma tool, corrida tras corrida. En producción,
book_roomno siempre tarda exactamente lo mismo — puede depender de la carga de la base de datos detrás, de si hay contención con otra escritura simultánea, de la latencia de red hacia ese servicio.TOOL_LATENCY_MS["book_room"] = 120es un solo punto; la realidad es una distribución de valores alrededor de ese punto (y a veces lejos de él). - Los picos de cola (tail latency). Una tool que normalmente tarda
120ms puede, ocasionalmente, tardar2.000ms —por ungarbage collectorque pausa el proceso, por una conexión de red que se cae y se reintenta—. Esos picos son, con frecuencia, la parte más importante de la latencia real de un sistema, y un modelo de números fijos, por definición, no puede representarlos. - La dependencia del entorno. La misma tool, corriendo en un servidor con más carga o en una región geográfica distinta, puede tener una latencia base completamente diferente.
TOOL_LATENCY_MSasume un solo entorno, siempre.
Nada de esto invalida el modelo — lo contextualiza. El propósito de este módulo no es enseñarte cuánto tarda book_room en un sistema real (esa pregunta solo la responde el reloj real, en tu propio sistema real); es enseñarte qué hacer con una medición de latencia una vez que la tienes: cómo sumarla por run, cómo leer un percentil, cómo identificar qué tool domina. Esas habilidades son exactamente las mismas, se apliquen sobre datos modelados o sobre datos medidos de verdad.
Errores comunes
-
Pensar que "modelado" significa "no importa el número exacto". Sí importa —
TOOL_LATENCY_MSes una constante fija y citada, igual que el pricing declaude-sonnet-5del Módulo 3. Cambiarla a mitad de una lección, o inventar un valor nuevo para una tool que no está en el diccionario, rompe la reproducibilidad que esta lección demostró. -
Agregar
time.perf_counter()"solo para comparar" dentro de un ejercicio. Es la trampa más fácil de caer en este módulo — parece inofensivo, "solo para ver cuánto tarda de verdad en mi máquina". El problema es que, en el momento en que lo haces, el "Qué esperar" de tu ejercicio deja de ser reproducible para cualquier otra persona que lo corra en una máquina distinta. -
Creer que el ejemplo de la sección "Qué pasaría" es código real de esta guía. No lo es — está marcado explícitamente como
CONCEPTO ILUSTRATIVO, en un bloque de texto, no en un bloque de Python ejecutable. Ningún bloque de código de esta lección —ni de ninguna otra de este módulo— usatime.time()nitime.perf_counter(). -
Pensar que un modelo con un solo número fijo por tool es "más simple" y por eso "menos preciso" que medir con el reloj real. Es menos preciso, sí —la sección anterior lo dice sin rodeos—, pero eso es exactamente el punto: sacrifica precisión de un solo run a cambio de reproducibilidad de la lección completa. Un sistema real necesita las dos cosas —el reloj real para operar, el modelo fijo para enseñar y para el gate de regresión del Módulo 5— y confundir cuál corresponde a cuál contexto es el error de fondo que esta lección busca prevenir.
-
Subestimar la cola (tail latency) porque el modelo de esta guía no la representa. Es tentador, después de trabajar con
TOOL_LATENCY_MS, olvidar que la variabilidad real existe y que los picos ocasionales son, con frecuencia, lo que un cliente real experimenta como "el sistema se sintió lento". La lección 06 de este módulo, sobre percentiles, existe precisamente para que ese hábito de pensamiento —mirar más allá del promedio— se te quede grabado, aunque los datos de este módulo sean fijos.
Ejercicios
Ejercicio 1: Confirma la reproducibilidad sobre un guion distinto (Fácil)
Corre tres veces el guion de Ana (list_rooms, get_quote con tier="premium" rechazado, get_quote con tier="pro", book_room) del Módulo 1, y confirma que total_run_latency_ms da exactamente el mismo número las tres veces.
Ver solución
script_ana = [
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_01", "name": "list_rooms", "input": {}}]},
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_02", "name": "get_quote",
"input": {"room": "Focus", "tier": "premium", "hours": 3}}]},
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_03", "name": "get_quote",
"input": {"room": "Focus", "tier": "pro", "hours": 3}}]},
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_04", "name": "book_room",
"input": {"room": "Focus", "tier": "pro", "hours": 3, "member": "Ana"}}]},
{"stop_reason": "end_turn", "content": [
{"type": "text", "text": "Reservé Focus pro por 3 horas para Ana. Total $60.00. Confirmación #1."}]},
]
for intento in range(1, 4):
final, history = ra.run_reservo_agent("Reserva Focus pro 3h para Ana", script_ana)
print(f"intento {intento}: latencia total = {total_run_latency_ms(history)} ms")
Salida esperada:
intento 1: latencia total = 185 ms
intento 2: latencia total = 185 ms
intento 3: latencia total = 185 ms
Explicación: los tres intentos dan 185, la misma cifra del Módulo 1 —a pesar de que este guion, a diferencia del de Sofía, sí tiene un tool_use rechazado en el camino (tier="premium")—. La reproducibilidad no depende de que el guion sea "simple" o "sin errores" — depende, únicamente, de que TOOL_LATENCY_MS es un dato fijo, sin importar cuántas veces se ejecute el mismo run.
Ejercicio 2: Redacta, en una frase, la diferencia entre "modelado" y "estimado" (Medio)
El Módulo 3 usa la palabra estimado para el costo (len(texto) // 4 es una aproximación de un conteo real de tokens que existe, aunque esta guía no lo use). Este módulo usa la palabra modelado para la latencia. Explica, en una frase, la diferencia entre ambos términos, usando los dos casos concretos de esta guía.
Ver solución
Estimado significa que existe un valor real y exacto —el conteo verdadero de tokens de un tokenizer real— y la guía calcula una aproximación deliberada de ese valor (len(texto) // 4), rotulada siempre como orden de magnitud. Modelado significa que no hay un intento de aproximar ningún valor medido en esta corrida específica — TOOL_LATENCY_MS no es una aproximación de "cuánto tardó realmente esta llamada a book_room en esta ejecución"; es un dato de diseño, fijo por decisión, que nunca varía sin importar cuántas veces corra el mismo código. La diferencia de fondo: una estimación se acerca (con más o menos error) a un número real que existe en algún lado; un modelo declara un número que reemplaza deliberadamente a la medición real, para otro propósito (reproducibilidad, foco en el análisis) que no es "acercarse a la verdad".
Ejercicio 3: Diseña, en prosa, cómo cambiaría el "Qué esperar" de esta guía si usara el reloj real (Difícil)
Sin escribir código —el punto de este ejercicio es razonar, no implementar—: si esta guía decidiera, desde este módulo en adelante, medir latencia con time.perf_counter() real, describe en un párrafo cómo tendría que cambiar el formato de cada bloque "Qué esperar" de las lecciones que siguen para seguir siendo honesto con el lector, dado que el número exacto ya no sería reproducible.
Ver solución
Cada "Qué esperar" tendría que dejar de mostrar un número exacto (185 ms) y, en su lugar, mostrar un rango razonable ("entre 2 y 15 ms, según la carga de tu máquina") o, más honesto todavía, instruir al lector a ignorar el valor absoluto y fijarse solo en la forma relativa del resultado ("book_room va a ser, de forma consistente, la tool más lenta de las cuatro — el número exacto en milisegundos va a variar cada vez que corras este código"). Cualquiera de las dos opciones es sustancialmente menos útil como material de aprendizaje que un número exacto y reproducible: la primera obliga al lector a "confiar" en que su resultado cae dentro del rango sin poder confirmarlo con precisión; la segunda renuncia por completo a poder citar una cifra concreta en los ejercicios, en las comparaciones de percentiles, o en el gate de regresión del Módulo 5 — que sí necesita un umbral numérico exacto contra el cual comparar. Esta es, en el fondo, la razón completa por la que esta guía eligió modelar: no porque medir con el reloj real sea difícil de programar, sino porque un material educativo reproducible necesita números que no cambien entre una lectura y la siguiente.
Resumen y siguiente paso
- Confirmamos, con un ejemplo ilustrativo (nunca ejecutado), que medir latencia con el reloj real da un número distinto en cada corrida — la variabilidad es información real en producción, pero rompe la reproducibilidad de una lección.
- Confirmamos, ejecutado tres veces sobre el mismo guion, que
total_run_latency_msconTOOL_LATENCY_MSda exactamente el mismo resultado siempre —185ms, sin ninguna variación, en los tres intentos. - Nombramos, con honestidad, lo que se pierde al modelar: la variabilidad de una tool corrida a corrida, los picos de cola, la dependencia del entorno — ninguno de los tres está representado en un modelo de números fijos, y esta guía lo dice explícitamente en vez de ocultarlo.
- Con este problema resuelto, el resto del módulo puede construir sobre
TOOL_LATENCY_MSsin volver a justificar por qué es la elección correcta para esta guía.
Siguiente lección: 04 — TOOL_LATENCY_MS como dato fijo. Con el problema de honestidad ya resuelto, formalizamos el diccionario que vas a reusar sin cambios hasta el cierre del Módulo 8: de dónde salen sus cuatro números, y por qué book_room es, con diferencia, el más caro.
Recursos adicionales
- Python —
time— La referencia oficial detime.perf_counter(), la función que esta lección nombra en detalle y nunca ejecuta. - Anthropic — Building effective agents — Sobre por qué la latencia de un sistema agentic real varía según el entorno, la carga y la tool específica que se llama.
- Python — determinismo y pruebas reproducibles — La documentación de
random, útil por contraste: es exactamente el tipo de fuente no determinista que esta guía prohíbe en cualquier dato, por la misma razón que prohíbe el reloj real. - Python 3.14 — What's New — La versión con la que se ejecutó, tres veces con el mismo resultado, cada bloque de código de esta lección.