Setup¶
The ReAct (Reasoning + Acting) pattern requires an LLM client and a set of external tools. The helper function ask() wraps the OpenAI chat completions API with temperature=0 by default, which is critical for ReAct agents – deterministic outputs produce more reliable action selection. We also import requests for HTTP-based tools and re for parsing structured text from model responses. The model choice (gpt-4o-mini) balances cost and capability; for complex multi-tool reasoning, upgrading to gpt-4o can improve action selection accuracy.
# Install if needed
# !pip install openai requests wikipedia
import os
import re
import json
import requests
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
def ask(prompt, model="gpt-4o-mini", temperature=0):
"""Helper function for API calls."""
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=temperature
)
return response.choices[0].message.content
1. Simple ReAct Example¶
The ReAct pattern (Yao et al., 2022) interleaves reasoning (“Thought”) with tool use (“Action” / “Observation”) in a single prompt. Unlike chain-of-thought alone, which can only reason over what the model already knows, ReAct grounds reasoning in real-world data by calling external tools mid-generation. The tools below provide a Calculator for arithmetic and a Search for factual lookups. To answer “How much taller is the Burj Khalifa than the Eiffel Tower?”, the agent must search for both heights and then compute the difference – a multi-step process that neither pure reasoning nor pure tool use could accomplish alone.
# Define simple tools
def calculator(expression):
"""Evaluate a mathematical expression."""
try:
# Safe eval for math only
result = eval(expression, {"__builtins__": None}, {})
return str(result)
except Exception as e:
return f"Error: {e}"
def search(query):
"""Simulated search - returns mock results."""
# In production, use real search API (Google, Bing, etc.)
mock_results = {
"eiffel tower height": "The Eiffel Tower is 330 meters tall.",
"statue of liberty height": "The Statue of Liberty is 93 meters tall.",
"burj khalifa height": "The Burj Khalifa is 828 meters tall.",
}
for key, value in mock_results.items():
if key in query.lower():
return value
return "No results found."
# Available tools
TOOLS = {
"Calculator": calculator,
"Search": search,
}
print("Tools available:")
for tool_name in TOOLS:
print(f" - {tool_name}")
# ReAct prompt template
REACT_PROMPT = """
Answer the following question using this format:
Question: the input question
Thought: 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
... (repeat Thought/Action/Observation as needed)
Thought: I now know the final answer
Final Answer: the final answer to the question
Available tools:
{tool_descriptions}
Question: {question}
"""
TOOL_DESCRIPTIONS = """
- Calculator: Evaluate mathematical expressions. Input should be a valid Python expression.
- Search: Search for information. Input should be a search query.
"""
def create_react_prompt(question):
"""Create ReAct prompt for a question."""
return REACT_PROMPT.format(
tool_names=", ".join(TOOLS.keys()),
tool_descriptions=TOOL_DESCRIPTIONS,
question=question
)
# Test prompt
prompt = create_react_prompt("How much taller is the Burj Khalifa than the Eiffel Tower?")
print(prompt[:200] + "...")
2. ReAct Agent¶
The agent loop is the core of the ReAct pattern: generate a response, check if it contains a final answer, and if not, parse and execute the requested action, append the observation, and repeat. The parse_action() function uses regex to extract the tool name and input from the model’s structured output. Each iteration extends the prompt with the new observation, giving the model a growing context of what it has learned. The max_iterations parameter prevents infinite loops in case the model fails to converge on an answer – a crucial safeguard for production deployments.
def parse_action(text):
"""Extract action and input from model response."""
# Look for Action: ... and Action Input: ...
action_match = re.search(r'Action: (\w+)', text)
input_match = re.search(r'Action Input: (.+?)(?:\n|$)', text)
if action_match and input_match:
action = action_match.group(1)
action_input = input_match.group(1).strip()
return action, action_input
return None, None
def react_agent(question, max_iterations=5, verbose=True):
"""ReAct agent that uses tools to answer questions."""
prompt = create_react_prompt(question)
for i in range(max_iterations):
if verbose:
print(f"\n{'='*60}")
print(f"Iteration {i+1}")
print(f"{'='*60}")
# Get model response
response = ask(prompt)
if verbose:
print(f"\nModel response:\n{response}")
# Check if done
if "Final Answer:" in response:
# Extract final answer
answer = response.split("Final Answer:")[1].strip()
if verbose:
print(f"\n✅ Final Answer: {answer}")
return answer
# Parse action
action, action_input = parse_action(response)
if not action:
if verbose:
print("\n⚠️ No action found, stopping.")
return "Could not determine action."
# Execute action
if action in TOOLS:
observation = TOOLS[action](action_input)
if verbose:
print(f"\n🔧 Action: {action}({action_input})")
print(f"👁️ Observation: {observation}")
# Add observation to prompt
prompt += f"\n{response}\nObservation: {observation}\n"
else:
if verbose:
print(f"\n❌ Unknown action: {action}")
return f"Unknown action: {action}"
return "Max iterations reached without finding answer."
# Test
answer = react_agent("How much taller is the Burj Khalifa than the Eiffel Tower?")
3. Real Tools – Wikipedia Search¶
Moving beyond mock data, the Wikipedia API provides a real-world knowledge source for the agent. The wikipedia_search() function queries Wikipedia’s search API to find the most relevant article, then extracts the introductory text. This demonstrates a key advantage of ReAct: by grounding responses in retrieved facts rather than parametric memory, the agent can answer questions about recent events, niche topics, and specific details that the LLM might not know or might hallucinate. The 500-character limit on the extract keeps context windows manageable.
def wikipedia_search(query):
"""Search Wikipedia and return summary."""
try:
# Wikipedia API
url = "https://en.wikipedia.org/w/api.php"
params = {
"action": "query",
"format": "json",
"list": "search",
"srsearch": query,
"srlimit": 1
}
response = requests.get(url, params=params)
data = response.json()
if data['query']['search']:
# Get page content
page_id = data['query']['search'][0]['pageid']
extract_params = {
"action": "query",
"format": "json",
"pageids": page_id,
"prop": "extracts",
"exintro": True,
"explaintext": True
}
extract_response = requests.get(url, params=extract_params)
extract_data = extract_response.json()
page = extract_data['query']['pages'][str(page_id)]
return page.get('extract', 'No summary available')[:500] # Limit length
else:
return "No results found."
except Exception as e:
return f"Error searching Wikipedia: {e}"
# Add to tools
TOOLS["Wikipedia"] = wikipedia_search
# Update tool descriptions
TOOL_DESCRIPTIONS = """
- Calculator: Evaluate mathematical expressions. Input should be a valid Python expression.
- Search: Search for general information. Input should be a search query.
- Wikipedia: Search Wikipedia for factual information. Input should be a topic or person.
"""
# Test
answer = react_agent("Who invented the telephone and when were they born?")
4. More Complex Example¶
Multi-step questions test the agent’s ability to chain multiple tool calls together with intermediate reasoning. The question below requires factual knowledge (Eiffel Tower height), arithmetic (multiply by 3), more factual knowledge (Burj Khalifa height), and a comparison. The agent must plan the sequence, execute each tool call, and synthesize results across observations. This is where the “Thought” step becomes essential – it lets the model plan which information to gather next and how to combine results from previous steps.
# Multi-step question requiring multiple tools
question = """
If the Eiffel Tower is 330 meters tall and I stack 3 of them on top of each other,
would they be taller than the Burj Khalifa?
"""
answer = react_agent(question)
5. Custom Tools¶
The ReAct pattern is domain-agnostic – you can add any tool that takes a string input and returns a string output. The examples below add weather and stock price lookups (mocked for demonstration). In production applications, custom tools connect to databases, internal APIs, CRM systems, or any service relevant to the domain. The key design principle is that each tool should do one thing well and return a concise, informative response. Tool descriptions in the prompt act as documentation that helps the model select the right tool for each sub-task.
# Weather tool (mock)
def get_weather(location):
"""Get weather for a location."""
# In production: use real weather API
mock_weather = {
"paris": "Sunny, 22°C",
"london": "Cloudy, 18°C",
"new york": "Rainy, 15°C",
}
location_lower = location.lower()
for key, value in mock_weather.items():
if key in location_lower:
return f"Weather in {location}: {value}"
return f"Weather data not available for {location}"
# Stock price tool (mock)
def get_stock_price(symbol):
"""Get current stock price."""
# In production: use real stock API (Alpha Vantage, Yahoo Finance, etc.)
mock_prices = {
"AAPL": "$175.43",
"GOOGL": "$139.82",
"MSFT": "$378.91",
}
symbol_upper = symbol.upper()
if symbol_upper in mock_prices:
return f"{symbol_upper}: {mock_prices[symbol_upper]}"
return f"Stock symbol {symbol} not found"
# Add tools
TOOLS["Weather"] = get_weather
TOOLS["StockPrice"] = get_stock_price
# Update descriptions
TOOL_DESCRIPTIONS = """
- Calculator: Evaluate mathematical expressions. Input: valid Python expression.
- Search: General information search. Input: search query.
- Wikipedia: Factual information from Wikipedia. Input: topic or person name.
- Weather: Get weather for a location. Input: city name.
- StockPrice: Get current stock price. Input: stock symbol (e.g., AAPL).
"""
# Test
answer = react_agent("What's the weather in Paris and is it warmer than London?")
6. Error Handling and Self-Correction¶
Production agents must handle failures gracefully – invalid tool names, malformed inputs, API errors, and parsing failures. The robust_react_agent() adds three improvements: if no action is parsed, it prompts the model to try again; if an unknown tool is requested, it lists available tools in the observation; and if a tool raises an exception, the error message becomes the observation so the model can adjust its approach. This self-correction capability is a key advantage of the ReAct loop – errors become feedback that guides the next iteration rather than terminal failures.
# Enhanced agent with error handling
def robust_react_agent(question, max_iterations=7, verbose=True):
"""ReAct agent with better error handling."""
prompt = create_react_prompt(question)
for i in range(max_iterations):
if verbose:
print(f"\n{'='*60}")
print(f"Iteration {i+1}/{max_iterations}")
print(f"{'='*60}")
# Get response
response = ask(prompt, temperature=0) # Use 0 for consistency
if verbose:
print(f"\nResponse:\n{response[:300]}...") if len(response) > 300 else print(f"\nResponse:\n{response}")
# Check for final answer
if "Final Answer:" in response:
answer = response.split("Final Answer:")[1].strip()
if verbose:
print(f"\n✅ Done! Answer: {answer}")
return answer
# Parse action
action, action_input = parse_action(response)
if not action:
# Try to guide the model
prompt += f"\n{response}\nObservation: Please specify an Action and Action Input.\n"
continue
# Validate action
if action not in TOOLS:
available = ", ".join(TOOLS.keys())
prompt += f"\n{response}\nObservation: Error - '{action}' is not a valid action. Available: {available}\n"
continue
# Execute action
try:
observation = TOOLS[action](action_input)
if verbose:
print(f"\n🔧 {action}({action_input})")
print(f"👁️ {observation}")
prompt += f"\n{response}\nObservation: {observation}\n"
except Exception as e:
error_msg = f"Error executing {action}: {str(e)}"
if verbose:
print(f"\n❌ {error_msg}")
prompt += f"\n{response}\nObservation: {error_msg}\n"
return "Could not find answer within iteration limit."
# Test with error-prone question
answer = robust_react_agent("Calculate 15 * 23 + 100")
Best Practices¶
1. Clear Tool Descriptions¶
# ✅ Good
"Calculator: Evaluate math expressions. Input: '2 + 2' or '10 * 5'"
# ❌ Bad
"Calculator: Does math"
2. Limit Tool Count¶
Too many tools confuses the model
3-7 tools is ideal
Group similar functionality
3. Validate Tool Inputs¶
def safe_calculator(expression):
# Validate input
allowed_chars = set('0123456789+-*/() .')
if not set(expression) <= allowed_chars:
return "Invalid input: only math operations allowed"
# ...
4. Set Iteration Limits¶
Prevents infinite loops
5-10 iterations usually enough
Track costs with API calls
5. Use Temperature=0¶
More consistent action selection
Deterministic for debugging
Can increase for creative tasks
Exercise: Build Your Agent¶
Apply the ReAct pattern to a specific domain by creating a customer support agent with tools for order tracking, returns, and product search. Define each tool function, write clear tool descriptions, and test the agent with realistic customer queries. Pay attention to how tool descriptions affect the model’s tool selection, and experiment with adding edge cases like invalid order IDs to verify error handling.
# Example: Customer Support Agent
def check_order_status(order_id):
"""Check order status (mock)."""
orders = {
"12345": "Shipped - arrives tomorrow",
"12346": "Processing - ships today",
"12347": "Delivered on Dec 15"
}
return orders.get(order_id, "Order not found")
def initiate_return(order_id):
"""Start return process (mock)."""
return f"Return initiated for order {order_id}. Label sent to email."
def find_product(product_name):
"""Search for product (mock)."""
return f"Found {product_name} - $49.99 - In stock"
# Set up agent
SUPPORT_TOOLS = {
"CheckOrderStatus": check_order_status,
"InitiateReturn": initiate_return,
"FindProduct": find_product,
}
# Your turn: Build it!
# - Update TOOLS and TOOL_DESCRIPTIONS
# - Test with customer queries
# - Add more tools as needed
Key Takeaways¶
ReAct = Reasoning + Acting: Interleave thinking and tool use
Interpretable: See the reasoning process
Extensible: Easy to add new tools
Powerful: Solve complex multi-step problems
Foundation: Basis for many agent frameworks (LangChain, AutoGPT)
Limitations¶
More API calls = higher cost
Can get stuck in loops
Tool selection not always optimal
Sensitive to prompt wording
Next Steps¶
04_tree_of_thoughts.ipynb- Explore multiple reasoning paths07_advanced_techniques.ipynb- Self-consistency, reflection../9-specializations/ai-agents/02_langchain_agents.ipynb- Use frameworks../9-specializations/ai-agents/03_langgraph.ipynb- Build complex agents