Home / Articles

Building a ReAct Agent in LangChain: Executors, Scratchpads and Their Limits

Build a tool-using ReAct agent, see how the agent and AgentExecutor split the work, and learn why LangGraph handles control flow, cycles and state better.

Publisher: REACTAPP.TOP12 min read2719 words

This article is published in English.

Sources: Langchain Part 17 — End to End Agent Building

What an agent is

An AI agent is a system that takes a high-level goal from a person and then plans, decides and acts on its own. It carries out a sequence of steps using outside tools, APIs or knowledge sources, keeps context along the way, reasons across several steps, adjusts when new information arrives and works toward the intended result.

Stripped down, an agent is an LLM plus some tools. The LLM does the reasoning and decision making, and the tools do the acting.

Traits of an agent

  • Goal-driven. You say what you want and not how to get it.
  • Plans by itself. The agent splits the problem up and orders the tasks without help.
  • Uses tools. It calls APIs, calculators, search and similar. The LLM knows which tools exist and when each one applies.
  • Context-aware. It keeps memory across steps so earlier results shape later actions.
  • Adaptive. When something changes, such as an API failing or data being missing, it revises the plan.

A working agent in code

The script below builds a small agent with two tools, web search through DuckDuckGo and a weather lookup, and asks it a two-part question. The weather call needs your own API key from the weather provider, shown here as a placeholder.

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
import requests

from langchain_community.tools import DuckDuckGoSearchRun

from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub # It is a place from where we can get many type of prompts, so if we want any predefined prompt, we can get it from here

# Step 1: Creating tool definitions
search_tool = DuckDuckGoSearchRun()

@tool
def get_weather_data(city: str) -> str:
  """
  This function fetches the current weather data for a given city
  """
  url = f'https://api.weatherstack.com/current?access_key=4d1d8ae207a8c845a52df8a67bf3623e&query={city}'
  response = requests.get(url)
  return response.json()

llm = ChatOpenAI()

# Step 2: Pull the ReAct prompt from LangChain Hub
prompt = hub.pull("hwchase17/react")  # pulls the standard ReAct agent prompt
# The agent which we are gonna make is a ReAct agent (reasoning + action = ReAct)
# ReAct is a design pattern

# Step 3: Create the ReAct agent manually with the pulled prompt
agent = create_react_agent(
    llm=llm,
    tools=[search_tool, get_weather_data],
    prompt=prompt
)

# Step 4: Wrap it with AgentExecutor
agent_executor = AgentExecutor(
    agent=agent,
    tools=[search_tool, get_weather_data],
    verbose=True # whatever the agent thinks, it will be visible to us
)

# Step 5: Invoke
response = agent_executor.invoke({"input": "Find the capital of Madhya Pradesh, then find it's current weather condition"})
print(response)

