ReAct (Reasoning and Acting) is a design pattern for AI agents that combines reasoning with taking actions. The agent alternates between "thinking" (reasoning) and "acting" (acting), leading to better results!
ReAct is a paradigm in which an AI agent:
1βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2β ReAct Loop β
3βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
4β β
5β ββββββββββββ ββββββββββββ βββββββββββββββ β
6β β Thought βββββΆβ Action βββββΆβ Observation β β
7β β (Think) β β (Act) β β (Observe) β β
8β ββββββββββββ ββββββββββββ ββββββββ¬βββββββ β
9β β² β β
10β ββββββββββββββββββββββββββββββββββββ β
11β (Repeat) β
12β β
13β Example: β
14β Thought: I need to check the weather in Serengeti β
15β Action: get_weather("Serengeti") β
16β Observation: Temperature 28Β°C, sunny β
17β Thought: Now I can answer the user β
18β Action: final_answer("The weather in Serengeti...") β
19β β
20βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ1from openai import OpenAI
2from typing import Callable
3import json
4import re
5
6client = OpenAI()
7
8class ReActAgent:
9 """An agent implementing the ReAct pattern."""
10
11 def __init__(self, tools: dict[str, Callable], max_iterations: int = 10):
12 self.tools = tools
13 self.max_iterations = max_iterations
14 self.history: list[str] = []
15
16 def _create_system_prompt(self) -> str:
17 """Creates a system prompt with tool descriptions."""
18 tool_descriptions = "\n".join([
19 f"- {name}: {func.__doc__}"
20 for name, func in self.tools.items()
21 ])
22
23 return f"""You are a helpful Safari assistant who solves problems step by step.
24
25Available tools:
26{tool_descriptions}
27
28Use this format:
29Thought: [your reasoning about what to do next]
30Action: [tool_name(arguments)]
31
32When you have enough information to answer:
33Thought: [final reasoning]
34Answer: [your answer for the user]
35
36Always think before acting. Analyze observations before taking the next step."""
37
38 def _parse_response(self, response: str) -> tuple[str, str, str]:
39 """Parses the response into thought, action, and answer."""
40 thought_match = re.search(r'Thought:\s*(.+?)(?=Action:|Answer:|$)', response, re.DOTALL)
41 action_match = re.search(r'Action:\s*(.+?)(?=Thought:|Observation:|Answer:|$)', response, re.DOTALL)
42 answer_match = re.search(r'Answer:\s*(.+)', response, re.DOTALL)
43
44 thought = thought_match.group(1).strip() if thought_match else ""
45 action = action_match.group(1).strip() if action_match else ""
46 answer = answer_match.group(1).strip() if answer_match else ""
47
48 return thought, action, answer
49
50 def _execute_action(self, action_str: str) -> str:
51 """Executes an action and returns the observation."""
52 # Parse function name and arguments
53 match = re.match(r'(\w+)\((.*)\)', action_str)
54 if not match:
55 return f"Error: Invalid action format: {action_str}"
56
57 func_name = match.group(1)
58 args_str = match.group(2)
59
60 if func_name not in self.tools:
61 return f"Error: Unknown tool: {func_name}"
62
63 try:
64 # Simple argument parsing
65 if args_str:
66 args = [arg.strip().strip('"').strip("'") for arg in args_str.split(',')]
67 result = self.tools[func_name](*args)
68 else:
69 result = self.tools[func_name]()
70 return str(result)
71 except Exception as e:
72 return f"Execution error: {str(e)}"
73
74 def run(self, query: str) -> str:
75 """Runs the ReAct agent."""
76 self.history = [f"User: {query}"]
77
78 for i in range(self.max_iterations):
79 # Build context
80 context = "\n".join(self.history)
81
82 # Call LLM
83 response = client.chat.completions.create(
84 model="gpt-5-mini",
85 messages=[
86 {"role": "system", "content": self._create_system_prompt()},
87 {"role": "user", "content": context}
88 ],
89 temperature=0
90 )
91
92 llm_response = response.choices[0].message.content
93 thought, action, answer = self._parse_response(llm_response)
94
95 # Record thought
96 if thought:
97 self.history.append(f"Thought: {thought}")
98 print(f"π€ Thought: {thought}")
99
100 # If there's a final answer
101 if answer:
102 print(f"β
Answer: {answer}")
103 return answer
104
105 # Execute action
106 if action:
107 self.history.append(f"Action: {action}")
108 print(f"β‘ Action: {action}")
109
110 observation = self._execute_action(action)
111 self.history.append(f"Observation: {observation}")
112 print(f"ποΈ Observation: {observation}")
113
114 return "Exceeded iteration limit without finding an answer."1# Tool definitions
2def get_weather(location: str) -> str:
3 """Retrieves the current weather for a given location."""
4 weather_data = {
5 "Serengeti": "28Β°C, sunny, humidity 45%",
6 "Kilimanjaro": "5Β°C, cloudy, snow at the summit",
7 "Nairobi": "22Β°C, partly cloudy"
8 }
9 return weather_data.get(location, f"No weather data for {location}")
10
11def search_animals(query: str) -> str:
12 """Searches for information about Safari animals."""
13 animals = {
14 "lion": "Panthera leo - Africa's largest cat, lives in groups called prides",
15 "elephant": "Loxodonta africana - the largest land animal, lives 60-70 years",
16 "giraffe": "Giraffa camelopardalis - the tallest animal in the world, up to 5.5m tall"
17 }
18 for key, value in animals.items():
19 if key in query.lower():
20 return value
21 return f"No information found about: {query}"
22
23def calculate_distance(from_loc: str, to_loc: str) -> str:
24 """Calculates the distance between Safari locations."""
25 distances = {
26 ("Nairobi", "Serengeti"): 350,
27 ("Serengeti", "Kilimanjaro"): 280,
28 ("Nairobi", "Kilimanjaro"): 200
29 }
30 key = (from_loc, to_loc)
31 reverse_key = (to_loc, from_loc)
32
33 if key in distances:
34 return f"{distances[key]} km"
35 elif reverse_key in distances:
36 return f"{distances[reverse_key]} km"
37 return f"Unknown distance between {from_loc} and {to_loc}"
38
39# Create the agent
40tools = {
41 "get_weather": get_weather,
42 "search_animals": search_animals,
43 "calculate_distance": calculate_distance
44}
45
46agent = ReActAgent(tools=tools)
47
48# Run
49result = agent.run("What's the weather in Serengeti and what animals live there?")
50print(f"\nFinal answer: {result}")1from langchain.agents import create_react_agent, AgentExecutor
2from langchain_openai import ChatOpenAI
3from langchain.tools import tool
4from langchain import hub
5
6# Tools as LangChain tools
7@tool
8def safari_weather(location: str) -> str:
9 """Retrieves weather for a Safari location."""
10 return f"Weather in {location}: 26Β°C, sunny"
11
12@tool
13def animal_info(animal_name: str) -> str:
14 """Searches for information about a Safari animal."""
15 return f"Information about {animal_name}: A magnificent African animal!"
16
17@tool
18def safari_distance(from_location: str, to_location: str) -> str:
19 """Calculates the distance between Safari points."""
20 return f"Distance from {from_location} to {to_location}: 150 km"
21
22# LLM and prompt
23llm = ChatOpenAI(model="gpt-5-mini", temperature=0)
24prompt = hub.pull("hwchase17/react")
25
26# ReAct Agent
27tools = [safari_weather, animal_info, safari_distance]
28agent = create_react_agent(llm, tools, prompt)
29
30# Executor
31agent_executor = AgentExecutor(
32 agent=agent,
33 tools=tools,
34 verbose=True,
35 handle_parsing_errors=True,
36 max_iterations=10
37)
38
39# Run
40result = agent_executor.invoke({
41 "input": "Check the weather in Serengeti and tell me about lions"
42})
43print(result["output"])1from openai import OpenAI
2import json
3
4client = OpenAI()
5
6# Tool definitions for OpenAI
7tools = [
8 {
9 "type": "function",
10 "function": {
11 "name": "get_safari_info",
12 "description": "Retrieves information about Safari",
13 "parameters": {
14 "type": "object",
15 "properties": {
16 "topic": {
17 "type": "string",
18 "description": "Topic: weather, animals, locations"
19 },
20 "query": {
21 "type": "string",
22 "description": "Detailed query"
23 }
24 },
25 "required": ["topic", "query"]
26 }
27 }
28 }
29]
30
31def execute_safari_tool(topic: str, query: str) -> str:
32 """Executes a Safari tool."""
33 if topic == "weather":
34 return f"Weather for {query}: 28Β°C, sunny"
35 elif topic == "animals":
36 return f"Information about {query}: A fascinating Safari animal!"
37 elif topic == "locations":
38 return f"Location {query}: A popular Safari destination"
39 return "Unknown topic"
40
41def react_with_functions(query: str) -> str:
42 """ReAct using OpenAI function calling."""
43 messages = [
44 {
45 "role": "system",
46 "content": "You are a Safari expert. Answer step by step, using available tools."
47 },
48 {"role": "user", "content": query}
49 ]
50
51 for _ in range(5): # Max iterations
52 response = client.chat.completions.create(
53 model="gpt-5-mini",
54 messages=messages,
55 tools=tools,
56 tool_choice="auto"
57 )
58
59 message = response.choices[0].message
60 messages.append(message)
61
62 # Check for tool calls
63 if message.tool_calls:
64 for tool_call in message.tool_calls:
65 args = json.loads(tool_call.function.arguments)
66 result = execute_safari_tool(**args)
67
68 messages.append({
69 "role": "tool",
70 "tool_call_id": tool_call.id,
71 "content": result
72 })
73 else:
74 # No tool calls = final answer
75 return message.content
76
77 return "Exceeded iteration limit"
78
79# Usage
80answer = react_with_functions("What's the weather like in Serengeti?")
81print(answer)1from dataclasses import dataclass, field
2from typing import Optional
3from datetime import datetime
4
5@dataclass
6class ThoughtStep:
7 """An agent reasoning step."""
8 thought: str
9 action: Optional[str] = None
10 observation: Optional[str] = None
11 timestamp: datetime = field(default_factory=datetime.now)
12
13class AdvancedReActAgent:
14 """Advanced ReAct agent with memory and reflection."""
15
16 def __init__(self, tools: dict, model: str = "gpt-5-mini"):
17 self.tools = tools
18 self.model = model
19 self.client = OpenAI()
20 self.thought_history: list[ThoughtStep] = []
21 self.long_term_memory: list[str] = []
22
23 def reflect(self) -> str:
24 """Reflects on the steps taken so far."""
25 if len(self.thought_history) < 2:
26 return ""
27
28 steps_summary = "\n".join([
29 f"- Thought: {s.thought}, Action: {s.action}, Result: {s.observation}"
30 for s in self.thought_history[-3:]
31 ])
32
33 response = self.client.chat.completions.create(
34 model=self.model,
35 messages=[
36 {
37 "role": "system",
38 "content": "Analyze the steps taken so far and suggest improvements."
39 },
40 {
41 "role": "user",
42 "content": f"Steps so far:\n{steps_summary}"
43 }
44 ],
45 max_tokens=200
46 )
47
48 return response.choices[0].message.content
49
50 def should_reflect(self) -> bool:
51 """Decides whether the agent should reflect."""
52 # Reflect every 3 steps or when the last action failed
53 if len(self.thought_history) % 3 == 0 and len(self.thought_history) > 0:
54 return True
55 if self.thought_history and "error" in (self.thought_history[-1].observation or "").lower():
56 return True
57 return False
58
59 def run(self, query: str) -> str:
60 """Runs the agent with reflection."""
61 # ... implementation similar to the basic version
62 # but with added reflection
63
64 for i in range(10):
65 if self.should_reflect():
66 reflection = self.reflect()
67 print(f"π Reflection: {reflection}")
68
69 # ... rest of the ReAct logic
70
71 return "Answer"1"""
2Popular patterns used in ReAct agents:
3
41. BASIC ReAct
5 Thought -> Action -> Observation -> Repeat
6
72. ReAct with Reflection
8 Thought -> Action -> Observation -> Reflect -> Repeat
9
103. ReAct with Planning
11 Plan -> Thought -> Action -> Observation -> Replan -> Repeat
12
134. ReAct with Self-Consistency
14 Multiple ReAct traces -> Vote on best answer
15
165. ReAct with Retrieval (RAG-ReAct)
17 Thought -> Retrieve -> Augment -> Action -> Observation
18"""
19
20# Example: ReAct with Planning
21class PlanningReActAgent:
22 def __init__(self, tools: dict):
23 self.tools = tools
24 self.plan: list[str] = []
25 self.current_step = 0
26
27 def create_plan(self, query: str) -> list[str]:
28 """Creates an action plan before starting."""
29 # LLM creates a step-by-step plan
30 response = client.chat.completions.create(
31 model="gpt-5-mini",
32 messages=[
33 {
34 "role": "system",
35 "content": "Create a step-by-step plan to solve the task. Each step on a new line."
36 },
37 {"role": "user", "content": query}
38 ]
39 )
40 plan = response.choices[0].message.content.split("\n")
41 return [step.strip() for step in plan if step.strip()]
42
43 def replan(self, remaining_steps: list[str], new_info: str) -> list[str]:
44 """Updates the plan based on new information."""
45 # LLM can modify the remaining steps
46 return remaining_steps # or modified stepsReAct is a powerful pattern for AI agents. It combines reasoning with action, leading to more thoughtful and accurate responses. In the next module, you will learn even more advanced techniques - RAG and Multi-Agent systems!