The ReAct Pattern – Reasoning + Acting

The ReAct pattern (short for Reasoning + Acting) is one of the most powerful and widely used patterns for building AI Agents. It was introduced in a 2022 research paper from Google and has since become the standard architecture for reasoning-capable agents.

ReAct teaches an agent to think out loud before acting — and this single idea dramatically improves agent accuracy, reliability, and transparency.

The Problem ReAct Solves

Without the ReAct pattern, an agent might rush to call a tool without fully thinking through the problem:

User: "Who was the CEO of Twitter before Elon Musk took over,
       and when did Elon Musk become CEO?"

Simple Agent (without ReAct):
  → Calls web_search("Twitter CEO Elon Musk")
  → Gets mixed results
  → Gives an incomplete or inaccurate answer

With ReAct, the agent first thinks through the problem:

ReAct Agent:
  Thought: "I need two pieces of info — who was CEO before Elon Musk, 
            and when did Elon Musk become CEO. Let me search for each separately."

  Action: web_search("Twitter CEO before Elon Musk")
  Observation: "Jack Dorsey was Twitter's co-founder and served as CEO..."

  Thought: "Good. Now I need the date Elon Musk became CEO."
  Action: web_search("Elon Musk Twitter CEO date")
  Observation: "Elon Musk became CEO on October 27, 2022 after completing acquisition..."

  Thought: "I now have both answers. I can give a complete response."
  Final Answer: "Jack Dorsey was the CEO before Elon Musk. Elon Musk took over 
                 as CEO on October 27, 2022, after completing his acquisition of Twitter."

The ReAct Format

ReAct works by structuring the agent's response in a specific format — alternating between Thought, Action, and Observation:

ComponentWhat It IsWritten By
ThoughtThe agent's reasoning about what to do nextLLM
ActionThe tool or function to call, with its inputLLM
ObservationThe result returned by the toolAgent Framework (injected back)
Final AnswerThe complete response to the userLLM

A Full ReAct Example

Question: "What is the capital of Australia, and what is the current population of that city?"

Thought: The user wants two things — the capital of Australia and that city's 
         population. Let me start by confirming the capital.

Action: web_search("capital of Australia")
Observation: "Canberra is the capital city of Australia."

Thought: The capital is Canberra. Now I need the current population of Canberra.

Action: web_search("Canberra population 2024")
Observation: "As of 2024, Canberra has an estimated population of approximately 
              470,000 people."

Thought: I now have both pieces of information. I can give the final answer.

Final Answer: The capital of Australia is Canberra. As of 2024, its population 
              is approximately 470,000 people.

Implementing ReAct from Scratch

ReAct can be implemented using prompt engineering — the system prompt instructs the LLM to think before acting:

# react_agent.py

import os
import re
import json
from dotenv import load_dotenv
import openai
from tools import TOOL_MAP

load_dotenv()
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

REACT_SYSTEM_PROMPT = """You are a smart research assistant using the ReAct framework.

For every task, follow this exact loop:

Thought: [Reason about what you know and what you need to find out]
Action: tool_name({"parameter": "value"})
Observation: [This will be filled in by the system after the tool runs]

Repeat Thought/Action/Observation as many times as needed.
When you have enough information to answer, respond with:

Final Answer: [Your complete, clear answer to the user's question]

Available Tools:
- web_search(query): Search the internet for information
- calculate(expression): Evaluate a mathematical expression

IMPORTANT: Always write a Thought before every Action. Never skip the reasoning step."""


def parse_action(text: str):
    """Extract tool name and arguments from the LLM's Action line."""
    # Match pattern: tool_name({"key": "value"})
    pattern = r'Action:\s*(\w+)\((\{.*?\})\)'
    match = re.search(pattern, text, re.DOTALL)
    if match:
        tool_name = match.group(1)
        try:
            tool_args = json.loads(match.group(2))
            return tool_name, tool_args
        except json.JSONDecodeError:
            return None, None
    return None, None


