PromptisePromptise
Why PromptiseUse casesRoadmapDocs
GitHubGet started
Promptise - AI Framework LogoPromptise

The foundation layer for agentic intelligence. Build, secure and operate autonomous AI systems with Promptise Foundry.

pip install promptise

[01] Foundry

  • MCPcast
  • The Promptise Agent
  • Reasoning Engine
  • MCP
  • Agent Runtime
  • Prompt Engineering
  • Execution Engine
  • Agent Identity

[02] Resources

  • Documentation
  • GitHub
  • Guides
  • Learning Paths
  • Questions

[03] Company

  • About
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Subprocessors

© 2026 Promptise by Manser Ventures. All rights reserved.

Open source · Python

← Guides> Agent reasoning patterns

ReAct Agent From Scratch in Python: The Reason-Act Loop

Build a ReAct agent from scratch in Python: the Thought, Action, Observation loop, a 60-line controller with a real trace, and how it maps to tool calling.

Level
Intermediate
Reading time
8 min
Published
Sep 12, 2025
Last updated
Oct 25, 2026
By
Promptise Team
  • ReAct
  • Tool Calling
  • Agent Loop
Contents · 11 sections↓
  1. 01What is a ReAct agent?
  2. 02How the ReAct loop works
  3. 03When to use ReAct (and when not to)
  4. 04Step 1: Define two tools
  5. 05Step 2: Write the ReAct prompt
  6. 06Step 3: Write the controller loop
  7. 07Step 4: Run it
  8. 08From text ReAct to native tool calling
  9. 09Common mistakes
  10. 10Frequently asked questions
  11. 11Next steps

A ReAct agent alternates between reasoning about what it needs and acting with a tool, then reads the result and decides again, until it can answer. It's the loop behind most AI agents. This guide explains the pattern from the original paper, builds a working loop from scratch in about 60 lines of Python with a real trace, and maps it to native tool calling. It's for developers who want to know what an agent framework does, or need a minimal loop they control.

[01]

What is a ReAct agent?

ReAct (Reason + Act) is a prompting pattern where the model produces interleaved reasoning traces and actions: a Thought about what to do next, an Action that calls a tool, then an Observation with the tool's result, repeated until a final answer. It was introduced in ReAct: Synergizing Reasoning and Acting in Language Models (Yao et al., 2022, ICLR 2023). The reasoning steers which tool to call; the observations ground the reasoning in real data.

Key takeaways:

  • The loop is simple: model proposes an action, your code runs it, the result goes back to the model.

  • Your code is in charge: it parses actions, runs tools, enforces limits and decides when to stop.

  • Feed errors back as observations, so the model can correct itself.

  • Always cap the steps. A loop without a limit will eventually run forever.

  • In production, use native tool calling instead of parsing text; the loop is the same.


[02]

How the ReAct loop works

Each turn of the loop has three parts:

  1. Thought: the model writes what it needs next.

  2. Action: the model names a tool and its input, as tool[input], and stops there.

  3. Observation: your code runs the tool and adds its result.

The loop repeats until the model writes a Final Answer instead of an Action.

The paper's motivation: chain-of-thought alone reasons only over what the model already knows, so it can hallucinate facts and carry errors forward. Acting alone (tool calls without reasoning) loses track of the plan. Interleaving them lets reasoning "induce, track, and update action plans as well as handle exceptions", while actions fetch real information. On HotpotQA and FEVER, ReAct with a simple Wikipedia API reduced hallucination compared with chain-of-thought; on the ALFWorld and WebShop decision-making benchmarks it beat imitation and reinforcement learning baselines by 34 and 10 points of success rate, with one or two in-context examples.

The model never runs anything. It writes text that names a tool and an input. Your controller decides whether to run it.


[03]

When to use ReAct (and when not to)

ReAct fits when the next step depends on what the last step returned: search, lookup, debugging, research, support questions that need account data. It's a poor fit when the steps are known in advance (write a plan and execute it instead, see planning API calls), when there are many independent calls that could run in parallel, or when one tool call answers the question. For a comparison of ReAct with plan-and-execute, reflection and code-action on the same tasks, see AI agent reasoning patterns.


[04]

Step 1: Define two tools

Pythonreact_loop.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import ast, operator, re

DOCS = {
    "pricing": "Team plan: $12 per seat per month. Business plan: $25 per seat per month.",
    "annual billing": "Paying annually gives 15% off the monthly price. Annual plans are billed once per year.",
    "seats": "You can add seats at any time. Removed seats are credited at the next renewal.",
}

def search_docs(query: str) -> str:
    words = set(re.findall(r"\w+", query.lower()))
    hits = [f"[{k}] {v}" for k, v in DOCS.items() if words & set(re.findall(r"\w+", (k + " " + v).lower()))]
    return "\n".join(hits[:2]) or "no results"

OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv}

