Módulo 1: Por qué operar es distinto de construir
Un primer vistazo a costo, latencia y errores
Descripción
Las dos primeras señales de la lección 04 —tasa de error, tasa de fallo por herramienta— se calculan contando lo que ya está en history. Las dos que faltan —costo por run y latencia por run— son distintas: no están en history en ningún formato, así que hay que estimarlas (el costo) o modelarlas (la latencia) con una capa de ingeniería nueva. Esta lección construye esa capa, mínima pero real, y la corre por primera vez sobre el run canónico de Reservo.
No es el desarrollo a fondo de ninguna de las dos cosas —eso son los Módulos 3 y 4 de esta guía, con desglose por tool call, agregación sobre lotes grandes, y percentiles—. Es, literalmente, lo que dice el título: un primer vistazo, honesto sobre sus límites, que deja las cuatro señales del módulo completas por primera vez.
Conexión con el módulo
Con esta lección, las cuatro señales de la lección 04 —tasa de error, tasa de fallo por herramienta, costo, latencia— quedan calculadas, todas, al menos una vez, sobre un run real. Eso es exactamente lo que la lección 08 (el mini-proyecto) va a envolver en una sola función reusable.
Analogía: las dos agujas que faltaban en el tablero
La lección 03 dejó el tablero con dos agujas vacías: cuánta gasolina consumiste y qué tan caliente corrió el motor. Esta lección las llena. El indicador de gasolina es el costo: cuánto "combustible" —tokens, y su equivalente en centavos— consumió este viaje en particular. El indicador de temperatura es la latencia: qué tan exigido estuvo el motor —cuánto tiempo se llevó cada componente— en el camino. Ninguna de las dos agujas te dice si el viaje fue bueno o malo por sí sola; igual que en un auto, importan en conjunto con las otras dos, y sobre todo importan cuando las miras a lo largo de muchos viajes, no de uno solo.
Ejemplo trabajado: costo y latencia, calculados por primera vez
Costo: la fórmula fija de claude-sonnet-5
El precio de lista de claude-sonnet-5, verificado en la documentación oficial de Claude, es $3.00 por cada millón de tokens de entrada y $15.00 por cada millón de tokens de salida. Como constante fija, en centavos:
INPUT_PRICE_CENTS_PER_MILLION_TOKENS = 300 # $3.00 / 1M tokens
OUTPUT_PRICE_CENTS_PER_MILLION_TOKENS = 1500 # $15.00 / 1M tokens
def estimate_cost_cents(input_tokens, output_tokens):
return (
input_tokens * INPUT_PRICE_CENTS_PER_MILLION_TOKENS
+ output_tokens * OUTPUT_PRICE_CENTS_PER_MILLION_TOKENS
) // 1_000_000
Para llegar a input_tokens/output_tokens hace falta una estimación de texto, con la misma convención honesta que agent-fundamentals estableció desde su Módulo 6: len(texto) // 4, un orden de magnitud, nunca un conteo exacto de un tokenizer real.
import json
def estimate_run_tokens(history):
"""Primera mirada, orden de magnitud (len//4): suma lo que entra al
modelo como INPUT (la pregunta + cada tool_result) y lo que el modelo
genera como OUTPUT (cada tool_use + el texto final)."""
input_chars = 0
output_chars = 0
for turn in history:
content = turn["content"]
if isinstance(content, str):
input_chars += len(content)
continue
for block in content:
if block["type"] == "tool_result":
input_chars += len(block["content"])
elif block["type"] == "tool_use":
output_chars += len(json.dumps(block["input"]))
elif block["type"] == "text":
output_chars += len(block["text"])
return input_chars // 4, output_chars // 4
Fíjate en la clasificación: la pregunta original y cada tool_result son texto que el modelo lee —input—; cada tool_use (lo que el modelo decide pedir) y el texto final son lo que el modelo genera —output—. Esta es una simplificación deliberada para un primer vistazo: no tiene en cuenta que, en una API real, cada turno reenvía el historial completo de la conversación como parte del input de esa llamada —el desglose exacto, turno por turno, es trabajo del Módulo 3—. Aquí, suma el texto que fluyó en cada dirección a lo largo de todo el run, una sola vez.
Latencia: modelada, con una regla honesta sobre cuándo se cuenta
TOOL_LATENCY_MS = {
"list_rooms": 40,
"get_quote": 25,
"book_room": 120,
"cancel_booking": 90,
}
def estimate_run_latency_ms(history):
"""Suma la latencia modelada de cada tool que se EJECUTÓ de verdad.
Un tool_use rechazado por validación (is_error, sin ejecutar la función
real) no le agrega latencia al run -- nunca llegó a la tool."""
total_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"])
total_ms += TOOL_LATENCY_MS.get(name, 0)
return total_ms
TOOL_LATENCY_MS es un dato fijo, no una medición — la honestidad explícita que esta guía sostiene desde su DISEÑO: en producción de verdad, esto se mide con el reloj real (time.perf_counter() alrededor de cada execute_tool); aquí se modela para que el ejemplo sea reproducible byte a byte en tu máquina. La decisión de diseño que sí vale la pena notar es la condición not block.get("is_error"): un tool_use que check_input_v2 rechaza por validación —como el tier="premium" de la demo— nunca ejecuta la función real de Python, así que no tiene sentido cargarle la latencia modelada de esa tool. Solo las tool calls que de verdad se ejecutaron —incluidas las que después resultan en un is_error de negocio, como un cancel_booking que no encuentra la reserva— aportan latencia al total.
Todo junto, sobre el run canónico
import reservo_agent as ra
model_script_demo = [
{"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."}]},
]
final, history = ra.run_reservo_agent("Reserva Focus pro 3h para Ana", model_script_demo)
in_tok, out_tok = estimate_run_tokens(history)
cost_cents = estimate_cost_cents(in_tok, out_tok)
latency_ms = estimate_run_latency_ms(history)
tool_calls = sum(1 for m in history if m["role"] == "assistant" and isinstance(m["content"], list)
and m["content"][0]["type"] == "tool_use")
tool_errors = sum(1 for m in history if m["role"] == "user" and isinstance(m["content"], list)
and any(b.get("is_error") for b in m["content"]))
print("RESPUESTA :", final["content"][0]["text"])
print(f"tokens estimados : input={in_tok} output={out_tok} (len//4, orden de magnitud)")
print(f"costo estimado : {cost_cents} centavos")
print(f"latencia modelada : {latency_ms} ms")
print(f"tool calls : {tool_calls} (errores: {tool_errors})")
Qué esperar:
RESPUESTA : Reservé Focus pro por 3 horas para Ana. Total $60.00. Confirmación #1.
tokens estimados : input=64 output=56 (len//4, orden de magnitud)
costo estimado : 0 centavos
latencia modelada : 185 ms
tool calls : 4 (errores: 1)
Las cuatro señales de la lección 04, juntas, por primera vez: 0 errores a nivel de run (completó), 1 de 4 tool calls con error (25%), 0 centavos de costo, 185 milisegundos de latencia.
Por qué el costo salió 0, y por qué eso es correcto
0 centavos no es un error del cálculo — es la respuesta honesta a una pregunta real: ¿cuánto cuesta, con el precio de lista de claude-sonnet-5, un intercambio de texto de apenas 64 tokens de entrada y 56 de salida? Haz la cuenta a mano: (64 * 300 + 56 * 1500) // 1_000_000 = (19200 + 84000) // 1_000_000 = 103200 // 1_000_000 = 0. El costo real, sin redondear a un entero de centavos, sería del orden de $0.001 — una décima de centavo. Con dinero representado en centavos int (la convención dura de esta guía y de toda la familia de guías de Reservo), ese valor se redondea hacia abajo a 0.
Esto no significa que el costo no importe — significa que un solo run simple, con este pricing, cuesta una fracción de centavo, y que la señal solo se vuelve visible en agregado. Multiplica el mismo patrón de tokens por volumen, sin ejecutar ningún run adicional —es aritmética simple, no una corrida real—:
for n in [1, 100, 10_000]:
print(n, "runs similares ~", estimate_cost_cents(in_tok * n, out_tok * n), "centavos")
Qué esperar:
1 runs similares ~ 0 centavos
100 runs similares ~ 10 centavos
10000 runs similares ~ 1032 centavos
Diez mil runs como el de Ana costarían, en total, unos $10.32 — un número que empieza a significar algo para una decisión de negocio. Esta es exactamente la razón por la que el Módulo 3 de esta guía agrega el costo sobre lotes reales de runs, no sobre uno solo: la señal existe, pero solo se vuelve útil en la escala en la que de verdad importa.
Por qué la latencia se mantiene igual aunque el número de pasos cambie
Fíjate en algo que vale la pena confirmar con tus propios ojos antes de seguir: si ejecutaras el Run C de la lección 04 —el que tiene cinco tool calls y dos errores, en vez de cuatro tool calls y uno— la latencia modelada de ese run también da 185 milisegundos, exactamente la misma que la del Run A. No es una coincidencia del código: es la consecuencia directa de la regla que definiste arriba. Ambos runs, sin importar cuántos intentos rechazados por validación tuvieron en el camino, terminan ejecutando exactamente la misma secuencia de tools reales — list_rooms, un get_quote válido, book_room — y esa secuencia real es la que determina la latencia. Un tier inválido o un hours=0 cuestan pasos en la traza y turnos en el historial, pero no cuestan tiempo real, porque check_input_v2 los detiene antes de que la tool se ejecute. Esta es una de las razones por las que la tasa de fallo por herramienta y la latencia son señales independientes: una tasa de fallo alta no necesariamente implica un run más lento.
Errores comunes
-
Ver
0 centavosy asumir que el cálculo está mal. No lo está — es la respuesta correcta para un run de este tamaño con este pricing. El error real sería no verificarlo a mano (como hizo la sección anterior) antes de descartarlo como un bug. -
Cargar latencia a un
tool_useque nunca se ejecutó. Siestimate_run_latency_mssumara la latencia de cadatool_usesin chequearis_erroren sutool_result, eltier="premium"rechazado le agregaría25ms de más al total — tiempo que, en la realidad, nunca ocurrió, porque la validación decheck_input_v2es una función de Python local, no una llamada que tarda. -
Confundir la estimación de tokens de esta lección con un conteo exacto.
len(texto) // 4es, por diseño, un orden de magnitud — útil para tener una cifra de costo aproximada, inútil como fuente de verdad para una factura real. Cualquier decisión de negocio que dependa de un número exacto de tokens necesita el conteo real de un tokenizer, no esta estimación. -
Multiplicar por volumen y pensar que eso "ya es" la agregación del Módulo 3. La sección de escalamiento de esta lección es aritmética simple sobre un solo patrón repetido
nveces — no es lo mismo que sumar el costo denruns distintos, con tokens distintos cada uno, que es lo que el Módulo 3 hace de verdad sobre un lote real. -
Olvidar rotular el pricing como "precio de lista". Existió, y puede volver a existir, un precio promocional distinto del precio de lista — cualquier cifra de costo que compartas fuera de esta guía debería aclarar contra qué precio se calculó, para que no se confunda con una cotización real de facturación.
Ejercicios
Ejercicio 1: Calcula costo y latencia del Run B (Fácil)
Usando estimate_run_tokens, estimate_cost_cents y estimate_run_latency_ms, calcula las cuatro señales del guion de Sofía —Boardroom, pro, 1 hora, sin ningún error en el camino— de la lección 04.
Ver solución
script_b = [
{"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 #2."}]},
]
final_b, history_b = ra.run_reservo_agent("Reserva Boardroom pro 1h para Sofía", script_b)
in_b, out_b = estimate_run_tokens(history_b)
print(f"tokens: input={in_b} output={out_b}")
print("costo :", estimate_cost_cents(in_b, out_b), "centavos")
print("latencia:", estimate_run_latency_ms(history_b), "ms")
Salida esperada:
tokens: input=53 output=49
costo : 0 centavos
latencia: 185 ms
Explicación: 185 ms es idéntico al Run A —list_rooms (40) + get_quote (25) + book_room (120) = 185—, porque este guion, sin ningún tropiezo, ejecuta exactamente la misma secuencia de tools reales que el Run A ejecutó después de corregir su error. El costo, con menos texto que el Run A (sin el intento rechazado ni su mensaje de error), sigue redondeando a 0 centavos.
Ejercicio 2: ¿Cuántos runs como el de Sofía hacen falta para gastar un dólar? (Medio)
Usando el resultado del Ejercicio 1, calcula cuántos runs idénticos al de Sofía hacen falta para que el costo total supere los 100 centavos ($1.00). No ejecutes runs reales — usa la aritmética de escalamiento de la sección "Por qué el costo salió 0".
Ver solución
in_b, out_b = 53, 49
for n in [1_000, 5_000, 10_000, 20_000]:
print(n, "runs ->", estimate_cost_cents(in_b * n, out_b * n), "centavos")
Salida esperada:
1000 runs -> 89 centavos
5000 runs -> 447 centavos
10000 runs -> 894 centavos
20000 runs -> 1788 centavos
Explicación: entre 1.000 y 5.000 runs se cruza el umbral de 100 centavos — más precisamente, hace falta un número entre esos dos para llegar exactamente a un dólar. La estimación por lote (in_b * n, out_b * n) es válida aquí porque asume runs idénticos en tamaño de texto; en la realidad, cada run tiene una pregunta y un guion distintos, así que el Módulo 3 suma el costo real de cada run del lote por separado, en vez de multiplicar uno solo por n — la diferencia entre esta aproximación y la agregación real.
Ejercicio 3: Diseña un guion que maximice la latencia modelada sin agregar ningún error (Difícil)
Con las cuatro tools y sus latencias (list_rooms=40, get_quote=25, book_room=120, cancel_booking=90), diseña un guion de turnos —válido, sin ningún is_error— que resulte en la latencia modelada más alta posible usando cada una de las cuatro tools exactamente una vez. Calcula la latencia esperada antes de ejecutar, y confirma.
Ver solución
La latencia modelada no depende del orden en que se llamen las tools —es una suma, y la suma no cambia con el orden—, así que cualquier guion válido que use las cuatro exactamente una vez da el mismo total: 40 + 25 + 120 + 90 = 275 ms.
script_all_four = [
{"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": "Studio", "tier": "basic", "hours": 1}}]},
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_03", "name": "book_room",
"input": {"room": "Studio", "tier": "basic", "hours": 1, "member": "Nico"}}]},
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_04", "name": "cancel_booking", "input": {"id": 3}}]},
{"stop_reason": "end_turn", "content": [
{"type": "text", "text": "Reservé y cancelé Studio basic 1h para Nico."}]},
]
final_4, history_4 = ra.run_reservo_agent("Reserva y cancela Studio basic 1h para Nico", script_all_four)
print("latencia:", estimate_run_latency_ms(history_4), "ms")
Salida esperada (continuando el proceso de esta misma lección, donde el ejemplo trabajado ya reservó id=1 para Ana y el Ejercicio 1 ya reservó id=2 para Sofía, así que esta reserva real recibe id=3):
latencia: 275 ms
Explicación: book_room (120 ms) es, con diferencia, la tool más cara de las cuatro — más del doble que la siguiente (cancel_booking, 90 ms), y casi cinco veces get_quote (25 ms). En un sistema real, esto tendría sentido: crear una reserva probablemente implica escribir a una base de datos, mientras que cotizar es un cálculo en memoria. Identificar qué tool domina el total de latencia —aquí, book_room, sin ninguna ambigüedad— es exactamente el tipo de lectura que el Módulo 4 de esta guía desarrolla a fondo, con percentiles sobre lotes grandes en vez de una suma sobre cuatro tools.
Resumen y siguiente paso
- Construimos, por primera vez en esta guía, un estimador de costo (
len(texto)//4+ el precio de lista declaude-sonnet-5, $3.00/$15.00 por millón de tokens) y un modelo de latencia (TOOL_LATENCY_MS, sumado solo sobre tool calls que se ejecutaron de verdad). - Ejecutamos ambos sobre el run canónico de Ana:
0centavos,185ms — y confirmamos, con la cuenta a mano, por qué0centavos es la respuesta correcta para un run de ese tamaño, no un error. - Descubrimos, ejecutado, que la latencia modelada depende de la secuencia de tools que realmente se ejecutaron, no del número de intentos en la traza — un
tierinválido no le agrega tiempo al run, porque nunca llega a la tool. - Con esto, las cuatro señales de la lección 04 quedan calculadas, todas, sobre el mismo run — la base exacta que la lección 08 va a envolver en una sola función.
Siguiente lección: 06 — Operar vs. construir: la frontera. Con las cuatro señales ya en la mano, trazamos con precisión dónde termina lo que agent-fundamentals ya construyó y dónde empieza esta guía — y dónde termina esta guía (el agente) y empieza sre-and-incident-response-guide (la infraestructura).
Recursos adicionales
- Anthropic — Pricing — La fuente oficial del precio de lista de
claude-sonnet-5($3.00/$15.00 por millón de tokens) que esta lección fija como constante. - Anthropic — Token counting — El conteo exacto de tokens de un tokenizer real, frente a la estimación de orden de magnitud (
len//4) que usa esta lección. - Python —
json—json.dumps, usado para estimar el tamaño en texto de cadatool_use. - Python 3.14 — What's New — La versión con la que se ejecutó cada cálculo real de esta lección.