DEV Community

Cover image for Agentes de IA: Dominando 3 Patrones Esenciales (ReAct). Parte 2 de 3
Gabriel Melendez
Gabriel Melendez

Posted on

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

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

El Patrón ReAct (Reason + Act) – Cuando el Agente empieza a "hablar solo"

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".

Hagamos memoria: en 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 seamos realistas, el mundo real es desordenado. ¿Qué pasa cuando la pregunta no se resuelve con un solo clic? ¿Qué pasa cuando hay que investigar, comparar datos y luego hacer un cálculo?

Ahí es donde el modelo lineal se estrella y entra en juego ReAct (propuesto por gente muy lista de Princeton y Google en 2022), que hoy en día es básicamente el estándar de la industria.

¿Qué es exactamente ReAct?

El nombre viene de Reasoning + Acting (Razonar + Actuar).

Image 1

La idea rompedora aquí no es que use herramientas (eso ya lo teníamos), sino que le obligamos al LLM a tener un Monólogo Interno ("Thought Trace"). En lugar de lanzarse a responder a lo loco, el agente entra en un bucle cognitivo:

Pensar → Actuar → Observar → Repetir
Enter fullscreen mode Exit fullscreen mode

El agente literalmente "habla consigo mismo" en los logs. Planifica su siguiente paso basándose en lo que acaba de descubrir hace un segundo. Esto le permite corregir el rumbo sobre la marcha si las cosas no salen como esperaba.
Destripando el Bucle: El caso del "Detective Histórico"

Vamos a ver qué pasa bajo el capó con el ejemplo que acabamos de montar. El usuario pregunta: "¿Cuál es la raíz cuadrada de la edad del presidente actual de Francia?"

Si le preguntas esto a un modelo "a pelo", alucinará o te dirá que no sabe la fecha de hoy. Pero fíjate cómo lo gestiona un agente ReAct paso a paso:

Iteración 1:

    Thought (Lo que piensa): "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".

    Action (Lo que hace): search_tool("Presidente de Francia actual")

    Observation (Lo que ve): "Emmanuel Macron."

Iteración 2:

    Thought: "Ok, ya tenemos el nombre: Macron. Ahora necesito su edad."

    Action: search_tool("Emmanuel Macron edad")

    Observation: "47 años."

Iteración 3:

    Thought: "Tengo el 47. El usuario quería la raíz cuadrada de esto."

    Action: calculator_tool("sqrt(47)")

    Observation: "6.8556..."

Respuesta Final: "La raíz cuadrada de la edad de Emmanuel Macron (47) es aproximadamente 6.85."
Enter fullscreen mode Exit fullscreen mode

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

Ahora vamos al codigo

Usaremos Agno, para crear nuestro ejemplo del patron ReAct, implementando 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. Global Logging and Error Handling Configuration
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):
    """Captures unhandled exceptions and records them in the log."""
    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. Environment Variables Loading
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. Tool Definitions
def calculate(expression: str) -> str:
    """
    Solves a simple mathematical expression (addition, subtraction, multiplication, division).
    Useful for calculating year or date differences.

    Args:
        expression (str): The mathematical expression to evaluate (e.g., "2024 - 1789").
    """
    try:
        # Allow only safe characters for basic eval
        allowed_chars = "0123456789+-*/(). "
        if all(c in allowed_chars for c in expression):
            result = eval(expression)
            return f"Result: {result}"
        else:
            return "Error: Disallowed characters in expression."
    except Exception as e:
        return f"Error while calculating: {str(e)}"

# 4. Agno Agent Configuration (ReAct Pattern)
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. User Interface
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")
            agent.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()

Enter fullscreen mode Exit fullscreen mode

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

  • PENSAR (Think): Se define en las instructions del agente.
    Código: instructions=["You are a researcher using the ReAct method...", "Think step-by-step...", "Show your reasoning..."].
    Esto obliga al LLM a generar un "Thought Trace" (monólogo interno) antes de hacer nada.

  • ACTUAR (Act): El agente tiene capacidades externas definidas en tools.
    Código: tools=[TavilyTools(), calculate].
    Si necesita datos, usa Tavily; si necesita matemáticas, usa calculate.

  • OBSERVAR (Observe): El framework (Agno) ejecuta la herramienta y le devuelve el resultado al agente.
    Código: Ocurre automáticamente dentro de la clase Agent. El agente "lee" el retorno de calculate (ej: "Result: 6.85").

  • REPETIR (Repeat): El bucle continúa hasta que el agente tiene suficiente información.
    Código: agent.print_response(..., show_tool_calls=True)
    El parámetro show_tool_calls=True es vital: te permite ver en la consola cómo el agente "dispara" las acciones en tiempo real.

¿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 busca "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.

No todo es color de rosa (Contras)

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.

Consejos de trinchera (Ingeniería)

Si vas a poner esto en producción (usando Agno, LangChain o lo que sea), grábate esto a fuego:

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.

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.

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.

¿Dónde se usa esto en la vida real?

Agentes de Programación (tipo Devin o Copilot): El flujo es: "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! 🤖

Top comments (0)