Analizado a través de ejecuciones reales grabadas que puedes reproducir a continuación.
Por qué existe ReAct
Tienes un LLM. Puede razonar y escribir, pero tiene dos límites que deben tomarse en serio: su conocimiento termina en la fecha de corte de entrenamiento (training cutoff) y alucinará hechos con total seguridad cuando no los conoce. Para tareas que necesitan datos frescos o respuestas verificables —buscar una canción, encontrar el clip de una película, consultar una API— el modelo por sí solo no es suficiente. Necesita herramientas. ReAct (Reasoning + Acting) es un patrón para orquestar eso.
La idea es simple. En lugar de pedirle al modelo una respuesta de un solo golpe, le pides que piense y actúe en ciclos. En cada ciclo, el modelo razona sobre lo que sabe hasta el momento, elige una herramienta, y el sistema ejecuta la herramienta y le devuelve el resultado. El modelo vuelve a pensar con la nueva información. Esto se repite hasta que el modelo decide que tiene lo suficiente para responder.
Leer sobre un loop es una cosa. Verlo en ejecución es mejor. El widget a continuación reproduce ejecuciones reales grabadas de nuestro agente de clips de películas: la interacción capturada real, con sus tiempos reales, tokens de pensamiento, llamadas a herramientas (tool calls) y resultados. Nada es simulado y no se llama a ningún LLM cuando presionas play; estás viendo una grabación del agente haciendo el trabajo. Elige una ejecución:
Ejecuciones grabadas del agente ReAct de clips de películas. Cada reproducción muestra el rastro completo: el pensamiento interno del modelo (expande los bloques), cada llamada a herramienta con sus argumentos y resultados, y la respuesta final, exactamente como se transmitió durante la captura original.
La ejecución que analizaré en el resto de este artículo es la primera: la consulta fue "Houston, we have a problem". El agente terminó devolviendo la marca de tiempo exacta de YouTube donde Tom Hanks pronuncia esa frase en Apollo 13. Un puñado de iteraciones del loop, tres invocaciones de herramientas, una URL verificada. Cada fragmento de datos citado a continuación proviene de una ejecución real de este agente.
Los tres pasos: pensamiento, acción, observación
Cada iteración de un loop ReAct tiene tres fases lógicas. Dos de ellas son cíclicas; el loop solo termina cuando el modelo decide que ha terminado:
Pensamiento (Thought). El modelo razona sobre lo que sabe y lo que aún necesita. Parte de esto sucede en los tokens de pensamiento (thinking tokens), un borrador interno que el usuario normalmente nunca ve. Capturamos esos tokens durante la grabación, por lo que la reproducción de arriba tiene bloques de pensamiento expandibles. Esta es la parte que la mayoría de la gente nunca ha visto realmente, y es la parte que vale la pena observar.
En la ejecución de Houston, el razonamiento inicial va más o menos así:
Verificando escena famosa de película
He confirmado que la película es, de hecho, Apollo 13 (1995), dirigida por Ron Howard. El usuario quiere específicamente la frase "Houston, we have a problem", que sé que es de esa película. Mi siguiente paso es encontrar el clip de video relevante para referencia.
Ese es el modelo decidiendo qué hacer: verificar la cita una vez y luego ir directo a YouTube.
Acción (Action). El modelo elige UNA herramienta del conjunto disponible y proporciona sus argumentos. Este agente tiene cinco herramientas: web_search, youtube_search, get_timestamp_from_text, process_video y finish. Las herramientas se declaran a Gemini como esquemas de funciones (function schemas), y el modelo emite una llamada a función nativa:
{
"name": "youtube_search",
"args": {
"query": "Apollo 13 Houston we have a problem scene"
}
}Una herramienta, un conjunto de argumentos, sin comentarios. Debido a que la llamada llega a través del canal de function calling de la API en lugar de texto libre, no hay JSON que extraer de la prosa ni expresiones regulares que mantener. La API solo permite que el modelo llame a las herramientas que fueron declaradas, con argumentos que coincidan con el esquema declarado.
Observación (Observation). El sistema ejecuta la herramienta elegida y captura el resultado. El resultado vuelve a la conversación como una respuesta de función; desde el punto de vista del modelo, acaba de llegar nueva información. El sistema escribe la observación; el modelo nunca escribe una por sí mismo.
La conversación ES la memoria
Este es el detalle más importante a entender. El modelo no tiene memoria entre llamadas a la API. Lo que sabe sobre el loop tiene que estar EN la conversación que ve en la siguiente llamada. El agente mantiene una sesión de chat y cada ronda se añade a ella:
- La consulta original del usuario es el primer turno del usuario.
- El razonamiento visible del modelo y cada llamada a herramienta son turnos del modelo.
- Cada resultado de herramienta se devuelve como una respuesta de función.
- La respuesta final llega a través de la llamada a
finish.
Para la última ronda de la ejecución de Houston, la conversación que ve el modelo contiene cada resultado de búsqueda y cada marca de tiempo que ha recopilado. No hay un estado de sesión oculto en algún servidor. El historial es la memoria. Esta estructura es también lo que permite que el prefix caching implícito funcione: el prefijo de la llamada anterior es idéntico byte por byte al prefijo de la llamada actual, por lo que la mayor parte de la conversación no se vuelve a facturar a precio completo.
El contrato de herramientas: por qué los esquemas importan
Los tutoriales de ReAct a veces muestran al modelo "llamando herramientas" en texto libre: Acción 1: web_search("Apollo 13"). Eso funciona en una demo. Pero falla en producción: necesitas un regex para analizar el nombre de la herramienta, las reglas de citado varían, el modelo se desvía hacia la prosa a mitad de la acción. Aprendimos esto por las malas al grabar estas demos: con el protocolo del loop descrito solo como ejemplos de texto, el modelo ocasionalmente escribía un historial falso completo, inventando IDs de video y resultados de herramientas que nunca recibió, en cinco segundos. Parecía plausible, pero cada dato en él era fabricado.
Nuestra implementación se apoya en el function calling nativo de Gemini. Cada herramienta es una FunctionDeclaration con un JSON Schema para sus parámetros. El modelo transmite su razonamiento y luego emite partes estructuradas de llamadas a funciones que el SDK entrega como objetos, no como cadenas de texto. El despacho es una búsqueda en una tabla, no un análisis de prosa. Y el system prompt lo dice claramente: nunca escribas una llamada a herramienta como texto, nunca inventes una observación; solo llamadas reales.
Compara dos llamadas de la ejecución de Houston. Misma forma, diferente herramienta:
{
"name": "get_timestamp_from_text",
"args": {
"video_id": "YwG4F-16Tno",
"snippet": "Houston, we have a problem"
}
}El agente lee el nombre, elige la habilidad correspondiente de una tabla de despacho, pasa los argumentos y la ejecuta. Sin coincidencia de cadenas, sin ambigüedad.
La herramienta finish merece una nota. Se declara como cualquier otra herramienta, pero el ejecutor la trata como el terminador del loop: su argumento response lleva la respuesta final, y llamarla termina la ejecución. Hacer que "he terminado" sea una llamada a herramienta en lugar de una convención de texto significa que la condición de parada pasa por el mismo canal regulado por esquemas que todo lo demás.
La cadena de dependencia de herramientas
El punto de ReAct es que cada llamada a herramienta está informada por la anterior. El razonamiento del modelo se acumula a través de las iteraciones.
Observa cómo se desarrolla la ejecución de Houston (puedes ver esta secuencia exacta en la reproducción):
- web_search. Una búsqueda de verificación sobre la cita, como lo exigen las reglas estrictas.
- youtube_search. El modelo conoce la película y necesita un ID de video. La herramienta devuelve varios candidatos.
- get_timestamp_from_text. El modelo ahora tiene IDs de video. Elige el primer resultado (la carga oficial de Universal Pictures) y le pide a la herramienta de marcas de tiempo que "vea" el video para encontrar la frase.
- process_video. El modelo ahora tiene un ID de video más una marca de tiempo verificada. Llama al generador de URLs.
- finish. El modelo tiene la URL reproducible y compone la respuesta final.
Cada paso depende del resultado del anterior. El modelo literalmente no puede llamar a process_video sin un ID de video y una marca de tiempo; no puede obtener una marca de tiempo sin un ID de video; no puede obtener un ID de video sin una búsqueda. Esa cadena de dependencia es lo que hace necesario el enfoque de múltiples pasos; ningún prompt único de "responde esto" podría sustituirlo.
Vale la pena ver los datos reales que fluyen por la cadena. Aquí hay un resultado de youtube_search de la ejecución grabada:
{
"results": [
{"video_id":"YwG4F-16Tno","title":"Apollo 13 | \"Houston, We Have a Problem\"","channel":"Universal Pictures","view_count":7698237},
{"video_id":"C3J1AO9z0tA","title":"Apollo 13 (1995) - Houston, We Have a Problem Scene (4/11) | Movieclips","channel":"Movieclips","view_count":2803140},
{"video_id":"jMmo0AaPMn4","title":"Houston, We Have A Problem (Full Scene) | Apollo 13","channel":"Critic Picks","view_count":988838}
]
}La herramienta de marca de tiempo devolvió luego:
{
"start_seconds": 44,
"end_seconds": 46,
"confidence": 1.0,
"matched_text": "Houston, we have a problem",
"error": null
}confidence: 1.0 significa una coincidencia literal. matched_text devuelve exactamente lo que la herramienta encontró, que es como el agente detectaría un casi-acierto: si el modelo hubiera pedido "Huston, we have a problem here", el matched_text mostraría la frase más cercana realmente presente, con una confianza más baja indicándole que no confíe plenamente en el resultado.
El constructor de URLs tomó el video_id y la marca de tiempo y devolvió:
{
"status": "success",
"video_id": "YwG4F-16Tno",
"start_seconds": 44,
"clip_url": "https://www.youtube.com/watch?v=YwG4F-16Tno&t=44s",
"message": "Clip ready → https://www.youtube.com/watch?v=YwG4F-16Tno&t=44s (starts at 44s)"
}La salida de cada paso se convierte en la entrada del siguiente. La cadena es corta aquí —tres búsquedas y una generación de URL— pero el mismo patrón se escala: una consulta musical podría tomar cinco iteraciones porque la canción necesita ser identificada antes de encontrar el video y antes de que la letra pueda ser sincronizada.
¿Cuándo se detiene el loop?
El modelo lo decide. Cuando llama a finish, el loop termina y el valor de finish.response es lo que ve el usuario.
También hay un límite máximo estricto —la configuración de la escena limita el loop a ocho rondas— y una presión leve a través de las reglas de convergencia en el system prompt:
La mayoría de las consultas se resuelven en 3–4 pasos. Tan pronto como tengas los hechos clave, llama a finish — no busques confirmaciones adicionales. Para la ronda 7 DEBES llamar a finish a menos que sea genuinamente necesaria una llamada crítica a una herramienta.
La ejecución de Houston terminó muy por debajo del límite. Su llamada a finish fue:
{
"name": "finish",
"args": {
"response": "Esta frase icónica proviene de la película de 1995 **Apollo 13**, dirigida por **Ron Howard**.\n\nLa frase es pronunciada por el comandante **Jim Lovell** (interpretado por Tom Hanks) después de que un tanque de oxígeno explota en la nave espacial. Aunque la cita histórica real de la misión Apollo 13 fue \"Houston, we've had a problem\", la versión de la película se convirtió en una de las frases más famosas de la historia del cine.\n\n[Apollo 13 - \"Houston, we have a problem\"](https://www.youtube.com/watch?v=YwG4F-16Tno&t=44s)"
}
}Mira la estructura de esa respuesta. Los metadatos de la película provinieron del entrenamiento del modelo: título, año, director, personaje, actor — incluso el dato curioso de que la misión real dijo "we've had a problem" mientras que la película cambió el tiempo verbal. Un ancla verificada provino de las herramientas: la URL con la marca de tiempo. El modelo envuelve el resultado verificado en el contexto que ya conoce. Esa mezcla de hechos conocidos más anclas verificadas es la forma práctica de una salida de agente útil.
Qué protege contra comportamientos inadecuados
El modelo podría, en principio, hacer cosas peores de las que muestran estas ejecuciones. Podría alucinar un ID de video y llamar a process_video con basura. Podría saltarse la herramienta de marca de tiempo y adivinar un número. Podría entrar en un bucle infinito llamando a la misma herramienta. Podría inventar una respuesta sin llamar a ninguna herramienta en absoluto (vimos que intentaba exactamente eso antes de que el prompt fuera reforzado).
El agente evita cada uno de esos casos acumulando restricciones:
- Declaraciones de funciones. El modelo no puede llamar a una herramienta que no fue declarada o pasar argumentos que no coincidan con el esquema; la API lo rechaza antes de que nuestro código lo vea.
- Reglas estrictas en el system prompt. "Siempre llama a get_timestamp_from_text antes de process_video". "Nunca extraigas un clip de video sin una marca de tiempo confirmada". "Eres un asistente estrictamente basado en hechos (grounded). No afirmes ningún hecho que no hayas verificado a través de una llamada a herramienta en esta iteración o en una anterior en esta conversación".
- Solo llamadas reales. El prompt prohíbe escribir llamadas a herramientas u observaciones como texto; el modo de falla de "historial fabricado" se menciona y prohíbe explícitamente.
- Presupuesto de iteraciones. El loop está limitado en la configuración. Después de eso, el agente tiene que concluir con lo que haya encontrado.
- Presión de convergencia. El system prompt le dice al modelo cuándo DEBE llamar a finish.
- Reintento de llamada malformada. Ocasionalmente el modelo estropea una llamada a función lo suficiente como para que la API la descarte. El loop detecta eso y le pide al modelo que vuelva a emitir la llamada en lugar de terminar silenciosamente la ejecución.
La ejecución de Houston respeta todo esto. Cada afirmación en la respuesta final es o bien conocimiento de entrenamiento (metadatos de la película no controvertidos) o está respaldada por una llamada a herramienta (la URL).
Lo que el modelo no hizo
Vale la pena señalar explícitamente lo que no ocurrió. En esta ejecución, el modelo:
- No se saltó la verificación: hizo una búsqueda
web_searchsobre la frase, tal como exigen las reglas, y nada más. - No eligió un candidato aleatorio de YouTube: tomó el primer resultado, que era el más visto Y la carga oficial de Universal Pictures.
- No inventó una marca de tiempo: utilizó la herramienta dedicada
get_timestamp_from_text, que hace su propia llamada a Gemini contra el video real. - No agotó el presupuesto de iteraciones.
- No inventó al actor o al director: esos datos vinieron del entrenamiento, pero el paso de verificación de la marca de tiempo demostró que la escena realmente existe en el segundo que el modelo afirmó.
Una nota sobre la reproducción en sí
El widget en la parte superior no es un simulacro y no está llamando a un modelo cuando presionas play. Ejecutamos el agente de verdad una vez, en una herramienta de captura de administración, y grabamos cada evento con su marca de tiempo: mensaje del usuario, cada token de pensamiento, cada llamada a herramienta y su resultado, cada token de respuesta. El artículo reproduce esa grabación; las esperas largas de las herramientas se comprimen para que no tengas que esperar 20 segundos por una llamada de análisis de video, todo lo demás va a su ritmo real. Es la misma filosofía que la depuración con grabadora de vuelo (flight-recorder debugging): graba la ejecución una vez, reprodúcela para siempre sin gastar otro token. Por eso también puedes comparar dos ejecuciones del mismo flujo de trabajo arriba: mismo agente, mismas herramientas, diferente consulta, diferente camino a través del loop.
Conclusión
El valor de ReAct no es ser inteligente. Es ser predecible. Cada llamada a herramienta es acotada. La salida de cada herramienta tiene un esquema específico. El modelo está obligado a actuar a través de un canal regulado por esquemas, y el razonamiento de texto libre se queda en el flujo de pensamiento (thinking stream) donde corresponde. Ese contrato es lo que hace que el despacho (dispatch) sea confiable.
La ejecución completa de Houston, de principio a fin:
- Usuario: "Houston, we have a problem"
web_search→ confirma que la cita es de Apollo 13 (1995), Ron Howardyoutube_search→ videos candidatos, el primero es la carga oficial de Universal Picturesget_timestamp_from_text(YwG4F-16Tno, "Houston, we have a problem")→ 44s, confianza 1.0process_video(YwG4F-16Tno, 44)→ https://www.youtube.com/watch?v=YwG4F-16Tno&t=44sfinish→ respuesta final armada con datos de entrenamiento más la URL verificada
La misma estructura de loop funciona para consultas de películas, citas mal recordadas y rechazos: cuando el usuario pide algo fuera de alcance, el modelo llama a finish en la primera ronda con una declinación cortés y el loop termina inmediatamente. El patrón no cambia; solo cambian las llamadas a herramientas intermedias. Desplázate hacia arriba y reproduce la segunda ejecución para ver un camino diferente a través de la misma maquinaria.
Clip final de la ejecución de Houston: https://www.youtube.com/watch?v=YwG4F-16Tno&t=44s
