Beginner
Implementing the ReAct Pattern
Reasoning and Acting: The foundational pattern that allows LLMs to interact with external tools.
12 min read
LangChainOpenAI
Implementing the ReAct Pattern#
ReAct (Reasoning and Acting) is a foundational pattern that enables LLMs to solve complex problems by interleaving reasoning traces with action execution.
What is ReAct?#
ReAct combines:
- Reasoning: The model thinks through the problem step-by-step
- Acting: The model takes actions using external tools
- Observation: The model incorporates tool outputs into its reasoning
This loop continues until the model reaches a final answer.
The ReAct Loop#
Thought: I need to find information about X
Action: search[X]
Observation: [search results]
Thought: Based on the results, I now know Y. I need to calculate Z.
Action: calculate[Z formula]
Observation: [calculation result]
Thought: I have all the information needed.
Action: finish[final answer]Basic Implementation#
Setting Up Tools#
from typing import Callable, Dict
# Define available tools
tools: Dict[str, Callable] = {
"search": lambda query: search_web(query),
"calculate": lambda expr: eval_math(expr),
"lookup": lambda term: lookup_wikipedia(term),
}
def execute_action(action: str, input: str) -> str:
"""Execute an action using the appropriate tool."""
if action in tools:
return tools[action](input)
return f"Unknown action: {action}"The ReAct Prompt#
REACT_PROMPT = """Answer the following question using the available tools.
Available tools:
- search[query]: Search the web for information
- calculate[expression]: Evaluate a mathematical expression
- lookup[term]: Look up a term in Wikipedia
- finish[answer]: Provide the final answer
Always use this format:
Thought: <your reasoning about what to do next>
Action: <tool_name>[<input>]
After receiving an observation, continue with another Thought/Action or finish.
Question: {question}
{history}
Thought:"""The ReAct Agent#
from openai import OpenAI
import re
client = OpenAI()
def parse_action(response: str) -> tuple:
"""Parse action and input from model response."""
match = re.search(r'Action:\s*(\w+)\[(.*?)\]', response)
if match:
return match.group(1), match.group(2)
return None, None
def react_agent(question: str, max_steps: int = 10) -> str:
"""Run the ReAct loop until completion."""
history = ""
for step in range(max_steps):
# Generate next thought and action
prompt = REACT_PROMPT.format(question=question, history=history)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
temperature=0,
stop=["Observation:"]
)
output = response.choices[0].message.content
history += output + "\n"
# Parse and execute action
action, action_input = parse_action(output)
if action == "finish":
return action_input
if action:
observation = execute_action(action, action_input)
history += f"Observation: {observation}\n"
else:
history += "Observation: Could not parse action. Try again.\n"
return "Max steps reached without conclusion"Using LangChain#
LangChain provides built-in ReAct support:
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import Tool
from langchain_openai import ChatOpenAI
from langchain import hub
# Define tools
tools = [
Tool(
name="Search",
func=search_web,
description="Search the web for current information"
),
Tool(
name="Calculator",
func=eval_math,
description="Useful for math calculations"
),
]
# Get the ReAct prompt from hub
prompt = hub.pull("hwchase17/react")
# Create the agent
llm = ChatOpenAI(model="gpt-4", temperature=0)
agent = create_react_agent(llm, tools, prompt)
# Create executor
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=10,
handle_parsing_errors=True
)
# Run
result = agent_executor.invoke({"input": "What is the population of Tokyo times 2?"})
print(result["output"])Enhancing ReAct#
Adding Memory#
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=True
)Error Recovery#
def react_with_recovery(question: str) -> str:
"""ReAct agent with error recovery."""
history = ""
errors = 0
max_errors = 3
for step in range(max_steps):
try:
# ... normal ReAct loop
pass
except Exception as e:
errors += 1
history += f"Observation: Error occurred: {str(e)}. Trying alternative approach.\n"
if errors >= max_errors:
return "Too many errors, unable to complete task"
return resultStructured Output#
from pydantic import BaseModel
class ReActStep(BaseModel):
thought: str
action: str
action_input: str
observation: str | None = None
class ReActTrace(BaseModel):
question: str
steps: list[ReActStep]
final_answer: strCommon Patterns#
Search → Summarize#
Thought: I need to find recent news about AI
Action: search[latest AI news 2024]
Observation: [multiple articles...]
Thought: I found several articles. Let me summarize the key points.
Action: finish[Summary: 1. OpenAI released... 2. Google announced...]Calculate → Verify#
Thought: I need to calculate the compound interest
Action: calculate[1000 * (1 + 0.05) ** 10]
Observation: 1628.89
Thought: Let me verify by calculating step by step
Action: calculate[1000 * 1.05 ** 10]
Observation: 1628.89
Thought: Both calculations match
Action: finish[The compound interest result is $1,628.89]Best Practices#
- Clear Tool Descriptions: Make tool purposes unambiguous
- Limit Tool Count: 3-5 tools works best; more causes confusion
- Handle Errors Gracefully: Include error recovery in your loop
- Set Max Iterations: Prevent infinite loops
- Log Traces: Keep full traces for debugging and improvement
When to Use ReAct#
Good for:
- Multi-step research tasks
- Calculations with lookups
- Tasks requiring real-time data
Not ideal for:
- Simple Q&A
- Creative writing
- Tasks with no external tools needed