'''
Entering new AgentExecutor chain...
I should first find out the capital of Madhya Pradesh and then check the current weather condition for that city.
Action: duckduckgo_search
Action Input: "capital of Madhya Pradesh" Madhya Pradesh, state of India that is situated in the heart of the country. It has no coastline and no international frontier. Its physiography is characterized by low hills, extensive plateaus, and river valleys. The capital is Bhopal, in the west-central part of the state. Bhopal, city, capital of Madhya Pradesh state, central India. Situated in the fertile plain of the Malwa Plateau, the city lies just north of the Vindhya Range, along the slopes of a sandstone ridge. It is a major rail junction and has an airport. Pop. (2001) 1,437,354; (2011) 1,798,218. Bhopal was Indore (/ ɪ n ˈ d ɔːr / ⓘ; ISO: Indaura, Hindi: [ɪn̪d̪ɔːr]) is the largest and most populous city in the Indian state of Madhya Pradesh. [15] It is the commercial hub of Madhya Pradesh. It is consistently ranked as the cleanest city in India. [16] It serves as the headquarters of both the Indore District and the Indore Division.It is also considered the state education hub and ... In 1956, Bhopal became part of the state of Madhya Pradesh. Bhopal district was carved out on October 2, 1972, and is one of the 45 districts in the state. 4. Why is Bhopal famous? Bhopal is the capital city of the Indian state of Madhya Pradesh. It is known as the City of Lakes due to the presence of various natural and artificial lakes. In 1948, Madhya Bharat was created, with Indore designated as its summer capital and Gwalior as its winter capital. This arrangement highlights Indore's significance even then. However, Madhya Bharat was a temporary entity, and the map of central India was soon to be redrawn. The Creation of Madhya PradeshNow that I know the capital of Madhya Pradesh is Bhopal, I can use the get_weather_data function to check its current weather condition.
Action: get_weather_data
Action Input: Bhopal {'request': {'type': 'City', 'query': 'Bhopal, India', 'language': 'en', 'unit': 'm'}, 'location': {'name': 'Bhopal', 'country': 'India', 'region': 'Madhya Pradesh', 'lat': '23.267', 'lon': '77.400', 'timezone_id': 'Asia/Kolkata', 'localtime': '2025-05-01 17:52', 'localtime_epoch': 1746121920, 'utc_offset': '5.50'}, 'current': {'observation_time': '12:22 PM', 'temperature': 40, 'weather_code': 116, 'weather_icons': ['https://cdn.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0002_sunny_intervals.png'], 'weather_descriptions': ['Partly Cloudy '], 'astro': {'sunrise': '05:47 AM', 'sunset': '06:48 PM', 'moonrise': '08:38 AM', 'moonset': '11:01 PM', 'moon_phase': 'Waxing Crescent', 'moon_illumination': 15}, 'air_quality': {'co': '510.6', 'no2': '1.665', 'o3': '180', 'so2': '10.175', 'pm2_5': '29.045', 'pm10': '68.635', 'us-epa-index': '2', 'gb-defra-index': '2'}, 'wind_speed': 12, 'wind_degree': 302, 'wind_dir': 'WNW', 'pressure': 1005, 'precip': 0, 'humidity': 7, 'cloudcover': 25, 'feelslike': 40, 'uv_index': 1, 'visibility': 6, 'is_day': 'yes'}}The current weather condition in Bhopal, Madhya Pradesh is partly cloudy with a temperature of 40°C.
Final Answer: The capital of Madhya Pradesh is Bhopal, and the current weather condition in Bhopal is partly cloudy with a temperature of 40°C.

> Finished chain.
{'input': "Find the capital of Madhya Pradesh, then find it's current weather condition", 'output': 'The capital of Madhya Pradesh is Bhopal, and the current weather condition in Bhopal is partly cloudy with a temperature of 40°C.'}
'''

response['output']
# The capital of Madhya Pradesh is Bhopal, and the current weather condition in Bhopal is partly cloudy with a temperature of 40°C.

The five numbered steps in the script cover tool definitions, pulling the standard ReAct prompt from the LangChain hub, creating the agent, wrapping it in an executor and invoking it. The trace printed at the end shows the agent deciding to search for the capital first and checking the weather afterwards.

ReAct

LangChain supports several design approaches for agents. The one covered here is ReAct, short for Reasoning plus Acting. It lets a model alternate between internal reasoning, which is the Thought, and outward actions such as calling a tool, in a structured loop that spans several steps.

The model doesn't produce its answer in one go. It thinks step by step, decides what to do next and may call tools such as APIs, calculators or web search to help.

The pattern fits:

  • problems that take multiple steps;
  • tasks that need tools;
  • cases where you want the reasoning to be visible and auditable.

As an example, ask the model for the population of the capital of France. It first reasons that it must identify the capital, looks that up, sees that the answer is Paris, then reasons that it needs Paris's population, searches again and finally answers.

ReAct has five ingredients:

  • Thought. The agent's private reasoning, where it weighs the current state and plans the next move. It explains why a particular tool is chosen or what is still unknown.
  • Action. The decision to use one tool from the available toolkit, named by the function or API to call.
  • Action Input. The arguments passed to that tool. If the action is search, the input is the exact query text, something like the weather in Delhi.
  • Observation. The raw data the tool returns once the executor has run it. The agent reads it to judge whether the job is finished or more steps are needed.
  • Final Answer. The closing reply, produced when the agent concludes it has enough, which combines all the observations into plain language.

The prompt template that drives this loop looks like this:

Answer the following questions as best you can. You have access to the following tools:

