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(): Thecomputeaction useseval()— 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, ortranslate. - 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.