Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 18

Building ReAct Agents with LangGraph

Recommended resources

Hello! In our last lesson, we set up a complete LangGraph development environment and built a simple, linear chatbot. This gave us a solid foundation and verified that all the pieces of our setup are working correctly.

Today, we're taking a significant leap forward. We'll transform that simple chatbot into a true agent by implementing the ReAct (Reasoning and Acting) pattern. You will learn how to structure a LangGraph state graph to create a loop, allowing the agent to call tools to gather information, process the results, and decide on its next steps. This is a fundamental pattern for building agents that can interact with the world and solve complex problems.

By the end of this lesson, you will be able to implement this core agentic loop from scratch, giving you a deep understanding of how LangGraph orchestrates agent behavior.

From Linear Flow to an Agentic Loop

Our previous chatbot had a very simple, linear structure: START -> call_llm -> END. The agent could talk, but it couldn't do anything. The ReAct pattern changes this by introducing a cycle.

Simplified ReAct Agent Loop Flow Diagram
This diagram shows the basic flow of a ReAct agent. The process starts by calling the model (the "brain"). The model then decides whether to use a tool or provide a final answer. If it uses a tool, the flow loops back to the model to process the tool's output. This "think, act, observe" cycle continues until the task is complete.

From your experience as a software engineer, you can think of this as a state machine. The "state" is the accumulated conversation and data, and the LLM's output determines the next transition: either call a tool, or exit the loop. LangGraph allows us to define this state machine explicitly.

To build this, we need four key components:

  1. State: A central object to hold the conversation history (user questions, tool calls, tool results).
  2. Agent Node: A function that calls the LLM to "reason" and decide on the next step.
  3. Tool Node: A function that "acts" by executing the tool the agent decided to use.
  4. Conditional Edge: Logic that directs the flow, creating the loop between the Agent and Tool nodes.

Let's build this step-by-step. The following guide provides the code we'll be working with. We'll walk through its essential parts.

ReAct agent from scratch with Gemini 1.5 and LangGraph

The Google AI team provides an excellent tutorial titled 'ReAct agent from scratch with Gemini 1.5 and LangGraph'. We will use it as our primary reference for the code implementation. It clearly lays out all the components we need.

You don't need to read this entire document at once. I will guide you through the relevant code snippets as we proceed. Have it open as a reference.


Step 1: Defining the Agent's State and Tools

First, we need to set up the agent's state and give it a tool to use. We'll continue using the project you created in the last lesson.

State Definition

The state will be nearly identical to our last lesson. We use a TypedDict with a messages key. The add_messages helper function is crucial here, as it ensures that new messages are appended to the list, maintaining the full conversation history for the agent's context.

from typing import Annotated, Sequence, TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    """The state of the agent."""
    messages: Annotated[Sequence[BaseMessage], add_messages]

This code is from the "Define AgentState" section of the Google AI tutorial.

Tool Definition

An agent is only as capable as its tools. Let's create a simple tool that gets the weather. We define a standard Python function and use the @tool decorator from LangChain to make it available to our agent. The docstring is very important, as the LLM uses it to understand what the tool does and when to use it.

After defining the tool, we bind it to our LLM. This modifies the LLM so that it's aware of the tool and can generate the appropriate function-calling JSON when it decides to use it.

For this example, we'll switch to an OpenAI model as they currently have very reliable tool-calling capabilities. Make sure you have your OPENAI_API_KEY in your .env file and have run uv add langchain-openai.

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

# 1. Define the tool
@tool
def get_weather(city: str):
    """Call to get the weather in a specific city."""
    # This is a placeholder for a real API call.
    if "san francisco" in city.lower():
        return "It is currently 20 degrees Celsius and sunny in San Francisco."
    elif "tokyo" in city.lower():
        return "It is currently 25 degrees Celsius and rainy in Tokyo."
    else:
        return f"I don't have the weather for {city}."

# 2. Create a list of tools
tools = [get_weather]

# 3. Initialize the model and bind the tools
llm = ChatOpenAI(model="gpt-4o")
model_with_tools = llm.bind_tools(tools)

This setup is adapted from the LangChain documentation guide "How to create a ReAct agent from scratch" (LINK).


Step 2: Creating the Nodes and Edges

Now for the core logic of the graph: the nodes that do the work and the edges that connect them.

Node 1: The Agent/Reasoner (call_model)

This node is the agent's "brain." It takes the current state (message history) and calls the LLM. The LLM's response will either be a natural language answer or a tool_calls object instructing the graph to execute a tool.

def call_model(state: AgentState):
    """Calls the LLM to decide the next step or generate a final response."""
    response = model_with_tools.invoke(state["messages"])
    # The response is added to the messages list in the state
    return {"messages": [response]}

This function is adapted from the call_model node in the tutorial (LINK).

Node 2: The Action/Tool Executor (call_tool)

This node is the "hands" of the agent. It checks the last message in the state for any tool call requests. If it finds one, it executes the corresponding Python function and returns the result wrapped in a ToolMessage. This result becomes the "observation" that the agent will use in its next reasoning step.

