Iterative loop where agents reason about what to do, take action, observe results, and repeat
Perfect for:
- Open-ended problems requiring multi-step reasoning
- Tasks where the solution path is unknown upfront
- Research and exploration (gather info, synthesize, iterate)
- Debugging and troubleshooting (try fixes, check results)
- Complex decision-making with external data dependencies
Ideal scenarios:
- "Research the best budget laptop for video editing" → Search, compare, refine
- "Debug why the API is returning 500 errors" → Check logs, test endpoints, diagnose
- "Plan a 5-day trip to Japan" → Find flights, book hotels, plan itinerary
- "Analyze this dataset and find insights" → Explore, visualize, hypothesize, validate
❌ Avoid when:
- Path is predictable - Use sequential chain instead (cheaper, faster)
- Single-turn task - Simple tool calling is sufficient
- Strict cost budget - ReAct can loop indefinitely, burning tokens
- Latency sensitive - Multiple LLM calls add significant delay
- High accuracy required - Reasoning loops can drift or hallucinate
- Simple lookups - Overkill for "What's 2+2?" or "Find customer #123"
Cost trap: Without max iterations, ReAct can loop 10+ times on complex queries, costing $1+ per request.
flowchart TD
Start[User Query] --> Thought1[Thought: What do I need?]
Thought1 --> Action1[Action: search_web]
Action1 --> Obs1[Observation: Search results]
Obs1 --> Thought2[Thought: Need more details]
Thought2 --> Action2[Action: fetch_article]
Action2 --> Obs2[Observation: Article content]
Obs2 --> Thought3[Thought: I have enough info]
Thought3 --> Answer[Final Answer: Response to user]
Obs1 -.->|Max iterations check| Thought2
Obs2 -.->|Max iterations check| Thought3
Format:
Thought: [Internal reasoning about what to do next]
Action: [Tool to invoke]
Action Input: [Arguments for tool]
Observation: [Result from tool execution]
... (repeat until goal achieved)
Final Answer: [Response to user]
User query: "What's the population of the capital of France?"
Iteration 1:
Thought: I need to know the capital of France first
Action: search_web
Action Input: "capital of France"
Observation: Paris is the capital of France
Iteration 2:
Thought: Now I need the population of Paris
Action: search_web
Action Input: "population of Paris 2026"
Observation: Paris has approximately 2.1 million residents
Final:
Thought: I have the answer
Final Answer: The capital of France is Paris, with a population of approximately 2.1 million.
prompt = """
You are an assistant that uses tools to answer questions.
You have access to these tools:
{tool_descriptions}
Use this format:
Thought: [Reason about what to do]
Action: [tool_name]
Action Input: [arguments]
Observation: [tool result - will be provided]
Repeat Thought/Action/Observation as needed.
When you have the final answer:
Thought: I have enough information
Final Answer: [your response]
Question: {user_query}
"""def react_loop(query, tools, max_iterations=10):
context = []
for i in range(max_iterations):
# Get LLM reasoning
prompt = build_prompt(query, context, tools)
response = llm.generate(prompt)
# Parse output
if "Final Answer:" in response:
return extract_answer(response)
thought = extract_thought(response)
action = extract_action(response)
action_input = extract_action_input(response)
# Execute tool
observation = execute_tool(action, action_input)
# Add to context
context.append({
"thought": thought,
"action": action,
"action_input": action_input,
"observation": observation
})
# Max iterations reached
return "Unable to complete task within iteration limit"Regex-based:
import re
def extract_action(response):
match = re.search(r"Action:\s*(\w+)", response)
return match.group(1) if match else None
def extract_action_input(response):
match = re.search(r"Action Input:\s*(.+?)(?:\n|$)", response)
return match.group(1).strip() if match else NoneProblem: LLM may not follow format exactly (extra text, typos)
Robust parsing:
def parse_react_output(response):
lines = response.split('\n')
result = {}
for line in lines:
if line.startswith("Thought:"):
result["thought"] = line[8:].strip()
elif line.startswith("Action:"):
result["action"] = line[7:].strip()
elif line.startswith("Action Input:"):
result["action_input"] = line[13:].strip()
return result| Aspect | Pro | Con |
|---|---|---|
| Flexibility | Handles unpredictable problems | No guarantees it will find solution |
| Cost | Only uses tokens needed | Can loop many times ($1+ per query) |
| Latency | Self-corrects errors | 5-30s for complex tasks |
| Accuracy | Can gather comprehensive info | May get stuck or hallucinate |
| Debugging | Full reasoning trace visible | Hard to predict behavior |
| Scalability | Stateless (context in prompt) | Context grows with each iteration |
| Reliability | Retries failed actions | Infinite loops possible |
Task: "Find the best noise-canceling headphones under $300"
Execution trace:
Iteration 1:
Thought: I should search for top-rated noise-canceling headphones
Action: search_web
Action Input: "best noise canceling headphones 2026"
Observation: Top results: Sony WH-1000XM6 ($349), Bose QC45 ($279), Apple AirPods Max ($549)
Iteration 2:
Thought: Bose QC45 is under budget. Let me check detailed reviews
Action: fetch_article
Action Input: "https://reviews.com/bose-qc45"
Observation: Rating: 4.5/5. Pros: Great ANC, comfortable. Cons: Average battery life (24hrs)
Iteration 3:
Thought: Let me check if there are other options under $300
Action: search_web
Action Input: "noise canceling headphones under $300 2026"
Observation: Also recommended: Anker Soundcore Space One ($99), Sennheiser Momentum 4 ($299)
Iteration 4:
Thought: Let me compare the Sennheiser to the Bose
Action: fetch_article
Action Input: "https://reviews.com/sennheiser-momentum-4"
Observation: Rating: 4.7/5. Pros: 60hr battery, excellent sound. Cons: Bulkier design
Final:
Thought: I have enough information to recommend
Final Answer: For noise-canceling headphones under $300, I recommend:
1. Sennheiser Momentum 4 ($299) - Best overall with 60hr battery and 4.7/5 rating
2. Bose QC45 ($279) - Most comfortable with strong ANC, but shorter battery (24hrs)
3. Anker Soundcore Space One ($99) - Budget option if cost is primary concern
The Sennheiser offers the best value at your budget limit.
Cost: ~5,000 tokens ($0.05 at $0.01/1K)
Per-iteration breakdown:
| Component | Token Count | Cost (@$0.01/1K) |
|---|---|---|
| System prompt + tools | 500 tokens | $0.005 |
| Previous context (per iteration) | 200 tokens | $0.002 |
| User query | 50 tokens | $0.0005 |
| LLM thought + action | 150 tokens | $0.0015 |
| Tool result | 300 tokens | $0.003 |
| Total per iteration | 1,200 tokens | $0.012 |
Full task cost:
- 3 iterations: ~3,600 tokens = $0.036
- 5 iterations: ~6,000 tokens = $0.06
- 10 iterations: ~12,000 tokens = $0.12
Optimization strategies:
- Max iterations limit - Force stop after N loops
- Summarize observations - Compress tool results before adding to context
- Early stopping - Detect when goal is achieved
- Use cheaper models - GPT-3.5 for reasoning, GPT-4 for final answer
Problem: Agent repeats same action indefinitely
Thought: I should search
Action: search_web
Observation: [results]
Thought: I should search again ← Repeats
Action: search_web
...
Solution: Track action history, detect cycles, force different approach
Problem: LLM doesn't follow Thought/Action format
Let me search for that information... ← No "Thought:" prefix
Solution: Strict parsing with retries, few-shot examples in prompt
Problem: LLM invents non-existent tools
Action: super_search_engine ← Doesn't exist
Solution: Validate tool names, provide clear error, ask LLM to retry
Problem: After many iterations, context exceeds token limit
Iteration 1: 1K tokens
Iteration 5: 5K tokens
Iteration 10: 10K tokens ← Approaching limit
Solution: Summarize older observations, drop least relevant context
Problem: LLM gives final answer with incomplete info
Thought: I think I know the answer
Final Answer: [incorrect guess]
Solution: Prompt engineering ("Be thorough. Search multiple sources.")
Problem: Tool fails, LLM gets confused
Action: fetch_url
Observation: ERROR: 404 Not Found ← LLM doesn't know what to do
Solution: Return structured errors with recovery hints
Observation: {"error": "Page not found", "suggestion": "Try searching instead"}
Problem: Can't tell if agent is making progress Solution: Structured logging, trace visualization
Agent evaluates its own reasoning:
Thought: I searched for X and found Y
Reflection: Is this result reliable? Let me verify with another source
Action: cross_check_source
Combine planning with ReAct:
Initial Thought: Let me plan the steps
Plan:
1. Search for options
2. Compare top 3
3. Check reviews
4. Recommend best
[Now execute plan using ReAct loop]
Execute multiple actions simultaneously:
Thought: I need data from 3 sources
Action: parallel_execute
Action Input: [search_web("X"), fetch_url("Y"), query_db("Z")]
Observation: [results from all 3]
❌ "Thought: I should search"
✅ "Thought: I need to find current pricing info. Searching product review sites."
tools = [
{
"name": "search_web",
"description": "Search the web for current information. USE THIS for recent data, news, or general research. Returns 5-10 relevant snippets."
},
{
"name": "calculate",
"description": "Evaluate mathematical expressions. USE THIS for any math, including percentages, conversions, or comparisons. Returns numeric result."
}
]Include 1-2 example reasoning chains in prompt:
Example:
Question: What's the capital of France?
Thought: I need to search for France's capital
Action: search_web
Action Input: "capital of France"
Observation: Paris is the capital
Thought: I have the answer
Final Answer: The capital of France is Paris.
Now answer this:
Question: {user_query}
- Simple task - Should complete in 1-2 iterations
- Complex task - Requires 5+ iterations
- Impossible task - Gracefully fails at max iterations
- Tool errors - Recovers from failures
- Ambiguous query - Asks clarifying questions
- Cyclical reasoning - Detects and breaks loops
- Completion rate - % of tasks completed successfully
- Avg iterations - Lower is better (efficiency)
- Token usage - Cost per task
- Time to completion - Latency
Always set a limit:
MAX_ITERATIONS = 10 # Adjust based on task complexity
if iteration >= MAX_ITERATIONS:
return f"Task incomplete after {MAX_ITERATIONS} attempts. Last state: {context}"Log full reasoning trace:
{
"task_id": "abc123",
"query": "Best laptops under $1000",
"iterations": [
{
"num": 1,
"thought": "I should search for laptop reviews",
"action": "search_web",
"action_input": "best laptops under $1000 2026",
"observation": "...",
"duration_ms": 1200
}
],
"final_answer": "...",
"total_tokens": 4500,
"cost": 0.045
}def execute_tool_with_retry(action, action_input, max_retries=3):
for attempt in range(max_retries):
try:
return tools[action](action_input)
except Exception as e:
if attempt == max_retries - 1:
return {"error": str(e), "suggestion": "Try a different approach"}
time.sleep(2 ** attempt) # Exponential backoffAfter N iterations, compress history:
if len(context) > 5:
# Summarize oldest observations
summary = llm.generate(f"Summarize: {context[:3]}")
context = [{"summary": summary}] + context[3:]| Pattern | Use Case | Iterations | Cost |
|---|---|---|---|
| Tool Calling | Single action | 1 | $ |
| ReAct | Multi-step reasoning | 3-10 | $$$ |
| Sequential Chain | Predefined steps | N (fixed) | $$ |
| Router | Dispatch to specialist | 1 | $ |
When to upgrade from tool calling to ReAct:
- Task requires exploring multiple paths
- Don't know exact sequence upfront
- Need self-correction based on intermediate results
- ReAct Paper: arXiv:2210.03629 - Original research
- LangChain ReAct: Docs
- ReAct Prompting Guide: Learn Prompting
- Need structured planning? → See Chain-of-Thought
- Want predefined steps? → See Sequential Chain
- Need specialized agents? → See Router Agent
- Going to production? → See Error Handling