We use cookies to enhance your experience on the site
CodeWorlds

ReAct Agents - Reasoning and Acting

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!

What Is ReAct?

ReAct is a paradigm in which an AI agent:

  1. Thought - analyzes the situation, plans
  2. Action - performs an action (e.g., calls a tool)
  3. Observation - observes the result of the action
  4. Repeat - repeats the cycle until the problem is solved
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β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Basic ReAct Implementation

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."

ReAct Usage Example

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}")

ReAct with LangChain

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"])

ReAct with OpenAI Function Calling

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)

Advanced ReAct with Memory

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"

ReAct Patterns

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 steps

ReAct 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!

Go to CodeWorlds→