Módulo 1: Por qué operar es distinto de construir
El agente ya funciona en un notebook, ¿y ahora qué?
Descripción
agent-fundamentals M8 probó el agente de Reservo contra un guion: cinco turnos, escritos a mano, ensayados hasta que produjeron exactamente la traza que esa guía quería mostrar. Eso no es un defecto de esa guía — es, precisamente, cómo se prueba un sistema mientras se construye: con un caso conocido, controlado, que tú mismo diseñaste para ejercitar una capacidad específica (en ese caso, la auto-corrección frente a un tier inválido). Es la forma correcta de confirmar que el mecanismo funciona.
Pero hay una diferencia enorme entre "funciona contra el guion que yo escribí" y "funciona con lo que sea que un usuario real le pida". Esta lección no cambia ni una línea de run_reservo_agent — el agente sigue siendo exactamente el mismo, y sigue funcionando exactamente igual de bien. Lo que cambia es el contexto en el que corre, y ese cambio de contexto es, con precisión, el problema que esta guía completa existe para resolver.
Conexión con el módulo
La lección 01 mostró que una sola línea de respuesta no responde cinco preguntas operacionales básicas. Esta lección se detiene un paso antes: por qué esas preguntas empiezan a importar justo quiere en el momento en que el agente deja el notebook. En el notebook, tú escribiste el guion, tú corriste el código, tú leíste la salida con tus propios ojos. En operación, ninguna de esas tres cosas es cierta.
Analogía: la noche de apertura, y todas las noches que siguen
agent-fundamentals M8 comparó su capstone con la noche de apertura de un restaurante: la cocina completa, sirviendo por primera vez a un cliente real, con un pedido de varios pasos que incluía un error genuino en el medio. Esa comparación fue exacta para lo que esa guía entregaba — pero fíjate en un detalle que quedó implícito: fue una noche, con un pedido, y el dueño del restaurante estaba parado justo ahí, mirando cada plato salir de la cocina.
Esta guía empieza el día después de la apertura. El restaurante ya no tiene una sola noche de prueba — tiene que abrir todos los días, servir a desconocidos que piden lo que se les ocurre, sin que el dueño esté necesariamente ahí para ver cada plato. La cocina —el agente— no cambió: sigue siendo la misma, sigue cocinando igual de bien. Lo que cambió es que ya nadie puede confiar en "yo estuve ahí y lo vi funcionar" como evidencia de que sigue funcionando. Esa es la línea exacta donde termina construir y empieza operar.
Ejemplo trabajado: la misma función, tres pedidos que nadie guionó de antemano
run_reservo_agent no sabe, ni le importa, si lo llamas una vez en un notebook o mil veces desde un servicio real. Compruébalo: llámalo tres veces seguidas, con tres tareas distintas, ninguna repetida del guion original de agent-fundamentals.
import reservo_agent as ra
# "Usuario" 1: la tarea de siempre.
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."}]},
]
# "Usuario" 2: otra sala, otro tier, sin ningún tropiezo -- nadie escribió esto antes.
script_luis = [
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_01", "name": "get_quote",
"input": {"room": "Studio", "tier": "basic", "hours": 2}}]},
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_02", "name": "book_room",
"input": {"room": "Studio", "tier": "basic", "hours": 2, "member": "Luis"}}]},
{"stop_reason": "end_turn", "content": [
{"type": "text", "text": "Reservé Studio basic por 2 horas para Luis. Total $80.00. Confirmación #2."}]},
]
# "Usuario" 3: no reserva nada -- cancela algo que ya existía.
script_cancel = [
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_01", "name": "cancel_booking", "input": {"id": 1}}]},
{"stop_reason": "end_turn", "content": [
{"type": "text", "text": "Cancelé la reserva #1 de Ana."}]},
]
final_1, _ = ra.run_reservo_agent("Reserva Focus pro 3h para Ana", script_ana)
print("Usuario 1 ->", final_1["content"][0]["text"])
final_2, _ = ra.run_reservo_agent("Reserva Studio basic 2h para Luis", script_luis)
print("Usuario 2 ->", final_2["content"][0]["text"])
final_3, _ = ra.run_reservo_agent("Cancela la reserva #1", script_cancel)
print("Usuario 3 ->", final_3["content"][0]["text"])
Qué esperar:
Usuario 1 -> Reservé Focus pro por 3 horas para Ana. Total $60.00. Confirmación #1.
Usuario 2 -> Reservé Studio basic por 2 horas para Luis. Total $80.00. Confirmación #2.
Usuario 3 -> Cancelé la reserva #1 de Ana.
Tres tareas, tres respuestas correctas, cero cambios al código del agente. Fíjate en Confirmación #2: el booking_id de Luis es 2, no 1, porque BOOKINGS —el estado de Reservo— es compartido dentro de este mismo proceso de Python, y la reserva de Ana ya ocupó el 1. Eso es correcto y esperado (agent-fundamentals M8, lección 07, lo demostró a fondo). Y el tercer "usuario" no reservó nada — canceló algo que el segundo usuario ni siquiera tocó. run_reservo_agent no tuvo ningún problema con la variedad: cada llamada es independiente, cada guion resuelve una tarea distinta, y el agente responde correctamente a las tres, exactamente como respondería a la número 1.000.
Lo que cambió, y lo que no
Nada en el mecanismo cambió entre esta lección y agent-fundamentals M8. Vale la pena nombrar con precisión qué es lo que sí cambia cuando este mismo patrón —llamar run_reservo_agent con una pregunta y un guion— pasa de ser un ejercicio de notebook a ser un sistema en operación:
- Volumen. Un notebook corre una llamada, la mira, sigue. Un sistema real recibe cientos o miles de llamadas, sin pausa entre una y la siguiente.
- Variedad no controlada por ti. En el ejemplo de arriba, tú escribiste los tres guiones — sabías de antemano qué iba a pasar en cada uno. En producción, la variedad de preguntas la traen los usuarios: tú no decides qué te van a pedir, ni en qué orden, ni si un
tierinválido va a aparecer en la llamada número 4 o en la número 4.000. - Nadie está leyendo el
print. Arriba, cada respuesta pasó por tus ojos, en tu terminal, en el momento exacto en que ocurrió. En un sistema real, la respuesta definal["content"][0]["text"]va directo a un chat, una API, un correo — un proceso automatizado la consume, no un humano parado frente a la consola. - Distancia en el tiempo. Tú no vas a estar mirando cuando la llamada 4.347 del martes a las 3 de la mañana le pase algo raro al agente. Para saber que pasó algo raro, necesitas que quede un rastro — y hoy, como confirmó la lección 01, no queda ninguno.
Ninguno de estos cuatro puntos es un problema de ingeniería del agente. run_reservo_agent sigue siendo tan confiable como el día que agent-fundamentals lo entregó. El problema es un problema de visibilidad: la distancia entre "yo sé que esto funciona porque lo vi correr" y "el sistema puede demostrar que sigue funcionando, sin que nadie lo esté mirando". Cerrar esa distancia es, en una frase, de lo que trata esta guía completa.
Errores comunes
-
Pensar que "operar" significa "reescribir el agente para que sea más robusto". No — el agente ya es robusto: valida, reintenta, corta por timeout, nunca lanza una excepción sin control salvo el tope de iteraciones. Operar no es hacerlo más resistente a un error puntual (eso ya está hecho); es hacerlo visible, medible y gateado frente a lo que pasa cuando corre sin supervisión.
-
Confundir "probé varios guiones distintos" con "está listo para producción". El ejemplo trabajado de esta lección corrió tres guiones distintos — y eso todavía no es operar. Sigue siendo un notebook: tú escribiste los tres, tú los corriste, tú leíste la salida. La diferencia real aparece cuando nadie hace esas tres cosas por ti.
-
Creer que el volumen es "solo cuestión de un bucle
for". Mecánicamente, sí — llamar arun_reservo_agentmil veces en vez de tres es trivial de programar. El problema que el volumen expone no es de rendimiento, es de conocimiento: con tres llamadas puedes leer las tres respuestas a mano; con mil, necesitas que el sistema te resuma qué pasó, porque ya no puedes leerlas todas. -
Pensar que la concurrencia (varias preguntas al mismo tiempo) es el tema de esta guía. No lo es. Esta guía no construye un servidor concurrente ni resuelve condiciones de carrera — eso es un problema de infraestructura, mencionado pero no desarrollado aquí. El foco es más simple y más fundamental: aunque las llamadas llegaran una por una, sin ninguna al mismo tiempo, seguirías sin poder responder las cinco preguntas de la lección 01.
-
Saltar directo a construir el logger, sin entender primero qué información falta. Es tentador ir directo al Módulo 2 y empezar a escribir código de logging. Pero un logger que registra los campos equivocados es casi tan inútil como no tener logger — por eso esta guía dedica dos lecciones más (03 y 04) a nombrar, con precisión, qué es exactamente lo que falta antes de construir cómo capturarlo.
Ejercicios
Ejercicio 1: Predice el booking_id antes de ejecutar (Fácil)
Sin ejecutar nada: si agregaras un cuarto "usuario" al ejemplo trabajado que reserva Boardroom, basic, 1 hora, para "Carla", justo después del tercer usuario (que cancela), ¿qué booking_id debería recibir? Después, ejecútalo y confirma.
Ver solución
3. El contador itertools.count de book_room solo avanza cuando book_room se ejecuta — y hasta este punto se ejecutó dos veces: Ana (1) y Luis (2). cancel_booking (el tercer "usuario") no toca el contador en absoluto; solo borra la entrada 1 de BOOKINGS. La siguiente reserva real —la de Carla— es la tercera vez que book_room se ejecuta en este proceso, así que recibe 3, no 4.
script_carla = [
{"stop_reason": "tool_use", "content": [
{"type": "tool_use", "id": "toolu_01", "name": "book_room",
"input": {"room": "Boardroom", "tier": "basic", "hours": 1, "member": "Carla"}}]},
{"stop_reason": "end_turn", "content": [
{"type": "text", "text": "Reservé Boardroom para Carla."}]},
]
final_4, history_4 = ra.run_reservo_agent("Reserva Boardroom basic 1h para Carla", script_carla)
print("Usuario 4 ->", final_4["content"][0]["text"])
print("booking_id real:", history_4[2]["content"][0]["content"])
Salida esperada (continuando el mismo proceso del ejemplo trabajado):
Usuario 4 -> Reservé Boardroom para Carla.
booking_id real: {"booking_id": 3, "confirmed": true}
Explicación: el punto real del ejercicio no es memorizar el número exacto, sino confirmar algo más importante: nada en el código del agente lleva la cuenta de "cuántos usuarios distintos" hay — el contador solo cuenta reservas creadas, y cancelar una no le resta ni le suma nada. Esa es exactamente el tipo de pregunta operacional ("¿cuántas conversaciones distintas atendí hoy, más allá de cuántas reservas quedaron activas?") que el agente, tal como está, no puede responder por sí mismo.
Ejercicio 2: Simula "el día siguiente" (Medio)
Ejecuta el ejemplo trabajado completo (los tres "usuarios") en un proceso nuevo de Python. Después, sin mirar el código de reservo_tools.py, responde: si este mismo script se ejecutara mañana, en un proceso nuevo, ¿el booking_id de Ana volvería a ser 1? Justifica con lo que ya sabes de agent-fundamentals M8, lección 07.
Ver solución
Sí, volvería a ser 1. BOOKINGS y el contador _booking_ids son variables de módulo en reservo_tools.py — viven en la memoria RAM del proceso de Python que las importó. Un proceso nuevo (por ejemplo, correr el script "mañana", en una terminal nueva) arranca con reservo_tools recién importado, BOOKINGS = {} vacío, y el contador reiniciado en 1. Esto es, exactamente, el límite que agent-fundamentals M8 lección 07 demostró ejecutado: ningún estado de Reservo sobrevive al final del proceso.
import reservo_tools as rt
print("BOOKINGS al arrancar un proceso nuevo:", rt.BOOKINGS)
Salida esperada (en un proceso recién arrancado):
BOOKINGS al arrancar un proceso nuevo: {}
Explicación: esto importa para esta guía por una razón operacional concreta: un sistema real no corre "un script que se ejecuta y termina" — corre como un proceso de larga duración (o varios procesos, detrás de un balanceador), y ese es el ámbito dentro del cual el booking_id es consistente. Saber dónde empieza y termina ese ámbito es parte de entender qué es lo que estás operando.
Ejercicio 3: Diseña, en una frase, la pregunta que ninguno de los tres "usuarios" del ejemplo puede responder (Difícil)
Con el ejemplo trabajado ya ejecutado (tres respuestas, en tu terminal), imagina que tu jefe te pregunta: "¿cuántas de las solicitudes que llegaron hoy tuvieron que auto-corregirse por un argumento inválido?" Sin escribir código todavía —eso es la lección 04—, explica en un párrafo por qué no puedes responder esa pregunta con lo que tienes ahora mismo, aunque técnicamente ejecutaste las tres llamadas y viste sus tres respuestas con tus propios ojos.
Ver solución
No puedes responderla porque la información que necesitas —si hubo un tool_result con is_error: True en el camino de cada run— existe únicamente dentro de la variable history de esa llamada específica, y esa variable nunca se guardó en ningún lugar más allá del alcance de esa línea de código. En el ejemplo trabajado, history de la llamada de Ana sí tuvo un is_error (el tier="premium" rechazado); la de Luis y la de la cancelación no tuvieron ninguno. Pero como el ejemplo solo imprimió final["content"][0]["text"] —la respuesta final, no la traza completa—, esa distinción se perdió en el momento en que cada llamada a run_reservo_agent retornó. Haber "visto las tres respuestas con tus propios ojos" te dice que las tres tareas se completaron; no te dice nada sobre cómo se completó cada una. Responder la pregunta de tu jefe con precisión —contar cuántas de tres, o de tres mil, tuvieron al menos un is_error— requiere decidir, de antemano, capturar esa señal en cada run y guardarla en algún lugar que sobreviva más allá de esa sola llamada. Eso es, con exactitud, lo que la lección 04 empieza a construir.
Resumen y siguiente paso
- El agente no cambió:
run_reservo_agentresuelve tres tareas distintas —ninguna guionada enagent-fundamentals— exactamente igual de bien que la tarea original, sin ningún cambio de código. - Lo que cambia entre un notebook y una operación real no es el mecanismo del agente: es el volumen, la variedad no controlada por ti, la ausencia de un humano leyendo cada respuesta, y la distancia en el tiempo entre cuando algo pasa y cuando alguien podría notarlo.
- Confirmamos, con un ejercicio concreto, que ni siquiera con tres llamadas —vistas con tus propios ojos— puedes responder una pregunta operacional tan simple como "¿cuántas se auto-corrigieron?" sin haber decidido, de antemano, capturar esa señal.
Siguiente lección: 03 — Lo que no puedes ver sin instrumentación. Retomamos la analogía del auto sin tablero y la ponemos a prueba con código real: qué preguntas se pueden responder inspeccionando history a mano, y cuáles simplemente no están en ningún lado.
Recursos adicionales
- Anthropic — Building effective agents — Sobre la diferencia entre un agente que resuelve una demo y uno confiable con tráfico real y variado.
- Anthropic — Tool use (function calling) overview — El protocolo que
run_reservo_agentya implementa, sin cambios, para cualquier tarea que le llegue. - Python — Scope y ciclo de vida de módulos — La base técnica de por qué
BOOKINGSes consistente dentro de un proceso y se reinicia en uno nuevo. - Python 3.14 — What's New — La versión con la que se ejecuta cada línea de código de esta lección.