def calculator(expression: str) -> str:
    """Arithmetic only. Parses the expression; never calls eval()."""
    def ev(node):
        if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
            return node.value
        if isinstance(node, ast.BinOp) and type(node.op) in OPS:
            return OPS[type(node.op)](ev(node.left), ev(node.right))
        raise ValueError("only + - * / and numbers are allowed")
    return str(round(ev(ast.parse(expression, mode="eval").body), 2))

TOOLS = {"search_docs": search_docs, "calculator": calculator}

The calculator parses the expression into a syntax tree and evaluates only numbers and four operators. The model's input is text from a model; eval() on it would let it run any Python.


[05]

Step 2: Write the ReAct prompt

Pythonreact_loop.py
1
2
3
4
5
6
7
8
9
10
11
12
SYSTEM = """Answer the question by alternating Thought, Action and Observation steps.
Tools:
- search_docs[query]: searches the help center
- calculator[expression]: evaluates arithmetic such as 12*14
Format, one step per reply:
Thought: <what you need next>
Action: <tool name>[<input>]
Stop after the Action line. I will reply with "Observation: ...".
When you know the answer, reply instead with:
Thought: <why you are done>
Final Answer: <answer>
Use only facts from observations. Never compute in your head; use the calculator."""

The original paper used few-shot examples of full trajectories. Current models usually follow a format description like this one without examples. "Stop after the Action line" matters: some models keep going and write the Observation themselves, inventing the tool result. Some reasoning-model APIs don't support stop sequences, so the controller also cuts everything after the first Action.


[06]

Step 3: Write the controller loop

Pythonreact_loop.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ACTION = re.compile(r"^Action:\s*(\w+)\[(.*)\]\s*$", re.M)

def react(question: str, max_steps: int = 6) -> str:
    messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": f"Question: {question}"}]
    for step in range(max_steps):
        reply = llm(messages)                      # one model call
        if "Final Answer:" in reply:
            return reply.split("Final Answer:", 1)[1].strip()
        m = ACTION.search(reply)
        if not m:
            observation = "Error: no Action line found. Use the format Action: tool[input]."
        elif m.group(1) not in TOOLS:
            observation = f"Error: unknown tool {m.group(1)}. Tools: {', '.join(TOOLS)}."
        else:
            try:
                observation = TOOLS[m.group(1)](m.group(2))
            except Exception as e:                 # tool errors go back as observations
                observation = f"Error: {e}"
        reply = reply[: m.end()] if m else reply   # drop anything the model wrote after the Action
        messages += [{"role": "assistant", "content": reply},
                     {"role": "user", "content": f"Observation: {observation}"}]
    return "Stopped: step limit reached without a final answer."

llm() is one chat completion call; here it used gpt-5-mini at minimal reasoning effort. Four things make the loop reliable:

  • Errors become observations. A bad format, an unknown tool or a failing tool returns text the model can act on, instead of crashing the loop.

  • Truncation after the Action removes any invented observation.

  • A step limit ends runaway loops with an explicit message.

  • Only known tools run. The model can name anything; the controller runs only what's in TOOLS.

The controller loop: the model writes a Thought and an Action, a Final Answer ends the run, the controller parses the Action and runs only known tools, a missing Action, unknown tool or failing tool goes back as an error Observation, text after the Action is dropped, and the step limit stops the loop.
FigureThe ReAct controller loop: the model proposes an action, your code decides what runs

[07]

Step 4: Run it

Python
react("We have 14 seats on the Team plan. What do we pay per year if we switch to annual billing?")

The full trace:

Output
Thought: I need to find pricing info for Team plan monthly and annual to compute annual cost for 14 seats with annual billing (likely discounted rate). I'll search the help center for Team plan pricing and annual billing details.

Action: search_docs[Team plan pricing annual billing]
Observation: [pricing] Team plan: $12 per seat per month. Business plan: $25 per seat per month.
[annual billing] Paying annually gives 15% off the monthly price. Annual plans are billed once per year.

Thought: I need to calculate the annual cost for 14 seats on the Team plan applying a 15% discount to the monthly price and then multiplying by 12 months and 14 seats. I'll compute step-by-step using the calculator tool.

Action: calculator[(14*(12*(1-0.15))*12)]
Observation: 1713.6

Thought: I have the computed annual cost using the discounted monthly rate and total seats; I'm done.

Final Answer: $1,713.60 per year

Correct: 14 seats × $12 × 0.85 × 12 months = $1,713.60. Three model calls, two tool calls. The trace shows why ReAct is easy to debug: you can see what the model wanted, what it got and how it used it. If the answer were wrong, you'd know whether the search, the expression or the reasoning was at fault.


[08]

From text ReAct to native tool calling

Current model APIs have tool calling built in: you pass tool definitions as JSON schemas, and the model returns structured tool calls instead of Action: lines. The loop is the same, with less parsing:

Text ReAct (this guide)

Native tool calling

Tools described in the prompt

Tools passed as JSON schemas

Regex parses Action: tool[input]

The API returns tool_calls with name and JSON arguments