from langchain_core.messages import ToolMessage

# Create a dictionary to easily look up tools by name
tools_by_name = {tool.name: tool for tool in tools}

def call_tool(state: AgentState):
    """Executes the tool call requested by the LLM."""
    last_message = state["messages"][-1]
    outputs = []
    for tool_call in last_message.tool_calls:
        tool_result = tools_by_name[tool_call["name"]].invoke(tool_call["args"])
        outputs.append(
            ToolMessage(
                content=str(tool_result),
                name=tool_call["name"],
                tool_call_id=tool_call["id"],
            )
        )
    return {"messages": outputs}

This function is adapted from the call_tool node in the tutorial (LINK).

The Conditional Edge (should_continue)

This function directs the traffic in our graph. It inspects the last message from the call_model node.

  • If the message contains tool_calls, it means the agent wants to act. The function returns "continue", directing the graph to the call_tool node.
  • If there are no tool calls, the agent has provided its final answer. The function returns "end", terminating the graph execution.
def should_continue(state: AgentState):
    """Determines the next step: continue with tools or end."""
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "continue"
    return "end"

This function is from the should_continue edge in the tutorial (LINK).


Step 3: Assembling the Graph

With our state, nodes, and conditional edge defined, we can now assemble the StateGraph.

from langgraph.graph import StateGraph, END

# Define a new graph with our state
workflow = StateGraph(AgentState)

# 1. Add our nodes
workflow.add_node("agent", call_model)
workflow.add_node("tools", call_tool)

# 2. Set the entrypoint
workflow.set_entry_point("agent")

# 3. Add the conditional edge
workflow.add_conditional_edges(
    "agent",           # The edge starts from the 'agent' node
    should_continue,   # The function to decide the path
    {
        "continue": "tools", # If it returns "continue", go to "tools"
        "end": END           # If it returns "end", finish
    },
)

# 4. Add the edge that creates the loop!
workflow.add_edge("tools", "agent")

# 5. Compile the graph
graph = workflow.compile()

The most important line here is workflow.add_edge("tools", "agent"). This is what closes the loop. After the tools node executes, the graph flows back to the agent node, passing the tool's output so the agent can reason about it.


Step 4: Running the Agent

Let's run our new agent and see the ReAct loop in action. We can use graph.stream() to see each step of the agent's thought process.

# To see the output in a nice format
from langchain_core.messages import HumanMessage

inputs = {"messages": [HumanMessage(content="What is the weather like in San Francisco?")]}

for output in graph.stream(inputs):
    # stream() yields dictionaries with output from each node
    for key, value in output.items():
        print(f"Output from node '{key}':")
        print("---")
        print(value)
    print("\n---\n")

When you run this, you will see a sequence of outputs:

  1. The agent node will output an AI message containing a tool_call for get_weather.
  2. The tools node will output a ToolMessage with the weather information.
  3. The agent node will run again, this time generating the final, human-readable answer.

To solidify your understanding of this flow, let's watch a video that walks through this exact process.

Building Effective Agents with LangGraph

This video from the LangChain team builds the exact ReAct agent loop we've just coded. Hearing it explained will reinforce your understanding of the message flow and the role of each component.

Watch the segment from 23:13 to 29:42. Pay close attention to how the presenter explains the three key parts: The LLM node that can produce a tool call. The tool_node that executes the tool and provides the "environmental feedback" (the tool's output). The conditional logic that decides whether to loop back or end the process. This narration directly maps to the code we just wrote.

Test your understanding!

Looking at the graph assembly code, what would happen if you changed the final edge from workflow.add_edge("tools", "agent") to workflow.add_edge("tools", END)? How would this limit the agent's capabilities?

Show answer

This change would break the ReAct loop. The agent could decide to call a tool, and the tool would execute, but the graph would terminate immediately after. The agent would never get to "observe" the tool's output to reason further, formulate a final answer, or decide to call another tool. This would limit the agent to a single "thought -> action" step, preventing multi-step problem-solving which is a key strength of the ReAct pattern.


Conclusion

Congratulations! You have successfully implemented a ReAct-style agent from scratch using LangGraph. You've gone from a simple linear chain to a dynamic, looping graph that can reason, act, and observe.

Key Takeaways:

  • The ReAct pattern is implemented in LangGraph with a loop between a "reasoner" node (LLM call) and an "action" node (tool execution).
  • StateGraph's conditional edges (add_conditional_edges) are the mechanism for routing the flow based on the LLM's output (i.e., whether it decided to call a tool).
  • The edge connecting the tool node back to the agent node is what creates the powerful "think, act, observe" cycle.
  • The messages list in the state acts as the agent's short-term memory, accumulating the entire thought process and providing essential context for each reasoning step.

You now have a robust template for a tool-using agent. In our next lesson, we will build on this by adding multiple tools, forcing the agent to not only decide if it should act, but which tool is the right one for the job.