Developing Intelligent Agents Using the ReAct Framework

AI agents have become a cornerstone of modern applications, powering everything from chatbots to autonomous vehicles. At the heart of many advanced agents lies the ReAct pattern — a reasoning-acting loop that enables a language model to interact with external tools. This article walks you through building such an agent from scratch using Python and the OpenAI API.

Core Concepts of Agent Operations

An agent perceives its environment through sensors, processes data, and acts via effectors to achieve predefined goals. The ReAct pattern structures this as a continuous cycle: Thought → Action → Pause → Observation → Answer. First, the agant decides what to do (Thought), executes a tool (Action), waits for the result (Pause), processes the result (Observation), and finally produces a response (Answer). This loop dramatically expands what a language model can accomplish, letting it retrieve real‑time data, perform calculations, and query external services.

Tools and Libraries

We'll use the following Python packages:

  • openai — to call GPT‑3.5 or GPT‑4 models.
  • httpx — for making asynchronous HTTP requests to external APIs.
  • re (built‑in) — for parsing the model’s output with regular expressions.

Setting Up the Environment

Begin by creating a virtual environment and installing the dependencies:

python -m venv ai_env
source ai_env/bin/activate   # Windows: ai_env\Scripts\activate
pip install openai httpx

Store your OpenAI API key in an environment variable (e.g., OPENAI_API_KEY) and load it in your script:

import os
import openai

openai.api_key = os.getenv('OPENAI_API_KEY')

Building the Agent Core

The agent itself can be encapsulated in a class that maintains a conversation history and interacts with the API:

class TaskAgent:
    def __init__(self, system_prompt=""):
        self.system_prompt = system_prompt
        self.history = []
        if self.system_prompt:
            self.history.append({"role": "system", "content": system_prompt})

    def respond(self, user_input):
        self.history.append({"role": "user", "content": user_input})
        response_text = self._call_api()
        self.history.append({"role": "assistant", "content": response_text})
        return response_text

    def _call_api(self):
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=self.history
        )
        return response.choices[0].message.content

TaskAgent handles the conversation and retrieves completions. Next, we infuse it with the ReAct loop.

Implementing the ReAct Loop

We supply a detailed system prompt that describes the Thought/Action/Pause/Observation cycle and lists available tools. Here’s a modified version:

REACT_PROMPT = """
You operate in a loop of Thought, Action, PAUSE, Observation.
After an Observation you may continue thinking and acting, or output an Answer.
Available actions:
- lookup_wiki: e.g. lookup_wiki: Albert Einstein
  Retrieves a Wikipedia snippet.
- search_blogs: e.g. search_blogs: React patterns
  Searches a specific blog for the given term.
- compute: e.g. compute: 15 * 3 - 2
  Evaluates a Python expression (use floating-point syntax).

Example:
Question: What is the boiling point of water?
Thought: I need to find the boiling point of water.
Action: lookup_wiki: boiling point of water
PAUSE

Observation: The boiling point of water is 100 °C at sea level.

Answer: The boiling point of water is 100 °C.
"""

The prompt clearly instructs the model to interleave reasoning and tool usage. We then define the actual tool implementations.

Tool Implementations

import httpx

def lookup_wiki(term):
    resp = httpx.get("https://en.wikipedia.org/w/api.php", params={
        "action": "query",
        "list": "search",
        "srsearch": term,
        "format": "json"
    })
    data = resp.json()
    return data["query"]["search"][0]["snippet"]

def search_blogs(term):
    resp = httpx.get("https://datasette.simonwillison.net/simonwillisonblog.json", params={
        "sql": """
          select
            blog_entry.title || ': ' || substr(html_strip_tags(blog_entry.body), 0, 1000) as text
          from
            blog_entry join blog_entry_fts on blog_entry.rowid = blog_entry_fts.rowid
          where
            blog_entry_fts match escape_fts(:q)
          order by
            blog_entry_fts.rank
          limit 1
        """,
        "_shape": "array",
        "q": term,
    })
    return resp.json()[0]["text"]

def compute(expr):
    return eval(expr)

Orchestrating the Loop

The run_agent function manages the cycle, parsing the model’s responses for actions and feeding observations back:

import re

action_pattern = re.compile(r'^Action: (\w+): (.*)')

known_tools = {
    "lookup_wiki": lookup_wiki,
    "search_blogs": search_blogs,
    "compute": compute,
}

def run_agent(question, max_steps=5):
    agent = TaskAgent(REACT_PROMPT)
    next_prompt = question
    for step in range(max_steps):
        output = agent.respond(next_prompt)
        print(output)
        actions = [action_pattern.match(line) for line in output.split('\n') if action_pattern.match(line)]
        if not actions:
            return output  # final answer assumed
        action_name, action_arg = actions[0].groups()
        if action_name not in known_tools:
            raise ValueError(f"Unknown action: {action_name}")
        observation = known_tools[action_name](action_arg)
        print(f"Observation: {observation}")
        next_prompt = f"Observation: {observation}"
    return "Maximum steps reached."

Testing the Agent

Run a few queries to verify the agent behaves correctly:

print(run_agent("What countries border France?"))
print(run_agent("Has Simon visited Tanzania?"))
print(run_agent("42 * 17"))

Debugging and Error Handling

  • Missing API key: Ensure the environment variable is set and accessible.
  • Network errors: Confirm endpoints are reachable; add try/except blocks around HTTP calls.
  • Action parsing failures: The regular expression expects Action: tool_name: argument. If the model formats differently, adjust the pattern.
  • Unsafe eval(): The compute action uses eval() — in production restrict it to safe arithmetic or use a sandboxed environment.

Enhancing the Agent

To strengthen the agent:

  • Add input validation and sanitize all external data.
  • Enrich the toolset with actions like weather, news, or translate.
  • Introduce loggging to trace each thought, action, and observation step.

The ReAct approach opens a wide range of possibilities, from intelligent assistants to automated research tools. By building your own agent, you get complete control over its capabilities and can tailor it precisely to your needs.

Tags: AI Agent ReAct Pattern OpenAI python Agent Development

Posted on Fri, 31 Jul 2026 16:38:34 +0000 by jomama