The ReAct Loop
ReAct (Reason + Act) is the core agent pattern: the LLM reasons about what to do, calls a tool (acts), observes the result, and repeats until it has an answer. In LangGraph this is a cycle between two nodes.
1. agent node
LLM decides: answer directly or call a tool.
2. tools node
Execute the chosen tool and append the result to messages.
3. route
If tool calls remain, loop back. Otherwise END.
Defining Tools
Use the @tool decorator to turn any Python function into a LangChain tool. The docstring becomes the tool description the LLM reads.
from langchain_core.tools import tool
@tool
def search_web(query: str) -> str:
"""Search the web for current information. Use for recent events or facts."""
# In production: call an actual search API
return f"Search results for '{query}': [simulated result]"
@tool
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression. Input should be a valid Python expression."""
try:
result = eval(expression, {"__builtins__": {}})
return str(result)
except Exception as e:
return f"Error: {e}"
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
return f"Weather in {city}: 22°C, partly cloudy"
tools = [search_web, calculate, get_weather]
ToolNode
ToolNode is a prebuilt LangGraph node that reads tool call requests from the last AI message, executes them in parallel, and returns ToolMessage results.
from langgraph.prebuilt import ToolNode
tool_node = ToolNode(tools)
# ToolNode reads state["messages"][-1].tool_calls
# executes each tool, and returns {"messages": [ToolMessage, ...]}
Building the Agent Graph
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools)
def agent(state: MessagesState):
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
tool_node = ToolNode(tools)
builder = StateGraph(MessagesState)
builder.add_node("agent", agent)
builder.add_node("tools", tool_node)
builder.add_edge(START, "agent")
# tools_condition: returns "tools" if last message has tool_calls, else END
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent") # after tool execution, reason again
graph = builder.compile()
# Run the agent
result = graph.invoke({
"messages": [("user", "What is 17 * 23, and what's the weather in Paris?")]
})
print(result["messages"][-1].content)
Persistent Memory with Checkpointers
By default LangGraph graphs are stateless — each .invoke() starts fresh. Add a checkpointer to persist state between calls, enabling multi-turn conversations.
from langgraph.checkpoint.memory import MemorySaver
# In-memory checkpointer (lost on process restart)
memory = MemorySaver()
graph = builder.compile(checkpointer=memory)
# thread_id groups messages into a conversation
config = {"configurable": {"thread_id": "user-42-session-1"}}
# Turn 1
result = graph.invoke(
{"messages": [("user", "My name is Alice.")]},
config=config,
)
print(result["messages"][-1].content)
# Turn 2 — the graph remembers the full history
result = graph.invoke(
{"messages": [("user", "What is my name?")]},
config=config,
)
print(result["messages"][-1].content) # "Your name is Alice."
In-process dict. Fast, zero deps. Use for dev and testing.
SqliteSaver / PostgresSaver
Durable, survives restarts. Use in production APIs.
create_react_agent Shortcut
LangGraph provides create_react_agent to build the complete ReAct agent graph in one line — use it when you don't need to customize the graph structure.
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(
model=llm,
tools=tools,
checkpointer=MemorySaver(),
# Optional: system prompt
state_modifier="You are a helpful assistant. Be concise.",
)
config = {"configurable": {"thread_id": "session-1"}}
result = agent.invoke(
{"messages": [("user", "Search for the latest Python release")]},
config=config,
)
print(result["messages"][-1].content)
When to use each: Use create_react_agent for standard tool-use agents. Build the graph manually — as covered in LangGraph Basics — when you need custom routing, multi-agent orchestration, or human-in-the-loop interrupts.
Inspecting Graph State
# Get the full state snapshot at any point
snapshot = graph.get_state(config)
print(snapshot.values["messages"]) # all messages so far
print(snapshot.next) # which node runs next (if interrupted)
# Get state history (all checkpoints for a thread)
for state in graph.get_state_history(config):
print(state.config["configurable"]["checkpoint_id"])
print(len(state.values["messages"]), "messages")
LangGraph Agents FAQ
How do I build a tool-calling agent in LangGraph?
Define your tools with the @tool decorator, bind them to a chat model with llm.bind_tools(tools), and build a StateGraph with two nodes: an agent node that calls the model and a tools node (ToolNode) that executes the requested tools. Add a conditional edge from the agent using tools_condition so the graph routes to the tools node when there are tool calls and to END otherwise, then loop the tools node back to the agent.
What is a ReAct agent in LangGraph?
A ReAct (Reason + Act) agent is the core agent pattern where the LLM reasons about what to do, calls a tool (acts), observes the tool result, and repeats until it can answer. In LangGraph this is a cycle between an agent node and a tools node, with a conditional edge deciding whether to call another tool or finish.
What does create_react_agent do in LangGraph?
create_react_agent is a prebuilt helper from langgraph.prebuilt that builds the complete ReAct agent graph in one line. You pass it a model and a list of tools, and optionally a checkpointer and a system prompt via state_modifier. Use it for standard tool-use agents when you do not need to customize the graph structure.
How does a LangGraph agent loop between the LLM and tools?
The agent node invokes the LLM and appends the response to the messages state. A conditional edge with tools_condition checks the last message: if it contains tool_calls, the graph routes to the ToolNode, which executes the tools and appends ToolMessage results; the edge from the tools node then loops back to the agent so it can reason again. When the LLM returns a message with no tool calls, the graph routes to END.
When should I use a prebuilt agent versus a custom LangGraph graph?
Use the prebuilt create_react_agent for standard tool-calling agents where the default ReAct loop is enough. Build the StateGraph manually when you need custom routing, multi-agent orchestration, human-in-the-loop interrupts, or extra nodes and state fields beyond the message list.
How do I add memory to a LangGraph agent?
Attach a checkpointer when you compile the graph, for example builder.compile(checkpointer=MemorySaver()). Pass a config with a configurable thread_id on each invoke so messages are grouped into one conversation and the agent remembers prior turns. Use MemorySaver for development and a durable saver such as SqliteSaver or PostgresSaver in production.