Agentes de IA: Dominando 3 Patrones Esenciales (ReAct). Parte 2 de 3

Published: (January 4, 2026 at 04:44 PM EST)
7 min read
Source: Dev.to

Source: Dev.to

Artículo – Parte 1

El código de estos patrones está disponible en GitHub. → [Repo]

Si con el patrón “Tool Using” (Nivel 1) le dimos “manos” a la IA para que pudiera tocar el mundo exterior, con ReAct le estamos instalando un “cerebro funcional”.

Recordemos el Nivel 1

Todo era muy lineal:

Estímulo → Respuesta

Tú pedías un dato, el agente disparaba una herramienta y listo. Sin dudas ni planes.

Pero el mundo real es desordenado.

  • ¿Qué pasa cuando la pregunta no se resuelve con un solo clic?
  • ¿Qué ocurre cuando hay que investigar, comparar datos y luego hacer un cálculo?

En esos casos el modelo lineal se estrella y entra en juego ReAct (propuesto por gente de Princeton y Google en 2022). Hoy en día es básicamente el estándar de la industria.

ReAct = Reasoning + Acting (Razonar + Actuar)

¿Qué hace diferente a ReAct?

No es solo que use herramientas (eso ya lo teníamos).
Obligamos al LLM a mantener un Monólogo Interno – un “Thought Trace”.

En lugar de lanzarse a responder a lo loco, el agente entra en un bucle cognitivo:

Pensar → Actuar → Observar → Repetir

El agente literalmente “habla consigo mismo” en los logs.
Planifica su siguiente paso basándose en lo que acaba de descubrir hace un segundo, lo que le permite corregir el rumbo sobre la marcha si las cosas no salen como esperaba.

Ejemplo paso a paso

Pregunta del usuario:

“¿Cuál es la raíz cuadrada de la edad del presidente actual de Francia?”

IteraciónThought (Pensamiento)Action (Acción)Observation (Observación)
1“Vale, no tengo ni idea de la edad del presidente. De hecho, ni siquiera sé quién es el presidente ahora mismo. Primero busquemos eso.”search_tool("Presidente de Francia actual")“Emmanuel Macron.”
2“Ok, ya tenemos el nombre: Macron. Ahora necesito su edad.”search_tool("Emmanuel Macron edad")“47 años.”
3“Tengo el 47. El usuario quería la raíz cuadrada de esto.”calculator_tool("sqrt(47)")“6.8556…”

Respuesta final:

“La raíz cuadrada de la edad de Emmanuel Macron (47) es aproximadamente 6.85.”

¿Ves la magia? El paso 3 era imposible sin haber descubierto el paso 2 primero. El agente ha ido “atando cabos”.

Implementación del patrón ReAct con Agno

A continuación el código completo (Python) que crea un agente siguiendo la lógica Pensar → Actuar → Observar → Repetir.

import os
import sys
import logging
import traceback
from typing import List, Optional
from dotenv import load_dotenv, find_dotenv
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.tavily import TavilyTools

# ----------------------------------------------------------------------
# 1. Configuración global de logging y manejo de excepciones
# ----------------------------------------------------------------------
LOG_DIR = os.path.join(os.path.dirname(__file__), "log")
LOG_FILE = os.path.join(LOG_DIR, "logs.txt")

if not os.path.exists(LOG_DIR):
    os.makedirs(LOG_DIR)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(LOG_FILE, encoding="utf-8"),
        logging.StreamHandler(sys.stdout)
    ]
)

logger = logging.getLogger(__name__)

def global_exception_handler(exctype, value, tb):
    """Captura excepciones no manejadas y las registra."""
    error_msg = "".join(traceback.format_exception(exctype, value, tb))
    logger.error(f"Unhandled exception:\n{error_msg}")
    sys.__excepthook__(exctype, value, tb)

sys.excepthook = global_exception_handler

# ----------------------------------------------------------------------
# 2. Carga de variables de entorno
# ----------------------------------------------------------------------
env_path = find_dotenv()
if env_path:
    load_dotenv(env_path)
    logger.info(f".env file loaded from: {env_path}")
else:
    logger.warning(".env file not found")

# ----------------------------------------------------------------------
# 3. Definición de herramientas
# ----------------------------------------------------------------------
def calculate(expression: str) -> str:
    """
    Resuelve una expresión matemática simple (suma, resta, multiplicación, división).
    Útil para cálculos de años o diferencias de fechas.

    Args:
        expression (str): Expresión a evaluar, p.ej. "2024 - 1789".

    Returns:
        str: Resultado o mensaje de error.
    """
    try:
        # Permitir solo caracteres seguros
        allowed_chars = "0123456789+-*/(). "
        if all(c in allowed_chars for c in expression):
            result = eval(expression)   # noqa: S307  (uso controlado)
            return f"Result: {result}"
        else:
            return "Error: Disallowed characters in expression."
    except Exception as e:
        return f"Error while calculating: {str(e)}"

# ----------------------------------------------------------------------
# 4. Configuración del agente Agno (Patrón ReAct)
# ----------------------------------------------------------------------
model_id = os.getenv("BASE_MODEL", "gpt-4o")

agent = Agent(
    model=OpenAIChat(id=model_id),
    tools=[TavilyTools(), calculate],
    instructions=[
        "You are a researcher using the ReAct (Reason + Act) method.",
        "1. Think step‑by‑step about what information you need to answer the user's question.",
        "2. Use the search tool (Tavily) to find specific dates, facts, or data.",
        "3. Use the calculator ('calculate') for any mathematical operation or time calculation.",
        "4. Do not guess historical information. If you don't have a piece of data, look it up.",
        "5. Show your reasoning clearly: 'Thought:', 'Action:', 'Observation:'.",
        "6. Continue investigating until you have a complete and verified answer."
    ],
)

