DS DevShelfHub Projects · AI tools
Tutorials / LangChain / LangGraph Agents
LangGraph Advanced · 11 min read Page 14 of 20

LangGraph Agents: Stateful Tool-Calling Agents in Python

By DevShelfHub

Turn a graph into a tool-calling agent: bind tools to an LLM, use ToolNode to execute them, implement the ReAct loop, and add persistent memory with checkpointers.

Series progress14 / 20
LangGraph agents — define tools, run the ReAct loop with ToolNode, and add memory with checkpointers

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.

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

python
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

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

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

MemorySaver

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.

python
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

python
# 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.

Quick jump: API Reference