LangGraph — Módulo 5: tools y el patrón ReAct
Quinto módulo del curso de LangGraph: conecta un LLM real, dale herramientas con bind_tools, ejecútalas con ToolNode y arma el ciclo ReAct con tools_condition. Incluye el atajo create_agent.
☰ Índice del contenido
Arranca el bloque intermedio del Curso completo de LangGraph. En los cuatro módulos anteriores construimos grafos con funciones normales de Python: sin LLM, sin API keys. Eso fue a propósito — queríamos que entendieras la mecánica antes de meter magia. Hoy entra el modelo, y con él las tools: las manos del agente.
La tesis del módulo
Un LLM solo puede hablar. Una tool le permite actuar. El patrón ReAct es el ciclo que une ambas cosas: el modelo piensa, pide una herramienta, ve el resultado y vuelve a pensar — hasta que ya no necesita más. En LangGraph ese ciclo es literalmente un ciclo del grafo: exactamente lo que aprendiste en el Módulo 4.
Qué es una tool
Una tool (herramienta) es una función de Python que el modelo puede pedir que se ejecute: buscar en una base de datos, consultar una API, calcular algo, enviar un correo.
Lo importante: el modelo no ejecuta nada. El modelo solo pide. Devuelve un mensaje que dice “quiero llamar a buscar_pedido con el argumento id=4821”. Ejecutar esa petición es trabajo de tu código — y ahí es donde entra LangGraph.
Se declara con el decorador @tool:
from langchain.tools import tool
@tool
def consultar_stock(producto: str) -> str:
"""Consulta el stock disponible de un producto en bodega."""
inventario = {"teclado": 12, "monitor": 0, "mouse": 47}
cantidad = inventario.get(producto.lower())
if cantidad is None:
return f"No encontré el producto '{producto}'."
return f"Hay {cantidad} unidades de {producto}."
Tres cosas que el modelo lee de esa función y que por lo tanto importan mucho:
- El nombre (
consultar_stock) — le dice qué hace. - El docstring (
"""Consulta el stock...""") — es la descripción que el modelo usa para decidir cuándo llamarla. Un docstring vago produce un agente que llama tools al azar. - Los tipos (
producto: str) — definen el esquema de argumentos que el modelo debe rellenar.
El docstring de una tool no es documentación para ti: es prompt para el modelo. Escríbelo pensando en eso.
Paso 1: darle las tools al modelo
bind_tools le entrega al modelo la lista de herramientas disponibles. Devuelve una versión del modelo que ya sabe que existen:
from langchain.chat_models import init_chat_model
llm = init_chat_model("anthropic:claude-sonnet-4-6")
tools = [consultar_stock]
llm_con_tools = llm.bind_tools(tools)
Cuando invocas llm_con_tools, el mensaje que devuelve puede traer un atributo .tool_calls: la lista de herramientas que el modelo quiere usar, con sus argumentos ya rellenados.
Paso 2: el nodo que llama al modelo
from langgraph.graph import MessagesState
from langchain.messages import SystemMessage
def asistente(state: MessagesState) -> dict:
respuesta = llm_con_tools.invoke(
[SystemMessage(content="Eres un asistente de bodega. Sé breve.")]
+ state["messages"]
)
# Devolvemos SOLO el mensaje nuevo: add_messages lo acumula (Módulo 3).
return {"messages": [respuesta]}
Usamos MessagesState, el estado prehecho con el reducer add_messages que vimos en el Módulo 3. Aquí es donde ese módulo empieza a pagar.
Paso 3: el nodo que ejecuta las tools
Podrías escribir a mano el bucle que recorre .tool_calls, ejecuta cada función y arma un ToolMessage con el resultado. Pero LangGraph ya trae ese nodo hecho: ToolNode.
from langgraph.prebuilt import ToolNode
herramientas = ToolNode(tools)
Eso es todo. ToolNode recibe el estado, lee el último mensaje, ejecuta todas las tool calls que encuentre (en paralelo si son varias), captura errores y devuelve los resultados como ToolMessage en messages.
Paso 4: el ciclo ReAct
Ahora la pieza que conecta todo. Después del nodo asistente hay que decidir:
- ¿El modelo pidió una tool? → ir a
tools, ejecutarla, y volver al asistente con el resultado. - ¿No pidió nada? → terminar.
Eso es una arista condicional, exactamente como en el Módulo 4. Y LangGraph trae el router ya escrito: tools_condition.
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition
builder = StateGraph(MessagesState)
builder.add_node("asistente", asistente)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "asistente")
# Si el último mensaje trae tool_calls → "tools". Si no → END.
builder.add_conditional_edges("asistente", tools_condition)
# Y la arista que cierra el ciclo: tras ejecutar, volver a pensar.
builder.add_edge("tools", "asistente")
agente = builder.compile()
El grafo:
┌──────────────┐
▼ │
START ──► asistente ──► ? ──► tools ────────┘
│
└──(sin tool_calls)──► END
Esa flecha que vuelve de tools a asistente es el patrón ReAct. Razonar → actuar → observar → razonar. El agente da tantas vueltas como necesite.
respuesta = agente.invoke({
"messages": [{"role": "user", "content": "¿Queda stock de monitor?"}]
})
print(respuesta["messages"][-1].content)
# → No queda stock de monitor: hay 0 unidades en bodega.
Detrás de esa única línea pasaron cuatro mensajes: tu pregunta, la petición de tool del modelo, el resultado de consultar_stock, y la respuesta final. Puedes verlos todos recorriendo respuesta["messages"].
Ojo con el
recursion_limit. Este es un ciclo real, y aplica el tope de 25 pasos del Módulo 4. Un agente que se queda pegado llamando la misma tool una y otra vez casi siempre tiene un docstring confuso o una tool que devuelve algo que el modelo no entiende como respuesta.
El atajo: create_agent
Este patrón es tan universal que existe una función que arma ese grafo completo por ti:
from langchain.agents import create_agent
agente = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[consultar_stock],
)
agente.invoke({"messages": [{"role": "user", "content": "¿Queda stock de mouse?"}]})
Una línea, mismo comportamiento. Y como lo que devuelve es un grafo compilado normal, sigue soportando todo lo que viene en los próximos módulos: checkpointers, interrupts, streaming.
Nota de versión: en LangChain v1 esta función se llama
create_agent. Si sigues un tutorial más antiguo veráscreate_react_agentimportado desdelanggraph.prebuilt: es la versión previa de la misma idea.
Entonces, ¿por qué aprendimos a armarlo a mano? Porque create_agent es una caja. Sirve mientras tu agente sea un ciclo modelo↔tools estándar. En cuanto necesites meter un paso de validación entre medio, un nodo de aprobación humana o una ruta alternativa, vuelves al grafo explícito — y ahí ya sabes exactamente qué estás modificando.
Errores comunes
- Docstring vago o ausente. Es la descripción que el modelo usa para elegir. Sin docstring, el agente adivina.
- Olvidar
add_edge("tools", "asistente"). Sin esa arista el agente ejecuta la tool y muere sin usar el resultado. Es el error más común del módulo. - Usar
llmen vez dellm_con_toolsdentro del nodo. Si no invocas el modelo con las tools enlazadas, nunca va a pedir ninguna. - Tools que devuelven objetos raros. El resultado va al modelo como texto: devuelve strings claros, no dumps de objetos.
- Efectos secundarios sin control. Una tool que borra registros o cobra dinero no debería ejecutarse sin supervisión. Eso lo resolvemos en el Módulo 7.
La frase para llevarse
“El modelo no ejecuta tools: las pide. Tu grafo es quien decide si obedecer — y esa distinción es todo el control que tienes sobre un agente.”
Qué viene
En el Módulo 6 resolvemos algo que este agente todavía no puede hacer: recordar. Hoy cada invoke empieza de cero. Con los checkpointers y los threads, el agente mantiene la conversación entre turnos y puede retomar donde quedó.
Material relacionado
Preguntas sobre este curso
Regístrate gratis para hacer preguntas y guardar tu progreso.
✉ Entérate de lo nuevo
Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.