Lección 2: El bucle central: de una ida y vuelta a la operación continua
Objetivos de aprendizaje:
- Recitar los cuatro pasos del bucle multiturno que impulsa stop_reason, y conectar una ida y vuelta de llamada a herramienta en un bucle while que sigue en marcha
- Usar el valor de stop_reason (tool_use / end_turn) para decidir si el bucle continúa o se para, y explicar por qué ese campo es la condición del while del bucle
- Señalar qué fronteras le faltan a este bucle esqueleto, y explicar por qué el historial crece en cada turno y por qué no puedes fiarte solo de que el modelo diga «terminé»
Requisitos: Leíste la Lección 1 y sabes que un arnés es la capa de código de control alrededor del modelo; puedes leer el tool_use / tool_result de una sola ida y vuelta de llamada a herramienta | Anterior: Lección 1 << | Siguiente: Lección 3 >>
Una sola ida y vuelta deja de bastar
La Lección 1 ya desarmó una ida y vuelta completa de llamada a herramienta: el modelo regresa stop_reason: "tool_use" junto con un bloque tool_use, tu código anfitrión lee el name y el input, ejecuta de veras la cosa, empaca la salida en un tool_result y la envía de vuelta, y solo entonces el modelo da su respuesta final. Tres trozos de JSON, un viaje, listo.
Las tareas reales rara vez son tan corteses. Cambia el escenario: estás escribiendo un bot de guardia, y un usuario dice: «Reinicia el servicio api por mí, luego revisa si los logs todavía tienen errores, y pégalos si los hay». Esa sola oración empaca dos trabajos, y el segundo depende del primero: revisar los logs no significa nada hasta que el reinicio haya terminado. El modelo no puede hacer ambos en el primer turno. Todo lo que puede hacer es esto:
- El turno uno regresa
tool_use, llamando a restart_service. Lo corres y devuelves «reinicio exitoso».
- El turno dos regresa
tool_use otra vez, esta vez llamando a read_logs. Lo corres y devuelves el contenido de los logs.
- El turno tres por fin regresa
stop_reason: "end_turn", con una línea como «Reinicio completo; los logs tienen dos errores de timeout, pegados abajo».
Una solicitud del usuario, tres viajes de ida y vuelta. Lo que el modelo puede ver en cada paso, y lo que hace a continuación, depende de lo que regresó en el tool_result previo, que es exactamente la definición de agente de Anthropic: un LLM que usa herramientas basándose en la retroalimentación del entorno, dentro de un bucle.1 La sola ida y vuelta de la Lección 1 es apenas el caso especial donde ese bucle resultó girar una sola vez. Lo que hace esta lección es conectar «una ida y vuelta» en «idas y vueltas que siguen y siguen», y lograr una vista clara de qué es el bucle del medio, qué lo impulsa y dónde necesita un freno.
Los cuatro pasos del bucle
Convertir una sola ida y vuelta en un bucle no exige inventar nada nuevo. Solo repites los movimientos que ya conoces. La documentación de la API de Claude escribe este proceso multiturno como una secuencia fija:2
- Envías una solicitud que carga
messages y el manifiesto tools (tools tiene que ir junto en cada turno, sin excepción).
- El modelo regresa una respuesta. Si todavía necesita una herramienta,
stop_reason es "tool_use" y content carga uno o más bloques tool_use.
- Ejecutas cada bloque
tool_use y conviertes cada salida en un bloque tool_result. La palabra clave de ese paso es cada: por más bloques tool_use que hayan regresado en una respuesta, el siguiente mensaje user necesita esa misma cantidad de bloques tool_result coincidentes, cada uno reclamado por su tool_use_id, todos empacados en ese único mensaje user inmediatamente siguiente. La documentación pone la regla así: "Whichever strategy you use, return one tool_result for each tool_use block, all together in the next user message. Match each result to its call with tool_use_id, and put every tool_result block before any text content in that message."3 (Cualquiera que sea la estrategia que uses, devuelve un tool_result por cada bloque tool_use, todos juntos en el siguiente mensaje del usuario. Empareja cada resultado con su llamada mediante tool_use_id, y pon cada bloque tool_result antes de cualquier contenido de texto en ese mensaje.)
- Anexas tanto la respuesta completa del modelo de ese turno (rol
assistant) como el lote de tool_result que armaste (rol user) a messages, y luego envías otra solicitud.
Después viene la oración que más importa: repite desde el paso 2 mientras stop_reason sea "tool_use".2 Ese «repite mientras» es el eje que estira una sola ida y vuelta en un bucle. El ejemplo de la Lección 1 se detuvo en el primer end_turn porque un viaje era todo lo que esa tarea necesitaba; el bot de guardia ejecuta los pasos 2 al 4 tres veces, hasta que el turno tres regresa end_turn.
Vale la pena recordar: los campos de los bloques tool_use y tool_result no han cambiado en nada. Un bloque tool_use carga id / name / input; un bloque tool_result carga tool_use_id / content, más un is_error opcional cuando la llamada falló.3 El bucle no reescribe lo que significa ninguno de esos campos. Solo hace que el mismo conjunto de campos se llene y se envíe de vuelta, una y otra vez.
stop_reason es la condición del while del bucle
Esa línea de arriba —«repite mientras stop_reason siga siendo tool_use»— se traduce en código como la prueba de un bucle while. Y la única oración que deberías llevarte de esta lección es esta: decidir si el bucle continúa o se para se reduce a ese único campo, stop_reason. Tiene muchos valores posibles, pero para el control del bucle, distinguir dos de ellos basta para empezar:
"tool_use": el modelo todavía quiere una herramienta. Te entrega la solicitud y espera a que ejecutes y devuelvas el resultado antes de continuar. El bucle gira una vuelta más.
"end_turn": el modelo ya no quiere una herramienta; cree que dijo lo que tenía que decir. El bucle termina por sí solo y le entregas el texto final al usuario.
Una hoja de ruta de código abierto sobre ingeniería de arneses pone esta capa de control sin rodeos: lo que impulsa un arnés es el bucle while que impulsa modelo→herramientas→modelo.4 Y lo que se asienta en la expresión de condición de ese while es stop_reason. Mismo modelo, mismo conjunto de herramientas: cuántas vueltas gira y cuándo se para lo decide por entero cómo el anfitrión lee ese campo y cómo escribe esa condición. Por eso la Lección 1 dijo "Same model, different harness, completely different result."4 (Mismo modelo, distinto arnés, resultado completamente distinto.)
Una cosa que conviene zanjar de entrada, para que no leas la señal al revés: stop_reason: "tool_use" significa que el modelo quiere usar una herramienta, no que una herramienta se ha usado. El modelo nunca ejecuta nada por su cuenta. Emite una solicitud estructurada; lo que de veras ejecuta la herramienta es tu código anfitrión (o los servidores de Anthropic), y el resultado solo vuelve a fluir a la conversación después.2 Así que en el instante en que tool_use aparece en el bucle, nada ha pasado todavía. La acción pasa en las pocas líneas de tu código que leen el name y el input y van a hacer el trabajo. Tratar «recibí un tool_use» como «la herramienta terminó de ejecutarse» es el tropiezo más fácil al pasar de una sola ida y vuelta a un bucle: te hace juzgar mal en qué paso está de veras el bucle en este momento.
Escrito, son solo unas pocas líneas
Pon esos cuatro pasos y la condición del while sobre stop_reason en JavaScript y el esqueleto es sorprendentemente corto:
Recorre los cuatro pasos una vez más contra el código: la línea while es «repite mientras siga siendo tool_use»; dentro del cuerpo, tanto la respuesta assistant como el mensaje user de bloques tool_result se hacen push a messages, y luego response se reasigna. Esa última asignación es lo que hace posible parar siquiera: quítala, y response.stop_reason conserva su valor viejo para siempre, así que el bucle while nunca sale (ese sabor de bucle infinito es la estrella de la Lección 4).
Este código funciona, pero es un esqueleto: lo bastante simple para hacer visible el bucle mismo, y ni de cerca seguro para entregarlo a producción. Da por sentado que el modelo siempre regresará end_turn en algún turno, da por sentado que cada herramienta ejecuta sin problemas, y da por sentado que no importa cuán largo se vuelva el historial. Esas tres suposiciones son exactamente lo que las siguientes lecciones desarman una por una.
Cada turno de más alarga el historial
Mira otra vez esas líneas de messages.push: cada turno del bucle mete dos mensajes más en messages —la respuesta assistant del modelo y el lote de tool_result que devolviste. Y el siguiente callModel tiene que despachar todo messages de nuevo, sin cambios. Así que cuanto más se ejecuta este bucle, más historial carga cada solicitud, y solo crece.
Eso no es un descuido en la implementación. Es una propiedad inherente del bucle como estructura: un agente que se ejecuta en un bucle genera cada vez más datos que podrían ser relevantes para el siguiente turno de inferencia.5 Tres turnos del bot de guardia solo amontonan un resultado de reinicio más un trozo de logs. Pero una tarea que necesita decenas de turnos enrolla el historial hasta algo enorme.
Escondido aquí hay un problema que solo se abre en el curso «Memoria y estado del agente», pero que tiene que plantarse ahora: los modelos tienen un «presupuesto de atención», y cada nuevo token introducido agota ese presupuesto en cierta medida.5 Un historial más largo significa más tokens, y a medida que el número de tokens en la ventana de contexto aumenta, la capacidad del modelo de recordar con precisión información de ese contexto disminuye.5 Fíjate que esto es un gradiente de rendimiento que baja con suavidad conforme a la longitud, no un precipicio duro del que caes pasado cierto umbral5; no lo leas como «pasado el límite queda inservible». Pero la dirección es inequívoca: el contexto tiene que tratarse como un recurso finito con rendimientos marginales decrecientes.5 Ese messages.push irreflexivo del bucle esqueleto no hace nada al respecto. Da por sentado que el historial puede crecer para siempre, y «Memoria y estado del agente» es el curso que vuelve a saldar esa cuenta.
Un bucle solo no basta; hay que añadirle fronteras
Ahora tienes un bucle que gira. Pero «puede girar» y «gira con seguridad» son dos cosas distintas. El bucle esqueleto le entrega al modelo por entero la decisión de parar o continuar: el turno en que regrese end_turn es el turno en que el bucle se para. Sin embargo, los agentes son sistemas donde el modelo dirige dinámicamente su propio proceso y su uso de herramientas.1 Esa autonomía es precisamente de donde viene la utilidad, y precisamente donde vive el riesgo: la autonomía implica costos más altos y el potencial de errores que se componen acumulándose alrededor de turno tras turno del bucle.1 El modelo potencialmente operará durante muchos turnos, y debes tener cierto nivel de confianza en su toma de decisiones antes de dejarlo ejecutarse.1
El truco está en que la confianza no es lo mismo que la ausencia de supervisión. Si el modelo se atasca en algún paso, o se desvía por lo que una herramienta devolvió, y simplemente nunca regresa end_turn, un bucle que no vigila más que stop_reason seguirá girando a su lado indefinidamente. Así que sobre la propia señal de fin del modelo, por lo general añades condiciones de parada explícitas también —un tope al número máximo de iteraciones, por ejemplo, para mantener el control de tu lado.1 El escueto while (response.stop_reason === "tool_use") del esqueleto no tiene tal fusible: le tiene confianza al modelo sin dejarse una salida.
Eso planta los dos montajes de esta lección: este bucle necesita fronteras (no puedes depender de que el modelo diga end_turn; necesitas condiciones de parada explícitas — Lección 3), y el historial que este bucle produce necesita gestión (los tokens son un recurso finito, así que no puedes solo echar cosas dentro — queda para el curso Memoria y estado del agente). Cómo se ve de verdad un bucle cuando se sale de los rieles, y cómo atraparlo, es el tema de la Lección 4. Para esta lección basta con dejar puesto el eje: cómo gira el bucle, y que stop_reason es lo que lo impulsa.
Resumen
- Conectar una ida y vuelta de llamada a herramienta en un bucle no necesita ningún mecanismo nuevo, solo cuatro pasos repetidos: envía la solicitud → lee
stop_reason y los bloques tool_use → ejecuta las herramientas y empaca los bloques tool_result → anexa al historial y envía de nuevo; repite mientras stop_reason siga siendo tool_use2
stop_reason es la condición del while de este bucle: tool_use significa que el modelo todavía quiere una herramienta y el bucle continúa, end_turn significa que el modelo está cerrando y el bucle termina por sí solo; el eje modelo→herramientas→modelo lo impulsa ese campo4
tool_use es la señal de que el modelo quiere una herramienta, no un recibo que diga que una se ejecutó; el modelo nunca ejecuta nada por sí mismo, y la acción pasa donde el anfitrión lee el name y el input y se pone a trabajar2
- Cada turno del bucle alarga el historial y nunca lo acorta, ya que un agente en un bucle sigue generando más datos que podrían ser relevantes5; y con un presupuesto de atención finito, el recuerdo se degrada conforme el contexto crece, así que los tokens tienen que tratarse como un recurso finito con rendimientos marginales decrecientes5
- Un bucle solo no basta: la autonomía trae costos más altos y errores que se componen, y el modelo puede operar durante muchos turnos1, así que sobre el propio
end_turn del modelo por lo general añades condiciones de parada explícitas (un número máximo de iteraciones, digamos) para mantener el control de tu lado1; cómo fijarlas es el tema de la Lección 3
>> Lección 3: Condiciones de parada: cuándo debería rendirse un agente