"Observation:" in a user message

A tool-result message tied to the call id

Format errors are common

Arguments are structured; schema errors still possible

Thought is visible text

Reasoning may be hidden; ask for a short plan if you want it visible

Use text ReAct to learn the pattern, with models that lack tool calling, or when you want the reasoning in plain view. Use native tool calling for anything you ship. How to build an AI agent in Python builds that version step by step, and what an agent harness is covers everything around the loop: context compaction, token budgets, loop guards and verification. Frameworks also ship ReAct as the default; in Promptise Foundry it's the default reasoning pattern, with others (verify, code-action, debate and more) selectable per agent.


[09]

Common mistakes

Mistake

Why it fails

Fix

No step limit

A confused model loops forever

Cap steps; return an explicit "stopped" result

Letting the model write Observations

It invents tool results and answers from them

Truncate after the Action; add observations only from code

Crashing on a bad action

One format slip ends the run

Return the error as an observation

eval() in a calculator tool

Model text becomes code execution

Parse with ast, allow numbers and operators only

Mental math in the Thought

Arithmetic slips

"Never compute in your head; use the calculator"

Same call repeated

Wasted steps, then the limit

Detect repeated tool calls with the same input; see agent stuck in a loop


[10]

Frequently asked questions

What does ReAct stand for?

Reason + Act. The model alternates reasoning steps (Thoughts) with actions (tool calls), and each action's result (Observation) feeds the next reasoning step. The name comes from the 2022 paper by Yao et al.

Is ReAct still used with function calling?

Yes. Native tool calling is the same loop in a structured format: the model reasons, requests a tool call, receives the result and continues. Most agent frameworks implement ReAct this way, and "ReAct agent" usually means this loop.

What's the difference between ReAct and chain-of-thought?

Chain-of-thought reasons only over what's in the prompt and the model's knowledge. ReAct adds actions, so the model can look things up or compute, and its reasoning is grounded in the results. The paper found that this reduced hallucination on fact-based questions.

How many steps should a ReAct agent get?

Enough for your hardest normal task plus a margin. Measure how many steps successful runs take, set the limit somewhat above that, and treat hitting it as a failure to log and review.

ReAct or plan-and-execute?

ReAct when each step depends on the previous result. Plan-and-execute when the steps are predictable, some actions change data, or you want to review the plan before anything runs. See plan-and-execute agents in Python.


[11]

Next steps

  • Build a production agent with native tool calling: how to build an AI agent in Python.

  • The same loop as a framework runs it: ReAct agent pattern.

  • What surrounds the loop: what is an agent harness?

  • Compare patterns on the same tasks: AI agent reasoning patterns.

  • Plan first instead: plan-and-execute agents.

  • Detect and stop loops: AI agent stuck in a loop.

  • Several agents working together: LLM agent orchestration patterns.

Contents

  1. 01What is a ReAct agent?
  2. 02How the ReAct loop works
  3. 03When to use ReAct (and when not to)
  4. 04Step 1: Define two tools
  5. 05Step 2: Write the ReAct prompt
  6. 06Step 3: Write the controller loop
  7. 07Step 4: Run it
  8. 08From text ReAct to native tool calling
  9. 09Common mistakes
  10. 10Frequently asked questions
  11. 11Next steps

Learning paths

Want more structure? Paths put guides in order, like a short course.

See the paths →

> Agent reasoning patterns

Related guides

All guides in this topic →
  • // 01Advanced · 15 minPlan-and-Execute Agents in Python: Beyond the ReAct LoopBuild a plan, act, verify, reflect agent in Python with the Promptise Reasoning Graph, then compare it with ReAct on one task: accuracy, calls, tokens.
  • // 02Advanced · 15 minCode-Action Agents in Python: One Program vs 37 Tool CallsBuild a code-action agent in Python over an MCP server: the model writes one program that calls your tools in a Docker sandbox, within a budget you set.
  • // 03Intermediate · 22 minSelf-Improving AI Agent: Learn From Tool Failures in PythonBuild a self-improving AI agent in Python that learns from failed tool calls without fine-tuning, and keep bad lessons, leaks and poisoned rules out.
  • // 04Advanced · 16 minSelf-Modifying AI Agent in Python: Open Mode & LimitsBuild a self-modifying AI agent in Python with Promptise open mode: meta-tools, hot reload, the guardrails that bound it, and the limits to plan for.
  • // 05Beginner · 14 minReAct Agent Pattern: Tool Execution Loop Explained SimplyThe ReAct agent pattern explained simply: what an agent does after it calls a tool, a loop written by hand, the same agent in Promptise, where it breaks.
  • // 06Intermediate · 15 minAI Agent Reasoning Patterns in Python: From ReAct to DebateHow AI agent reasoning patterns work, from ReAct to plan-and-execute, reflection, debate and code-action, and how to choose and switch them in Python.

Keep going.

Browse every guide by topic and level, or follow a learning path that puts them in order.

All guidesLearning paths