Inicio / Artículos

ReAct en LangGraph: De un bucle creado manualmente a ToolNode y StateGraph

Crea un agente ReAct dos veces: primero como un bucle asíncrono manual, y luego con ToolNode de LangGraph, tools_condition y StateGraph que gestionan las llamadas a herramientas en cadena por ti.

Editor: REACTAPP.TOP6 min de lectura1391 palabras

Fuentes: Agentic AI with LangChain — Part 4: ReAct Pattern in LangGraph

Un bucle básico de llamada a herramientas realiza una sola pasada: el modelo solicita las herramientas, llegan los resultados y se emite una respuesta. Las tareas que requieren llamadas en cadena, donde la segunda llamada depende de lo devuelto por la primera, necesitan un agente capaz de pensar, actuar, analizar el resultado y volver a actuar. Ese ciclo es el patrón ReAct. A continuación se presenta la idea, un bucle ReAct escrito a mano y el mismo agente reconstruido en LangGraph con el nodo de herramienta, mensajes de herramienta, un borde condicional y un grafo de estado.

Los componentes que proporciona LangGraph para ello:

  • Nodo de herramienta: analiza las llamadas a herramientas, ejecuta dichas herramientas y devuelve los resultados.
  • Mensaje de herramienta: transmite la salida de una herramienta de vuelta al LLM.
  • tools_condition: una función de enrutamiento que verifica las llamadas a herramientas y activa el borde condicional.
  • StateGraph: ejecuta el flujo de trabajo.
  • Estado: un diccionario de texto que almacena los datos en constante cambio del agente.
  • Nodos: funciones de Python que actualizan el estado; un nodo de herramienta es uno de ellos.
  • Aristas: transiciones entre nodos, donde las aristas condicionales permiten ramificaciones y bucles.
  • Configuración

    Guarde los secretos en un archivo .env en la raíz del proyecto:

    OPENAI_API_KEY="<Your OpenAI API Key>"
    TAVILY_API_KEY=<Your TAVILY API Key>
    

    Cargúelos mediante una clase BaseSettings de Pydantic:

    class BaseConfig(BaseSettings):
        OPENAI_API_KEY: Optional[str]
        PINECONE_API_KEY: Optional[str]
        TAVILY_API_KEY: Optional[str]
    
    model_config = SettingsConfigDict(env_file=".env", extra="ignore")
    

    La estructura del proyecto:

    project/
    ├── react_loop/
    │   └── weather_assistant.py # implement a vanilla react loop
    │
    ├── react_langgraph     # implement ReAct by LangGraph workflow
    │   ├── nodes.py        # graph nodes. Defined as Python functions
    │   ├── graph.py        # graph workflow
    │   ├── consts.py       # define node names as constants
    │   └── state.py        # typed dictionary to store graph states
    |
    ├── tools
    │   ├── tool_call.py  # implement simple tool-calling loop
    |   └── weather.py    # get_weather LangChain tool using open source APIs
    |
    |──    # implement tool-calling loop using LangChain tool
    ├── config.py         # pydantic BaseConfig
    ├── utils.py          # utility method to print messages
    ├── .env              # environment variable definition
    ├── .gitignore
    └── requirements.txt  # package requirements
    

    Qué significa ReAct

    ReAct es la abreviatura de Reasoning plus Acting. Un agente que realiza una tarea en varios pasos razona, actúa, observa y vuelve a razonar hasta completarla. El bucle básico solo permite una sola ejecución de herramientas, y eso es lo que ReAct soluciona.

    Cada ejecución consta de seis pasos:

    1. El usuario envía un mensaje.
    2. El LLM lee la solicitud, determina qué información necesita y decide si debe usar herramientas y cuáles.
    3. El agente ejecuta una o más herramientas, como buscar en la web, consultar una API o enviar un correo electrónico.
    4. Los resultados regresan y se integran al estado de trabajo del agente.
    5. El LLM evalúa la nueva información y ya sea responde o solicita más ejecuciones de herramientas.
    6. Una vez que no se necesita ninguna acción adicional, se emite la respuesta final.

    El bucle es poderoso porque el agente puede revisar su plan según lo que acaba de aprender, por lo que cada decisión puede depender de resultados anteriores.

    Un bucle ReAct simple

    Esta versión se encuentra en react_loop/weather_assistant.py y se asemeja al bucle de un solo paso, con dos diferencias:

    async def chat(self, message: str):
        # Add user message
        self.messages.append(HumanMessage(content=message))
    
        # loop to handle serial tool calls
        while True:
    
            # Get AI response with potential tool calls
            response = await self.llm_with_tools.ainvoke(self.messages)
            self.messages.append(response)
    
            # Check if there is any tools to call
            if not response.tool_calls:
                # no tools to call
                break
                # Execute each tool call
    
            # process tool calls asynchronously
            results = await asyncio.gather(*[self.tools[tc["name"]].ainvoke(tc) for tc in response.tool_calls])
            self.messages.extend(results)
    

    Primero, las llamadas a herramientas se ejecutan de forma asíncrona con asyncio.gather, por lo que varias pueden realizarse en paralelo. Segundo, el paso de ejecución de la herramienta se encuentra dentro de un bucle while, repitiendo el proceso de razonamiento y acción hasta que la respuesta del modelo ya no contenga llamadas a herramientas.

    El mismo agente en LangGraph

    El bucle manual funciona, pero obliga a gestionar personalmente el ciclo de razonamiento y acción, la lista de mensajes, el diccionario de herramientas y la recopilación de resultados. LangGraph se encarga de cada uno de estos aspectos: ToolNode analiza las llamadas y ejecuta las herramientas, StateGraph almacena los mensajes en el estado del grafo, y el enlace tools_condition reemplaza al bucle while escrito a mano y su verificación de llamadas a herramientas.

    Estado. Un diccionario tipado almacena la lista de mensajes (en react_langgraph/state.py):

    from typing import TypedDict, Annotated
    from langgraph.graph.message import AnyMessage, add_messages
    
    class ReactState(TypedDict):
        """
        Define the state schema for our hotel assistant.
    
        Attributes:
            messages: list of messages
        """
    
        messages: Annotated[list[AnyMessage], add_messages]
    

    Los nodos leen y actualizan mensajes a través de él, y el reductor add_messages agrega todo lo que devuelve un nodo.

    Nodos. En react_langgraph/nodes.py la configuración crea una lista de herramientas de LangChain, un prompt del sistema que indica al modelo cómo utilizarlas, y un modelo de OpenAI vinculado a esa lista de herramientas:

    tools = [TavilySearch(max_results=3, tavily_api_key=TAVILY_API_KEY), get_weather]
    
    SYSTEM_PROMPT = f"""You are an assistant for question-answering tasks with access to multiple tools.
        use these tools to retrieve context to answer the question. If you don't know the answer,
        just say that you don't know. Use three sentences maximum and keep the answer concise.
    
        Tool Use Instructions:
    
        - Use tools thoughtfully—only when they add value to the response
        - Chain tools when needed (e.g., search for info, then use that to search
          for relevant hotels)
          - Be conversational and helpful
        """
    
    system_message = SystemMessage(content=SYSTEM_PROMPT)
    
    llm = ChatOpenAI(api_key=api_key, model="gpt-4o-mini", temperature=0)
    llm_with_tools = llm.bind_tools(tools)
    
    
    async def run_agent_reasoning(state: ReactState) -> dict:
        """
        run agent reasoning node
        :param state: ReactState, manage graph state
        :return: dict containing new message
        """
    
        print("----AGENT REASONING------")
        messages = [system_message] + state["messages"]
    
        if isinstance(messages[-1], ToolMessage):
            print_message(messages[-1])
    
        response = await llm_with_tools.ainvoke(messages)
        print_message(response)
    
        return {"messages": [response]}
    
    
    tool_node = ToolNode(tools)
    

    Se obtienen dos nodos. El nodo de razonamiento crea la lista de mensajes, llama al LLM y devuelve su respuesta. El nodo de acción es un ToolNode que analiza las llamadas a herramientas, ejecuta dichas herramientas y devuelve los mensajes correspondientes.

    Gráfico. En react_langgraph/graph.py, un StateGraph registra los nodos y los conecta mediante aristas:

    workflow = StateGraph(ReactState)
    
    workflow.add_node(AGENT_REASON, run_agent_reasoning)
    workflow.add_node(ACT, tool_node)
    
    workflow.add_edge(START, AGENT_REASON)
    workflow.add_edge(ACT, AGENT_REASON)
    
    workflow.add_conditional_edges(
        AGENT_REASON,
        tools_condition,
        {"tools": ACT, END: END}
    )tion is invoked first
    

    La ejecución sigue cuatro reglas. El grafo comienza en el nodo de razonamiento. Si la respuesta del LLM contiene llamadas a herramientas, tools_condition redirige al nodo de acción. Después de que las herramientas se ejecutan, el control siempre vuelve al razonamiento. Si la respuesta no contiene llamadas a herramientas, el grafo finaliza y se devuelve la respuesta final. Esto refleja el ciclo ReAct sin un bucle manual ni mensajes gestionados a mano.

    Compilar y ejecutar. La compilación genera el agente, y se puede exportar un diagrama del grafo como PNG:

    react_agent = workflow.compile()
    
    react_agent.get_graph().draw_mermaid_png(output_file_path="graphs.png")
    
    question = """
               tell me the first hotel you find in Boston if
                its temperature is lower than 30 degree."""
    
    
    async def main():
        print("hello ReAct agent by Langraph")
        message = HumanMessage(content=question)
        res = await react_agent.ainvoke({"messages": [message]})
    
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Úsalo con un mensaje del usuario y la lista de mensajes resultante mostrará los turnos entre el nodo de razonamiento y el nodo de herramienta, lo que constituye un flujo multi-paso funcional.

    Donde deja esto las cosas

    Tres características de LangGraph realizan esta tarea: ToolNode para encontrar y ejecutar herramientas, tools_condition para el control de bucles y add_messages para el manejo automático de mensajes. Una vez comprendido y desarrollado ReAct, el siguiente paso es volver a un pipeline RAG de LangGraph y hacerlo completamente agente.