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) andsearch_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?