React: Reasoning and Acting with Language Models
What Is ReAct? ReAct (short for Reasoning and Acting) is a prompting framework in which a language model alternates between:
Thoughts: reasoning about what it knows and what it needs to do next. Actions: calling an external tool, such as a search engine, a database lookup, or a calculator. Observations: reading the result that the tool returns. The cycle repeats until the model has enough information to give a final answer.
Question ↓ Thought: what do I need? Action: call a tool Observation: tool result ↓ Thought: what does this tell me? What next? Action: call another tool Observation: tool result ↓ Thought: I now have enough information. Final Answer ReAct was introduced in the 2022 paper "ReAct: Synergizing Reasoning and Acting in Language Models" by Yao et al. The authors showed that combining reasoning with actions, such as looking up information in Wikipedia, reduced invented facts on knowledge-intensive questions compared with reasoning alone, and helped on interactive tasks that require several steps.
Why ReAct Is Needed Reasoning Alone Uses Only the Model's Memory Chain-of-thought (Lesson 39) improves reasoning, but every fact still comes from the model's training data. If a fact is missing, outdated, or misremembered, the reasoning builds on a wrong foundation (Lesson 44).
Actions Alone Lack a Plan A system that only calls tools, without reasoning, does not know which tool to call, what to look up, or how to combine the results.
ReAct Combines Both Component Contribution Reasoning Plans what to do, interprets results, decides when to stop Acting Retrieves facts and performs operations the model cannot do reliably itself Observation Grounds the next reasoning step in real information Reasoning guides the actions, and the observations correct the reasoning.
A Worked Example The Tools Tool Input Returns lookup_course A course name The course details from the catalog calculate An arithmetic expression The exact result The Question What is the total fee for the Django course and its prerequisite course? A ReAct Trace Thought: I need the Django course's fee and its prerequisite. Action: lookup_course[Django] Observation: Django Web Development: 6 weeks, fee 4999 rupees, prerequisite: Python Basics.
Thought: The prerequisite is Python Basics. I need its fee. Action: lookup_course[Python Basics] Observation: Python Basics: 4 weeks, fee 1999 rupees, no prerequisite.
Thought: Now I add the two fees. Action: calculate[4999 + 1999] Observation: 6998
Thought: I have the total. Final Answer: The total fee is ₹6,998 (Django ₹4,999 + Python Basics ₹1,999). Example Explanation The first thought identifies what is missing: the fee and the prerequisite. The first observation reveals new information, the name of the prerequisite, which determines the next action. The model could not have planned the second lookup in advance without this observation. The calculation is done by a tool, so it is exact (Lesson 58). Every fact in the final answer comes from an observation, not from the model's memory. The Structure of a ReAct Prompt A ReAct prompt has four parts:
Tool Descriptions You can use these tools:
lookup_course[name]: returns the catalog details of a course, including its fee and prerequisite.
calculate[expression]: evaluates an arithmetic expression with + - * / and parentheses. Each tool needs a name, the expected input, and what it returns. The model uses these descriptions to decide which tool to call.
- The Format Use this format: Thought: your reasoning about what to do next Action: tool_name[input]
After each Action, stop and wait. You will receive: Observation: the tool result
Repeat Thought / Action / Observation as needed. When you have the answer, write: Thought: I have enough information. Final Answer: your answer
Rules Rules:
Use only one Action per turn.
Never write an Observation yourself.
Use only information from Observations for facts about courses.
If a tool returns no result, say so in the Final Answer instead of guessing.
- The Question Question: What is the total fee for the Django course and its prerequisite course? Why "Never Write an Observation Yourself" Matters A model that has seen the Thought/Action/Observation pattern may continue it by inventing the observation, writing a plausible tool result without calling the tool. This defeats the purpose of ReAct. The rule helps, and the code below also cuts off any text after the Action line.
Implementing ReAct in Python Step 1: Define the Tools import ast import operator from fractions import Fraction
COURSES = { "django": "Django Web Development: 6 weeks, fee 4999 rupees, prerequisite: Python Basics.", "python basics": "Python Basics: 4 weeks, fee 1999 rupees, no prerequisite.", "react": "React Fundamentals: 5 weeks, fee 2999 rupees, prerequisite: JavaScript Basics.", }
OPERATORS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv}
def lookup_course(name: str) -> str: return COURSES.get(name.strip().lower(), f"No course found named '{name.strip()}'.")
def calculate(expression: str) -> str: def evaluate(node): if isinstance(node, ast.Expression): return evaluate(node.body) if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return Fraction(str(node.value)) if isinstance(node, ast.BinOp) and type(node.op) in OPERATORS: return OPERATORStype(node.op), evaluate(node.right)) raise ValueError("Unsupported expression") try: return str(evaluate(ast.parse(expression, mode="eval"))) except (ValueError, SyntaxError, ZeroDivisionError) as error: return f"Error: {error}"
TOOLS = {"lookup_course": lookup_course, "calculate": calculate} Code Explanation lookup_course() simulates a database lookup. In a real application, it would query your database or an API. calculate() uses the safe arithmetic evaluator approach from Lesson 58, so the model's expression is never executed as general Python code. Tools return text, including error messages. An error such as "No course found" becomes an observation the model can reason about, instead of crashing the program. TOOLS maps tool names to functions, so the loop can call whichever tool the model chooses. Step 2: Parse the Model's Output import re
ACTION_PATTERN = re.compile(r"Action:\s*(\w+)[(.*?)]", re.DOTALL) FINAL_PATTERN = re.compile(r"Final Answer:\s*(.+)", re.DOTALL)
def parse_step(text: str) -> tuple[str, str | None, str | None]: """Return ("final", answer, None), ("action", tool, input), or ("invalid", None, None).""" final = FINAL_PATTERN.search(text) if final: return "final", final.group(1).strip(), None action = ACTION_PATTERN.search(text) if action: return "action", action.group(1), action.group(2) return "invalid", None, None
print(parse_step("Thought: I need the fee.\nAction: lookup_course[Django]")) print(parse_step("Thought: Done.\nFinal Answer: The total is 6998 rupees.")) print(parse_step("I am not sure what to do.")) Output:
('action', 'lookup_course', 'Django') ('final', 'The total is 6998 rupees.', None) ('invalid', None, None) Code Explanation ACTION_PATTERN captures the tool name (\w+) and the input inside square brackets (.*?, non-greedy). FINAL_PATTERN captures everything after Final Answer:. A final answer is checked first, so a response that contains both is treated as finished. Responses that match neither are reported as "invalid", so the loop can ask the model to follow the format. Step 3: The ReAct Loop Requirements: pip install openai and an OPENAI_API_KEY environment variable.
from openai import OpenAI
client = OpenAI() MODEL = "gpt-5.4" # Replace with a model available in your account
REACT_INSTRUCTIONS = """Answer the question by reasoning and using tools.
Tools:
- lookup_course[name]: returns catalog details of a course, including fee and prerequisite.
- calculate[expression]: evaluates arithmetic with + - * / and parentheses.
Format: Thought: your reasoning about what to do next Action: tool_name[input]
After each Action, stop. You will then receive an Observation. When you have enough information, write: Thought: I have enough information. Final Answer: your answer
Rules:
- One Action per turn. Never write an Observation yourself.
- Use only Observations for facts about courses.
- If a tool finds nothing, say so in the Final Answer instead of guessing."""
def react(question: str, max_steps: int = 6) -> str: transcript = f"Question: {question}\n" for _ in range(max_steps): response = client.responses.create( model=MODEL, instructions=REACT_INSTRUCTIONS, input=transcript ) step = response.output_text.split("Observation:")[0].strip() # drop invented observations transcript += step + "\n" print(step)
kind, name, argument = parse_step(step) if kind == "final": return name if kind == "invalid" or name not in TOOLS: observation = "Invalid action. Use the format Action: tool_name[input] with a listed tool." else: observation = TOOLSname transcript += f"Observation: {observation}\n" print(f"Observation: {observation}") return "Stopped: maximum number of steps reached without a final answer."
print(react("What is the total fee for the Django course and its prerequisite course?")) Code Explanation transcript Holds the complete history: the question and every Thought, Action, and Observation so far. Each request sends the full transcript, because the model is stateless (Lesson 2) and must see all previous observations to decide the next step.
split("Observation:")[0] Keeps only the text before any observation the model may have written itself. Only real tool results are added as observations.
Tool execution If the action is valid and the tool exists, the tool is called with the model's input, and the result is appended as an observation. If not, a corrective observation tells the model how to fix its format.
max_steps A hard limit on the number of cycles. Without it, a confused model could loop indefinitely, calling tools and spending money.
Return value The final answer, or a clear message if the step limit was reached.
ReAct and Native Tool Calling The text-based Thought/Action/Observation format shown here is the original ReAct approach, and it is the clearest way to understand how reasoning and acting interact.
Modern model APIs, including OpenAI's, also offer native tool calling (often called function calling). With native tool calling:
You describe tools with structured schemas instead of text instructions. The API returns tool calls as structured data, so you do not need regular expressions to parse them. The model is trained to request tools in this structured format, which is generally more reliable than parsing free text. The underlying idea is the same as ReAct: the model decides which tool to call, your code runs it, and the result is sent back for the next step. Native tool calling is covered in Lesson 78, and ReAct for agents in Lesson 108.
When to Use ReAct Good Fit Why Questions that need facts from databases, documents, or APIs Observations provide real data Multi-step lookups where each step depends on the last The next action depends on what was found Tasks combining lookups and calculations Each operation uses the right tool Situations where the source of each fact must be traceable Every fact comes from an observation Poor Fit Why Questions answerable from the prompt alone No tools needed Fixed workflows where the steps are always the same A prompt chain is simpler and more predictable (Lesson 61) Latency-sensitive features Each step is another model call Common Mistakes Mistake 1: Letting the Model Invent Observations What it is: Accepting text the model wrote after Observation: as if it were a tool result.
Why it happens: The model continues the pattern it sees.
What it causes: Invented facts presented as tool results, which is worse than plain hallucination because it looks verified.
How to fix it: Instruct the model never to write observations, and cut off any observation text in code.
Mistake 2: No Step Limit What it is: Running the loop until the model produces a final answer.
Why it happens: Test questions always finish quickly.
What it causes: Infinite loops and runaway costs on confusing inputs.
How to fix it: Set max_steps and return a clear message when it is reached.
Mistake 3: Vague Tool Descriptions What it is: Tools described only by name.
Why it happens: The tool's purpose seems obvious to the developer.
What it causes: The model calls the wrong tool or passes the wrong input format.
How to fix it: Describe each tool's input, output, and purpose.
Mistake 4: Unsafe Tools What it is: Tools that execute arbitrary code, run database writes, or send emails without checks.
Why it happens: Powerful tools make impressive demos.
What it causes: The model, or a manipulated input, can trigger harmful actions.
How to fix it: Give tools the minimum permissions they need, validate inputs, and require confirmation for actions with side effects. This is covered in the AI Agents and Prompt Safety sections.
Mistake 5: Tools That Crash on Bad Input What it is: A tool raises an exception when it receives unexpected input.
Why it happens: Tools are written for valid input only.
What it causes: The whole loop fails instead of letting the model recover.
How to fix it: Return errors as observation text so the model can correct itself.
Best Practices Describe tools precisely. Name, input format, output, and purpose.
Keep one action per step. It keeps the loop simple and the trace readable.
Only real tool results become observations. Remove anything the model writes after an action.
Return errors as observations. Let the model recover from mistakes.
Limit steps and tool permissions. Control cost and risk.
Ground facts in observations. Instruct the model not to use memory for facts the tools provide.
Use native tool calling in production. Use the text format to learn and prototype.
Try It Yourself Exercise 1: Trace a Question Run react() with the question: "Which course has a higher fee, React or Django, and by how much?" Inspect the trace and check that every fact in the final answer came from an observation.
Exercise 2: Handle a Missing Course Ask: "What is the fee for the Rust course?" Check whether the model reports that the course was not found instead of inventing a fee. If it invents a fee, strengthen the rules and try again.
Exercise 3: Add a Tool Add a tool check_seats[course name] that returns the number of available seats from a dictionary. Update the tool descriptions and ask: "Can I enroll in React and its prerequisite this month, and what would it cost?" Note how the model decides which tools to use, and what it does when a prerequisite is not in your catalog.