ReAct Agent framework: Agents before they were cool
/ 6 min read
Last Updated:Introduction
Agents are trending right now, but before all the hype, they had humble beginnings. The ReAct framework is one of the earliest agentic approaches introduced in the literature. It combines reasoning (the thought process) and action-taking (executing specific functions) within an LLM to handle multifaceted tasks. This synergy enables LLMs to generate reasoning traces and task-specific actions, maintain and update action plans, handle exceptions, and interact with external sources for additional information.
Overview
In the original paper1, the authors presented the idea of synergizing reasoning and action-taking in language models, making them more capable of handling several tasks.
ReAct is inspired by the human ability to learn and make decisions by combining acting and reasoning.
With this framework, you can implement additional actions that an LLM can take, such as running calculations or searching for specific information in a database, then teach it how to request that those actions are run, and finally feed the results back into the LLM.

Quick Example
Let’s see a quick example of how this model works before moving into the Python implementation. Suppose we have a function called calculate
, which will be our action:
def calculate(what):
return eval(what)
If we query the model, using the ReAct framework, with the following:
query("Fifteen * twenty five")
The behavior, reasoning, and action of the agent will be something like this:
Thought: The action required is a calculation
Action: calculate: 15 * 25
PAUSE
-- running calculate 15 * 25
Observation: 375
Answer: Fifteen times twenty five equals 375.
Returning the correct answer.
Implementation from Scratch
Now, let’s dive deeper into how to implement this model from scratch using pure Python. Just as a disclaimer, this is not a very robust implementation, but it is a useful way to explain how the framework works at a low level.
- Defining the Agent Class
The Agent class manages the three main responsibilities of an agent: capturing user messages, processing them, and producing answers.
class Agent:
def __init__(self, system=""):
self.system = system
self.messages = []
if self.system:
self.messages.append({"role": "system", "content": system})
def __call__(self, message):
self.messages.append({"role": "user", "content": message})
result = self.execute()
self.messages.append({"role": "assistant", "content": result})
return result
def execute(self):
completion = client.chat.completions.create(
model="gpt-4o",
temperature=0,
messages=self.messages
)
return completion.choices[0].message.content
- Constructing the Main Prompt
We will have two available actions for the model: calculate
and get_country_capitals
. In the master prompt, we include a few-shot prompt to teach the model how to act:
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.
Your available actions are:
calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary
get_country_capitals:
e.g. get_country_capitals: Argentina
returns the capital when given the country name
Example session:
Question: What's is the capital of Argentina?
Thought: I should look the country capitals using get_country_capitals
Action: get_country_capitals: Argentina
PAUSE
You will be called again with this:
Observation: Buenos Aires
You then output:
Answer: The capital of Argentina is Buenos Aires
""".strip()
- Defining the Actions
Next, we define the functions (tools) that the model can use:
def calculate(what):
return eval(what)
def get_country_capitals(country):
if country in "Argentina":
return "Buenos Aires"
elif country in "Spain":
return "Madrid"
elif country in "United States":
return "Washington D. C."
known_actions = {
"calculate": calculate,
"get_country_capitals": get_country_capitals
}
- Querying the Agent
Let’s create an agent and see how the thought and action steps work:
agentOne = Agent(prompt)
result = agentOne("What's the capital of Argentina?")
print(result)
Output:
Thought: I should look up the capital of Argentina using get_country_capitals.
Action: get_country_capitals: Argentina
We can also check the messages attribute to see the entire content that is being sent to the LLM:
agentOne.messages
Output (simplified):
[{'role': 'system', 'content': "You run in a loop of Thought, Action, PAUSE, Observation..."}, {'role': 'user', 'content': "What's the capital of Argentina?"}, {'role': 'assistant', 'content': 'Thought: I should look up the capital of Argentina using get_country_capitals.\nAction: get_country_capitals: Argentina'}]
- Completing the ReAct Loop
This is where the core part of the ReAct agent framework comes into play. We will define a loop that checks if the output contains an action. If it does, the loop calls the relevant function, appends the observation to the conversation, and allows the model to produce the final response.
action_re = re.compile('^Action: (\w+): (.*)$')
def query(question, max_turns=5):
i = 0
bot = Agent(prompt)
next_prompt = question
while i < max_turns:
i += 1
result = bot(next_prompt)
print(result)
actions = [
action_re.match(a)
for a in result.split('\n')
if action_re.match(a)
]
if actions:
# There is an action to run
action, action_input = actions[0].groups()
if action not in known_actions:
raise Exception("Unknown action: {}: {}".format(action, action_input))
print(" -- running {} {}".format(action, action_input))
observation = known_actions[action](action_input)
print("Observation:", observation)
next_prompt = "Observation: {}".format(observation)
else:
return
- Testing the Agent
Now, if we query the agent, the entire agentic flow should be completed:
question = "What's the capital of spain?"
query(question)
Output:
Thought: I should look up the capital of Spain using get_country_capitals.
Action: get_country_capitals: Spain
PAUSE
-- running get_country_capitals Spain
Observation: Madrid
Answer: The capital of Spain is Madrid.
Another example:
question = "How much is two plus two and the result divided by two?"
query(question)
Output:
Thought: I need to calculate the sum of two plus two and then divide the result by two.
Action: calculate: (2 + 2) / 2
PAUSE
-- running calculate (2 + 2) / 2
Observation: 2.0
Answer: Two plus two, divided by two, equals 2.0.
This is a simple, step-by-step implementation of the ReAct agent model in Python from scratch. In real-world scenarios, it is recommended to use more robust implementations, such as LangChain’s ReAct version2.
The full code can be found on this Google Colab.
This post was inspired by the following resources:
- A simple Python implementation of the ReAct pattern for LLMs3.
- DeepLearning.ai AI Agents in LangGraph course4.
Special thanks to my friend J. Gavernet for providing a thorough review of this post.