ReAct & Chain-of-Thought Reasoning

25 min read Module 4 of 12 Topic 4 of 12

What you'll learn

  • Understand why explicit reasoning before action improves agent accuracy
  • Implement the ReAct (Reason + Act) prompting pattern
  • Use scratchpad thinking to make agent decisions transparent
  • Recognise when an agent is stuck in a reasoning loop
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

Why Thinking Out Loud Makes Agents Smarter

Imagine you’re solving a complex maths problem. You’d use scratch paper, working through intermediate steps, checking your reasoning, catching errors before committing to an answer. You wouldn’t try to jump directly from the problem statement to the final answer in your head.

LLMs work better the same way. When asked to solve complex tasks directly, they often make logical leaps that go wrong. When asked to explicitly reason through intermediate steps, their accuracy improves dramatically.

Chain-of-Thought (CoT) prompting is the technique of asking the model to reason step by step before answering. You might prompt it with “Think through this carefully, step by step” or simply include examples of step-by-step reasoning.

ReAct (Reason + Act) combines chain-of-thought with tool use. Before each action, the agent explicitly writes out its reasoning. This creates a transparent trace of why the agent is doing what it’s doing.


The ReAct Pattern: Thought → Act → Observe

Here’s what a ReAct agent’s internal monologue looks like. Goal: “What is the population of the city where the 2024 Olympics were held?”

Thought: I need to find out which city hosted the 2024 Olympics. I don't have
         this from memory: I should search for it.
Act: search_web(query="2024 Summer Olympics host city")
Observe: The 2024 Summer Olympics were held in Paris, France.

Thought: I now know the city is Paris. Next I need the current population
         of Paris. Let me search for that.
Act: search_web(query="population of Paris France 2024")
Observe: The city of Paris has a population of approximately 2.1 million.
         The greater Paris metropolitan area has about 12 million people.

Thought: I have both pieces of information: 2024 Olympics = Paris, Paris
         population = 2.1 million (city proper). I can now answer.
Final Answer: The 2024 Summer Olympics were held in Paris, France.
              The population of Paris is approximately 2.1 million people
              (city proper) or 12 million in the metropolitan area.

Each Thought makes the reasoning transparent. You can read it and immediately understand why the agent is taking each action. This also means when something goes wrong, you can see exactly where the reasoning broke down.


Implementing ReAct with System Prompt

The ReAct pattern is implemented primarily through the system prompt. You instruct the model to use the Thought/Act/Observe format:

REACT_SYSTEM_PROMPT = """
You are a research assistant. For every task, use this exact format:

Thought: [reason about what you know and what you need to do next]
Action: [the tool to call and its parameters]
Observation: [what the tool returned, filled in by the system]
... (repeat Thought/Action/Observation as needed)
Thought: [reason that you have enough information to answer]
Final Answer: [your complete answer to the user's question]

Rules:
- Always write a Thought before each Action
- Never skip steps, work through the problem methodically
- If a search doesn't return useful results, try a different query
- If you've tried 5+ times without progress, say what you've found so far
"""

Parsing the Agent’s Reasoning

With ReAct, you need to parse the agent’s output to find where it’s calling a tool versus where it’s thinking:

import re

def parse_react_output(text: str) -> dict:
    """Extract structured fields from a ReAct-format response."""
    result = {
        'thoughts':    [],
        'actions':     [],
        'final_answer': None,
    }
    
    # Extract all "Thought:" sections
    thoughts = re.findall(r'Thought:\s*(.+?)(?=Action:|Observation:|Final Answer:|$)',
                          text, re.DOTALL)
    result['thoughts'] = [t.strip() for t in thoughts]
    
    # Extract all "Action:" sections (tool calls)
    actions = re.findall(r'Action:\s*(.+?)(?=Observation:|Thought:|$)',
                         text, re.DOTALL)
    result['actions'] = [a.strip() for a in actions]
    
    # Extract the final answer
    final = re.search(r'Final Answer:\s*(.+?)$', text, re.DOTALL)
    if final:
        result['final_answer'] = final.group(1).strip()
    
    return result

A Complete ReAct Loop

Here’s a more explicit ReAct implementation where we inject “Observation:” lines manually after each tool call:

from openai import OpenAI
import json

client = OpenAI()

def run_react_agent(goal: str, max_steps: int = 10) -> str:
    messages = [
        {'role': 'system', 'content': REACT_SYSTEM_PROMPT},
        {'role': 'user',   'content': f'Goal: {goal}'},
    ]
    
    steps_taken = 0
    last_actions = []   # for loop detection
    
    while steps_taken < max_steps:
        response = client.chat.completions.create(
            model='gpt-5.6-sol',
            messages=messages,
            tools=tools,
            tool_choice='auto',
        )
        
        msg = response.choices[0].message
        steps_taken += 1

Handle the response: either a tool call or a final answer:

        if msg.tool_calls:
            # Agent wants to act: check for loops first
            action_signature = str([(tc.function.name, tc.function.arguments) 
                                     for tc in msg.tool_calls])
            if action_signature in last_actions[-3:]:
                return "Agent stuck: repeated the same action 3 times. Aborting."
            last_actions.append(action_signature)
            
            # Execute the tools
            messages.append(msg)
            for tc in msg.tool_calls:
                result = TOOL_REGISTRY[tc.function.name](**json.loads(tc.function.arguments))
                messages.append({
                    'role': 'tool',
                    'tool_call_id': tc.id,
                    'content': json.dumps(result),
                })
        else:
            # No tool call = final answer
            return msg.content
    
    return f"Reached max steps ({max_steps}). Partial result: {msg.content}"

The loop detection checks if the last 3 actions are identical, a sign the agent is stuck. max_steps provides a hard safety limit.


When to Use ReAct vs Direct Answer

ReAct adds overhead, more tokens, more API calls, more latency. Don’t use it for simple tasks:

  • Direct answer (no ReAct): “What’s the capital of France?”, the LLM knows this; reasoning isn’t needed.
  • ReAct: “Research the top 5 open-source AI agent frameworks and compare their GitHub stars, license, and primary use case.”: requires multiple steps, research, and synthesis.

The rule of thumb: if the task requires more than 2 sequential steps, ReAct (or explicit step-by-step prompting) will produce better results.

Exercise: Implement a ReAct agent that helps plan a trip. Give it two tools: get_weather(city, date) (you can mock this to return fake data) and search_flights(origin, destination, date) (also mocked). Ask it to plan a 3-city trip and pick the best route based on weather. Observe how the Thought sections explain its reasoning. Does it make sensible decisions?

Knowledge Check

3 questions to test your understanding

1 What does ReAct stand for and what's the key idea behind it?

2 Why does chain-of-thought reasoning improve agent accuracy?

3 How do you detect that a ReAct agent is stuck in an unproductive loop?

Discussion

Questions and notes from learners on this topic

Loading discussion…

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan