Episode 08: ReAct - Introduction to AI Agents
In this chapter, we transition from using Large Language Models (LLMs) as simple text generators to building autonomous AI Agents. We explore the ReAct (Reasoning + Action) framework, which allows LLMs to dynamically think, select real-time tools, execute actions, observe outcomes, and iteratively solve complex user queries.
1. The Evolution from LLMs to AI Agents
Historically, interacting with an LLM followed a straightforward, single-turn paradigm: you provided a prompt, and the model generated a response based entirely on its internal weights. While powerful, this approach limits the LLM to acting as a static knowledge retrieval engine.
By introducing the ReAct framework, we cross the boundary separating static LLMs from autonomous AI Agents.
An AI Agent is a full-fledged, autonomous service capable of executing workflows, calling external APIs, checking databases, and making decisions based on real-time feedback. ReAct is the foundational loop that powers this agentic behavior.
2. The Limitation of Static Training Data
To understand why we need ReAct, we must look at how LLMs are trained. An LLM is trained on a massive historical dataset up to a specific cutoff date.
The Cutoff Problem
Imagine an LLM whose training data was collected up to July 20, 2026. The model possesses deep historical knowledge up to that exact date. It knows how to write code, explain historical events, and answer general questions. However, if a user asks a real-time question on July 25, 2026, the static model fails:
- Query 1: "What is the temperature in Bangalore today?"
- Query 2: "What are the current hotel prices in Dubai?"
- Query 3: "Who won the India vs. England cricket match today?"
Because these events occurred after the training cutoff date, the LLM cannot answer them using its internal weights. It cannot predict the future or guess real-time, highly volatile data.
3. The Tool Integration Solution & The Tool Selection Dilemma
To overcome static data limitations, developers provide LLMs with access to external Tools (such as weather APIs, search engines, calculators, or database connectors).
LLM + Tools Architecture:
Historical Data (Internal Weights) + Real-Time Tools (APIs, Calculators, Search) = Modern AI Agent
While giving an LLM access to tools solves the data freshness problem, it introduces a massive orchestration challenge: The Tool Selection Dilemma.
If an agent has access to 20,000 different tools, how does it decide:
- Which tool to use?
- When to use it?
- What parameters to pass to it?
- How to chain them together when the output of Tool A is required as the input for Tool B?
Why Hardcoded Logic Fails
You cannot write static if-else code to handle tool selection because users communicate in unstructured, natural language. A user might ask: "I ordered a comb to my old address, but it hasn't arrived yet. Can you check where it is, change my address if it's wrong, and refund me if it's delayed?"
Handling this query with hardcoded rules requires accounting for thousands of permutation combinations. We need the LLM itself to dynamically reason about which tools to call and in what order.
4. The ReAct Mental Model: Reasoning + Action
The ReAct framework solves the tool selection dilemma by combining Reasoning (thinking about the problem) and Action (executing tool calls) in an iterative loop.
The ReAct Cycle
- Thought: The LLM analyzes the user prompt and determines the immediate next step.
- Action: The LLM selects a tool from its available list and generates the exact arguments needed to call it.
- Observation: The system executes the tool, captures the output, and feeds it back to the LLM's context window.
- Repeat: The LLM reviews the new observation, decides if it needs another tool, and repeats the process until it can formulate a final answer.
5. Real-World Analogy: The Amazon Customer Support Agent
Consider an automated Amazon Customer Support Agent. The developer provides this agent with an LLM and a suite of Python functions representing customer service tools:
Dynamic Chaining Scenario
If a customer says: "My order hasn't arrived yet. If it's still stuck in the warehouse, cancel it and refund me. Otherwise, tell me where it is."
The ReAct agent resolves this dynamically:
- Thought: I need to check the status of the order first.
- Action: Call
get_order_statuswith the order ID. - Observation: The tool returns
"Status: Pending in Warehouse". - Thought: The order is still in the warehouse. The user requested a cancellation and refund in this scenario.
- Action: Call
refund_orderwith the order ID. - Observation: The tool returns
"Refund Successful". - Thought: The refund is complete. I can now provide the final answer.
- Final Answer: "Your order was pending in our warehouse, so I have successfully cancelled it and initiated a full refund."
6. Step-by-Step Walkthrough of a ReAct Loop
Let's trace a concrete mathematical and retrieval query:
User Prompt: "I want to buy an iPhone 17. I have ₹5000. If I buy it, how much money will I have left?"
The agent has access to two tools:
get_product_price(product_name)calculator(expression)
7. Designing the System Prompt (The Brain of the Agent)
The system prompt is the most critical component of a ReAct agent. It instructs the LLM how to think, how to format its tool calls, and when to stop.
Below is a production-grade system prompt designed to enforce the ReAct loop structure:
SYSTEM_PROMPT = """
You are a helpful Shopping Assistant. You have access to the following tools:
1. get_product_price:
- Description: Use this tool to look up the price of a specific product.
- Argument: The exact name of the product (e.g., "iPhone 17").
2. calculator:
- Description: Use this tool to evaluate mathematical expressions.
- Argument: A valid mathematical expression (e.g., "5000 - 1000").
You must strictly follow this step-by-step loop format:
Thought: [Reason about what you need to do next. What information is missing?]
Action: [Tool Name] [Argument]
Observation: [This is where the system will provide the output of the tool. Do not write this yourself.]
... (this Thought/Action/Observation cycle can repeat multiple times)
Thought: [I now have all the information needed to answer the user's request.]
Final Answer: [Your complete, user-friendly response based on the observations.]
Rules:
1. Call only ONE tool at a time.
2. After writing an Action, STOP immediately and wait for the system to provide the Observation.
3. Never guess or invent a tool's result. Always use the exact value returned in the Observation.
4. Format your Action exactly like this:
Action: get_product_price iPhone 17
or
Action: calculator 5000 - 1000
"""
Note: Providing explicit, few-shot formatting examples in the system prompt prevents the model from hallucinating tool syntaxes or generating invalid arguments.
8. Python Implementation: Building the Tools
We define our tools as standard Python functions. In a real-world application, these functions would make HTTP requests to external APIs or query a database.
import re
import time
# Tool 1: Product Price Lookup Database
def get_product_price(product_name: str) -> str:
"""
Simulates a database lookup for product prices.
"""
database = {
"iphone 17": 1000,
"iphone 15": 500
}
normalized_name = product_name.lower().strip()
price = database.get(normalized_name, 0)
return str(price)
# Tool 2: Safe Mathematical Calculator
def calculator(expression: str) -> str:
"""
Evaluates a mathematical expression safely.
"""
try:
# Note: eval() is used here for educational simplicity.
# In production, use a safe math parser to prevent code injection.
result = eval(expression, {"__builtins__": None}, {})
return str(result)
except Exception as e:
return f"Error evaluating expression: {str(e)}"
# Map tool names to their respective Python functions
TOOLS_MAP = {
"get_product_price": get_product_price,
"calculator": calculator
}
9. Python Implementation: The ReAct Execution Loop
The execution loop orchestrates the interaction between the LLM and our Python tools. It parses the LLM's output, extracts the tool call, runs the corresponding Python function, appends the result back to the conversation history, and triggers the next iteration.
import google.generativeai as genai # Or any LLM SDK of your choice
# Configure your LLM API client
# genai.configure(api_key="YOUR_API_KEY")
def run_agent(user_query: str, max_steps: int = 5):
# Initialize conversation history with the system prompt and user query
messages = [
{"role": "system", "parts": [SYSTEM_PROMPT]},
{"role": "user", "parts": [user_query]}
]
model = genai.GenerativeModel('gemini-1.5-flash') # Using a standard fast model
print(f"Starting Agentic Workflow for Query: '{user_query}'\n" + "="*50)
for step in range(1, max_steps + 1):
print(f"\n[STEP {step}]")
# 1. Call the LLM with the current conversation history
response = model.generate_content(messages)
response_text = response.text
print(response_text)
# Append the LLM's response to the conversation history (Assistant Role)
messages.append({"role": "model", "parts": [response_text]})
# 2. Check if the LLM has arrived at the Final Answer
if "Final Answer:" in response_text:
print("\n>>> Workflow Completed Successfully! <<<")
break
# 3. Parse the Action using Regular Expressions
# Looking for pattern: Action: tool_name argument
action_match = re.search(r"Action:\s*(\w+)\s+(.*)", response_text)
if action_match:
tool_name = action_match.group(1).strip()
tool_argument = action_match.group(2).strip()
print(f"-> System parsed Action: Tool='{tool_name}', Argument='{tool_argument}'")
# 4. Execute the corresponding tool
if tool_name in TOOLS_MAP:
tool_function = TOOLS_MAP[tool_name]
try:
observation = tool_function(tool_argument)
except Exception as e:
observation = f"Error executing tool: {str(e)}"
else:
observation = f"Error: Tool '{tool_name}' is not available."
print(f"-> System Observation: {observation}")
# 5. Append the Observation back to the conversation history
# This allows the LLM to read the tool output in the next iteration
observation_message = f"Observation: {observation}"
messages.append({"role": "user", "parts": [observation_message]})
else:
# If the model didn't output a valid action and didn't finish, force-stop or prompt again
print("-> Warning: No valid Action or Final Answer found. Stopping loop.")
break
# Rate limit safety delay
time.sleep(2)
10. Analyzing the Execution Logs: Step-by-Step Trace
Let's execute the agent with our target query and analyze the step-by-step trace generated by the system:
run_agent("I have ₹5000. What is the price of an iPhone 17, and how much money will I have left?")
Step-by-Step Execution Trace
STEP 1
- LLM Output:
TEXT
Thought: First, I need to find the price of the iPhone 17. Action: get_product_price iPhone 17 - System Parser:
- Parsed Action: Tool =
get_product_price, Argument =iPhone 17 - Execution: Calls
get_product_price("iPhone 17")which returns1000. - Observation Sent to LLM:
Observation: 1000
- Parsed Action: Tool =
STEP 2
- LLM Output:
TEXT
Thought: Now that I know the price of the iPhone 17 is 1000, I need to calculate how much money will be left from my initial 5000. Action: calculator 5000 - 1000 - System Parser:
- Parsed Action: Tool =
calculator, Argument =5000 - 1000 - Execution: Calls
calculator("5000 - 1000")which returns4000. - Observation Sent to LLM:
Observation: 4000
- Parsed Action: Tool =
STEP 3
- LLM Output:
TEXT
Thought: I have calculated that 4000 will be left. I have all the information needed to answer the user's query. Final Answer: The price of an iPhone 17 is ₹1000. Since you started with ₹5000, you will have ₹4000 left after buying it. - System Parser:
- Detects
"Final Answer:"in the response text. - Breaks the loop and terminates successfully.
- Detects
11. Production Pitfalls, Rate Limits, and Debugging Mindset
When deploying ReAct agents in production environments, developers face several common challenges:
1. Infinite Loops
If an LLM fails to parse an observation correctly or gets confused by a tool's output, it may repeatedly call the same tool with the same arguments, creating an infinite loop.
- Mitigation: Always set a strict
max_stepslimit (e.g., 5 to 10 iterations) in your execution loop to prevent runaway API costs.
2. Rate Limiting (API Exhaustion)
Because the ReAct loop makes multiple rapid, sequential calls to the LLM within a single user session, you can quickly hit token or request rate limits.
- Mitigation: Implement exponential backoff retry logic and introduce slight delays (
time.sleep) between loop iterations.
3. Parsing Failures
Smaller, less capable open-source models often struggle to adhere strictly to the Action: Tool Name Argument format. They might output markdown tables, write conversational text before the action, or hallucinate tool names.
- Mitigation:
- Use robust regular expressions that can handle minor formatting variations.
- Provide explicit negative constraints in your system prompt (e.g., "Never write 'Product = iPhone 17' inside the action").
- Upgrade to highly capable reasoning models (like Gemini Pro, GPT-4, or Claude Sonnet) for agentic tasks.
Summary and Key Takeaways
- ReAct Definition: ReAct stands for Reasoning + Action. It is an agentic framework that combines reasoning (thinking steps) with action (calling external tools) in an iterative loop.
- Beyond Static Data: ReAct allows LLMs to overcome their training data cutoff limitations by dynamically querying external APIs, databases, and search engines.
- Dynamic Orchestration: Unlike static
if-elseprogramming, ReAct lets the LLM decide which tools to call, what arguments to pass, and how to chain outputs based on natural language inputs. - The ReAct Loop Structure: The loop consists of four repeating phases: Thought -> Action -> Observation -> Repeat (until a Final Answer is reached).
- System Prompt Importance: The system prompt acts as the agent's operating system, defining available tools, strict syntax rules, and few-shot examples.
- State Management: The execution loop must continuously append tool observations back to the conversation history so the LLM retains context across steps.
- Production Safeguards: Implementing
max_stepslimits, robust regex parsing, and rate-limit handling are essential to building stable, cost-effective production agents.