def run_react_agent(question: str) -> str:
    messages = [
        {"role": "system", "content": REACT_SYSTEM_PROMPT},
        {"role": "user",   "content": question}
    ]

    print(f"\nQuestion: {question}\n")

    for step in range(8):  # Max 8 ReAct steps
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            temperature=0.2,
            max_tokens=600,
            stop=["Observation:"]  # Stop before Observation — we will fill that in
        )

        llm_output = response.choices[0].message.content
        print(llm_output)

        # Check if agent has reached Final Answer
        if "Final Answer:" in llm_output:
            final = llm_output.split("Final Answer:")[-1].strip()
            print(f"\n{'='*50}")
            print(f"FINAL ANSWER: {final}")
            print(f"{'='*50}")
            return final

        # Parse the Action from LLM output
        tool_name, tool_args = parse_action(llm_output)

        if tool_name and tool_name in TOOL_MAP:
            # Execute the tool
            observation = TOOL_MAP[tool_name](**tool_args)
            print(f"Observation: {observation}\n")

            # Add LLM output + observation to conversation
            messages.append({"role": "assistant", "content": llm_output})
            messages.append({
                "role": "user",
                "content": f"Observation: {observation}"
            })
        else:
            # Could not parse an action — ask again
            messages.append({"role": "assistant", "content": llm_output})
            messages.append({
                "role": "user",
                "content": "Please provide an Action or a Final Answer."
            })

    return "Agent could not complete the task within the allowed steps."


# Test the ReAct agent
if __name__ == "__main__":
    run_react_agent("What is Python and who created it?")

Sample Console Output

Question: What is Python and who created it?

Thought: The user wants to know what Python is and who created it. 
         Let me search for this information.
Action: web_search({"query": "Python programming language creator history"})
Observation: {"result": "Python was created by Guido van Rossum and first released in 1991. 
               It is a high-level, general-purpose language known for its readability."}

Thought: I have enough information to answer both parts of the question.
Final Answer: Python is a high-level, general-purpose programming language known for its 
              clean syntax and readability. It was created by Guido van Rossum and first 
              released in 1991. Today it is widely used in data science, AI, web development, 
              and automation.

==================================================
FINAL ANSWER: Python is a high-level, general-purpose programming language...
==================================================

ReAct with Native Function Calling

Modern LLMs support function calling natively, which is a cleaner way to implement ReAct. The LLM automatically generates a structured tool call instead of outputting text that needs to be parsed:

# ReAct with native tool use (cleaner approach)
# The LLM outputs a structured tool_call object instead of text

response = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=TOOL_DEFINITIONS,
    tool_choice="auto"
)

# The Thought is internal to the model
# The Action is expressed as a tool_call object
# No regex parsing needed

if response.choices[0].message.tool_calls:
    # Tool call detected — run it and continue the loop
    ...
else:
    # No tool call — final answer reached
    final_answer = response.choices[0].message.content

When to Use Text-Based ReAct vs Native Function Calling

ApproachBest ForProsCons
Text-based ReActDebugging, transparency, older modelsShows full reasoning in textRequires regex parsing, more fragile
Native Function CallingProduction apps, modern LLMsReliable, structured, no parsingLess visible reasoning chain

Why ReAct Improves Agent Performance

  • Reduces errors: Thinking before acting prevents jumping to wrong conclusions
  • Improves transparency: The reasoning chain is visible and auditable
  • Handles complexity: Multi-step tasks are broken into logical sub-steps
  • Easier debugging: When something goes wrong, the thought trail shows exactly where
  • Better tool selection: Reasoning helps choose the right tool for each step

Summary

The ReAct pattern combines reasoning and acting into a structured loop: Thought → Action → Observation → repeat until Final Answer. This approach makes agents far more accurate and reliable than those that act without thinking. Whether implemented via text prompts or native function calling, ReAct is the most important architectural pattern in AI Agent development — and the foundation of all advanced agent frameworks covered in later topics.

Leave a Comment

Your email address will not be published. Required fields are marked *