# ----------------------------------------------------------------------
# 5. Interfaz de usuario
# ----------------------------------------------------------------------
def main():
    logger.info("Starting Historical Detective Agent (ReAct)...")
    print("--- Historical Detective - ReAct Pattern ---")
    print("Type 'exit' to quit.\n")

    while True:
        try:
            user_input = input("Researcher, what is your question?: ")

            if user_input.lower() == "exit":
                logger.info("The user has ended the session.")
                break

            if not user_input.strip():
                continue

            logger.info(f"User query: {user_input}")
            print("\nInvestigating...\n")

            # Ejecutar el agente con el prompt del usuario
            response = agent.run(user_input)
            print("\n--- Answer ---")
            print(response)
            print("\n----------------\n")

        except KeyboardInterrupt:
            logger.info("Session interrupted by user (Ctrl+C).")
            break
        except Exception as e:
            logger.error(f"Error during processing: {e}")

if __name__ == "__main__":
    main()

Qué hace este script

  1. Logging → guarda toda la actividad en log/logs.txt y la muestra en consola.
  2. Manejo de excepciones → captura cualquier error inesperado y lo registra.
  3. Herramientas
    • TavilyTools para búsquedas web.
    • calculate para operaciones aritméticas simples.
  4. Agente ReAct → sigue las instrucciones que obligan al modelo a:
    • Pensar paso a paso.
    • Actuar (buscar o calcular).
    • Observar el resultado.
    • Repetir hasta obtener una respuesta completa y verificada.
  5. Interfaz → bucle de consola donde el usuario escribe preguntas y el agente responde, mostrando el proceso interno en los logs.

Nota:

  • Asegúrate de tener un archivo .env con la variable BASE_MODEL (por ejemplo, gpt-4o).
  • Instala las dependencias necesarias (agno, python-dotenv, etc.) antes de ejecutar el script.

¡Listo! Ahora puedes experimentar con el patrón ReAct y observar cómo el agente “habla consigo mismo” para resolver preguntas complejas.

nt.print_response(user_input, stream=True, show_tool_calls=True)
print("\n")

except KeyboardInterrupt:
    logger.info("Keyboard interrupt detected.")
    break
except Exception as e:
    logger.error(f"Error in main loop: {str(e)}")
    print(f"\nAn error occurred: {e}")

if __name__ == "__main__":
    main()

¿Dónde está el ReAct en el código?

PENSAR (Think): Se define en las instructions del agente.

ACTUAR (Act): El agente tiene capacidades externas definidas en tools.

OBSERVAR (Observe): El framework (Agno) ejecuta la herramienta y le devuelve el resultado al agente.

REPETIR (Repeat): El bucle continúa hasta que el agente tiene suficiente información.

¿Por qué nos gusta tanto este patrón? (Pros)

  • Resuelve problemas de “varios saltos” (Multi‑hop)
    Es la capacidad de responder preguntas donde A te lleva a B, y B te lleva a C.

  • Capacidad de “Auto‑curación” (Self‑Healing)
    Esto es vital. Imagina que buscas “Edad de Macron” y Google falla. Un script normal colapsaría. Un agente ReAct piensa: “Vaya, la búsqueda falló. Bueno, voy a buscar su fecha de nacimiento y calculo la edad yo mismo”.

  • Adiós a la caja negra
    Como desarrollador, puedes leer los Thoughts. Sabes exactamente por qué el agente decidió usar la calculadora y no otra cosa.

  • Menos mentiras (Alucinaciones)
    Al obligarle a basar cada paso en una observación real (Observation), es mucho más difícil que se invente datos.

  • Es lento (Latencia)
    ReAct es secuencial. Pensar 3 veces significa llamar al LLM 3 veces + ejecutar herramientas. Prepárate para esperas de 10 a 30 segundos.

  • La factura de la API (Coste)
    Ese “monólogo interno” consume tokens como si no hubiera un mañana. Tu historial de contexto se llena rapidísimo.

  • El riesgo de la obsesión
    A veces entran en bucle: “Busco X → No está → Pienso que debo buscar X otra vez → Busco X…”.

  • Prompting delicado
    Necesitas un Prompt de Sistema muy bien ajustado para que el modelo sepa cuándo dejar de pensar y darte la respuesta.

Buenas prácticas para producción (Agno, LangChain, etc.)

  1. Ponle un freno (Max Iterations)
    Configura siempre un límite de iteraciones (ej. 10 pasos). Si no ha resuelto el problema en 10 pasos, no lo va a resolver en 100 y solo te está quemando dinero.

  2. Enséñale a callar (Stop Sequences)
    Técnicamente, el LLM debe parar de escribir justo después de lanzar la Action. Si no lo cortas ahí, empezará a inventarse la Observation él mismo (alucinar el resultado de la herramienta). Los frameworks modernos suelen manejar esto, pero vigílalo.

  3. Limpia la basura (Gestión de Contexto)
    En chats largos, borra las trazas de pensamiento antiguas y quédate solo con la respuesta final. Si no, el agente se quedará sin memoria “RAM” (ventana de contexto) enseguida.

Ejemplos de flujos de trabajo con ReAct

  • Agentes de Programación (tipo Devin o Copilot)
    “Escribo código → Ejecuto → Veo el error → Pienso cómo arreglarlo → Reescribo”.

  • Soporte Técnico Nivel 2
    “Leo el ticket → Miro el estado del servidor → Miro los logs del usuario → Cruzo datos → Respondo”.

  • Analistas Financieros
    “Busco precio actual → Busco noticias de última hora → Comparo con el histórico → Genero recomendación”.

¡Happy Coding! 🤖

Back to Blog

Related posts

Read more »