{tools}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action …
(this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: {input}
Thought:{agent_scratchpad} —

In that template, an Observation is simply what comes back after a tool has run.

The agent scratchpad

The scratchpad works as the agent's short-term memory or working notebook. It records the history of the current task: earlier Thoughts, the Actions taken, such as tool calls, and the Observations that came back.

Reading the notebook, the agent remembers that it has already worked out the capital of France and now has to find the population of Paris. It's temporary storage, refreshed every time the executor returns new information. Once the final answer goes to the user, the scratchpad is normally cleared or archived because its job for that task is over.

The name makes sense once you recall that an LLM is stateless. It doesn't naturally remember what happened a few seconds ago in a code loop. LangChain therefore takes the entire history of the current loop and re-inserts it into the prompt each time it asks the agent what to do next.

Agent versus AgentExecutor

The two parts have separate jobs.

The agent is the thinker, a bit like a project manager. It reads the request, breaks it into smaller steps and decides which tools are needed. Its input contains both the user's question and everything done so far.

The AgentExecutor is the doer, more like an assistant. It listens to the agent's instructions, actually runs the code for each tool and hands the results back.

Another way to see it: the agent is the architect who draws the blueprint, and the executor is the construction crew that does the building. Without the agent there is no plan, and without the executor the plan never leaves the paper.

The executor orchestrates the whole loop:

  1. It sends the inputs and prior messages to the agent so the agent knows the question and what has happened.
  2. It receives the next action from the agent.
  3. It runs that tool with the supplied input.
  4. It appends the tool's observation to the history.
  5. It sends the updated history plus the user's question back to the agent, and this repeats until the agent says Final Answer.

Take a request for the temperature in Delhi multiplied by 10. The agent decides it needs the weather tool, the executor calls it, the observation returns, the agent decides a multiplication is needed, the executor performs it and the agent finally gives the answer. The executor carries out whatever the agent thinks.

Limits of this approach

The prebuilt executor works for simple cases, but it has real weaknesses once agents get more serious. The comparison below is with LangGraph, LangChain's graph-based library. Note also that newer LangChain releases steer people toward LangGraph for agents, so check the current documentation before building on the older executor classes.

1. The black box

The standard AgentExecutor is prebuilt. Give it tools and a prompt, and a while loop you can't see keeps going until it finishes. Stepping in is hard. If you want the agent to always ask a human before spending money, or to follow a fixed order such as step A then step B, the executor resists, because it is designed to be fully autonomous.

Consider an agent with search_flights and book_flight tools. Inside LangChain's executor, the LLM decides what happens next in a hidden loop. Asked to book a flight, it might search and book immediately without asking, perhaps for a ten-thousand-dollar fare when the user wanted to approve any purchase first. Intermediate steps like mandatory approval can't be strictly enforced, because the control flow is implicit and steered by the model.

In LangGraph the workflow is defined as explicit steps, or nodes, such as searching flights, asking for approval and booking. They're connected so the system must pass through approval before booking. That gives full control and makes it easy to enforce rules, add human checkpoints, retries or conditional branches.

2. Cycles and loops

Real workflows are rarely linear. They involve going back, fixing errors and looping over sets of documents.

  • LangChain's chains are mainly designed as directed acyclic graphs, so data flows one way.
  • LangGraph is built to handle cycles. It models the flow as a map in which nodes are functions and edges are the paths between them.

For a document question-answering task, a LangChain pipeline might retrieve documents, write an answer and return it. If the answer is wrong or incomplete, it doesn't naturally go back to retry retrieval or refine the query without extra hand-written logic. Once it moves forward, it doesn't easily circle back to improve itself.

LangGraph lets you draw loops explicitly: retrieve, answer, check quality, and if the result is poor, refine the query and retrieve again, repeating until it's good. The system can cycle between nodes until a condition is met, which suits tasks that need iteration, correction and feedback.

3. State and persistence

This matters most for scalability.

LangChain's memory mostly concerns conversation context, such as remembering earlier chat messages so the model answers better. It isn't deeply tied to the workflow, and it doesn't track progress through a task. Unless stored explicitly, it is typically lost when the system restarts, so long-running tasks aren't dependable.

LangGraph treats memory as the full state of the workflow and not just chat history. At each step it saves what was done, which outputs were produced and what comes next. If the process stops midway, it can resume exactly where it left off. It also allows pausing, inspecting and editing state with a human involved, or even returning to an earlier step, which makes complex multi-step jobs much sturdier.

Imagine a long task, such as processing ten documents one by one, that crashes at step 7. With no built-in persistence, everything is lost and you start from scratch, with no record of progress by default. With saved state after every node, you restart at step 7. You can also pause, let a person review or edit the state, then continue, or go back to an earlier step, a kind of time travel. Long workflows become reliable, resumable and easier to debug or audit.

The takeaway is that the basic executor makes it hard to build scalable, production-grade agents, which is why graph-based tooling exists for that job.