Sponsored by

The Hidden Cost of Slow SaaS Billing

Slow billing doesn't just create friction — it delays cash and compounds across every deal you close.Slow billing doesn't just create friction — it delays cash and compounds across every deal you close.

Most SaaS finance teams assume the gap between contract signature and first invoice is a minor inconvenience. The Tabs Billing Lag Calculator puts a dollar figure on it.

Answer 5 quick questions about your contracts, invoicing process, error rate, and DSO, and the calculator benchmarks your billing lag against top SaaS companies — then shows you exactly what it's costing you.

Two minutes. One number that's hard to ignore.

Calculate your billing lag and see where you stand.

TL;DR: ReAct (Reasoning + Acting) is the pattern where an LLM alternates between thinking and using tools in a loop. It outperforms pure chain of thought because it grounds reasoning in real world data. This article builds a complete ReAct agent from scratch in Python with no frameworks, no LangChain, just raw API calls and a while loop.

Welcome to Grind Engineer, your guide to becoming a better software engineer! No fluff. Pure engineering insights.

Added Job Opening in the end of the article!

Every AI agent you've used, Claude Code, ChatGPT with plugins, Cursor, follows the same core loop under the hood. The model thinks about what to do, takes an action, looks at the result, and repeats. That loop has a name. It's called ReAct. And once you understand it, you can build your own agent from scratch in about 100 lines of Python.

What Is ReAct?

ReAct stands for Reasoning + Acting. It comes from a 2022 paper by Shunyu Yao et al. at Princeton, later published at ICLR 2023. The core idea is dead simple: instead of asking an LLM to think through a problem in one shot (chain of thought), you let it alternate between thinking and doing.

The loop has three steps:

Step

What Happens

Example

Thought

The model reasons about what to do next

"I need to find the population of France"

Action

The model picks a tool and provides input

search("population of France 2024")

Observation

The system executes the tool and returns the result

"The population of France is 68.4 million"

The model receives the full history of thoughts, actions, and observations at each turn. It keeps looping until it decides it has enough information to give a final answer, at which point it calls a special finish action.

That's the entire pattern. Every major agent framework, LangChain, CrewAI, AutoGen, implements this under the hood. When you strip away the abstractions, it's just a while loop.

Why ReAct Beats Pure Chain of Thought

Plain chain of thought (CoT) asks the model to reason step by step, but entirely inside its own head. The problem? LLMs make stuff up. They hallucinate numbers, invent facts, and confidently give you wrong answers. They're reasoning in a vacuum.

ReAct fixes this by grounding reasoning in external reality. At each step, the model can reach out, check something, and correct itself.

Approach

Strengths

Weaknesses

Chain of Thought only

No external dependencies, fast

Hallucinations, no access to live data

Tool use only (no reasoning)

Gets real data

No planning, picks wrong tools, no self correction

ReAct (reasoning + tools)

Plans before acting, self corrects with real data

More LLM calls, uses more tokens

The ReAct paper showed this on benchmarks: interleaving reasoning with actions outperformed both pure reasoning and pure tool use on question answering and decision making tasks. But you don't need a paper to see why. If I asked you to find the cheapest flight from Delhi to London, you wouldn't sit there and guess prices. You'd think about which sites to check, go check them, look at the results, and adjust your search. That's ReAct.

Building a ReAct Agent From Scratch

Let's build one. No frameworks. Just Python and the OpenAI SDK (you could swap in Anthropic or any provider with tool calling support).

Step 1: Define your tools.

import json
import math
import openai

# Tool implementations
def calculator(expression: str) -> str:
    """Evaluates a math expression safely."""
    try:
        allowed = {
            "abs": abs, "round": round,
            "min": min, "max": max,
            "sqrt": math.sqrt, "pow": pow
        }
        result = eval(expression, {"__builtins__": {}}, allowed)
        return str(result)
    except Exception as e:
        return f"Error: {e}"

def search(query: str) -> str:
    """Simulates a web search. Replace with real API."""
    # In production, call Google/Bing/Tavily API here
    knowledge = {
        "population of france": "The population of France in 2024 is approximately 68.4 million.",
        "population of germany": "The population of Germany in 2024 is approximately 84.5 million.",
        "eiffel tower height": "The Eiffel Tower is 330 meters tall including antennas.",
    }
    for key, value in knowledge.items():
        if key in query.lower():
            return value
    return f"No results found for: {query}"

# Registry mapping tool names to functions
TOOLS = {
    "calculator": calculator,
    "search": search,
    "finish": None  # special action to end the loop
}

Step 2: Write the system prompt that teaches the model the format.

This is the most important part. The system prompt IS the agent's brain. It tells the model exactly how to structure its output so your code can parse it.

SYSTEM_PROMPT = """You are a helpful assistant that answers questions
by reasoning step by step and using tools when needed.

You have access to these tools:
- search(query): Search for factual information
- calculator(expression): Evaluate math expressions
- finish(answer): Return the final answer to the user

For each step, you MUST respond in EXACTLY this format:

Thought: [your reasoning about what to do next]
Action: [tool_name]
Action Input: [input to the tool]

When you have enough information to answer, use:

Thought: [your final reasoning]
Action: finish
Action Input: [your complete final answer]

RULES:
- Always start with a Thought before taking an Action
- Use one tool per step
- Wait for the Observation before your next Thought
- Do NOT make up facts. Use the search tool to verify information
- If a tool returns an error, try a different approach
"""

Step 3: Build the loop.

def run_react_agent(question: str, max_iterations: int = 10) -> str:
    client = openai.OpenAI()

    # Start with the user's question
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": question}
    ]

    for i in range(max_iterations):
        # Ask the model to think and pick an action
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            temperature=0
        )
        assistant_message = response.choices[0].message.content
        messages.append({"role": "assistant", "content": assistant_message})

        # Parse the action from the model's output
        action, action_input = parse_action(assistant_message)

        if action is None:
            # Model didn't follow the format, nudge it
            messages.append({
                "role": "user",
                "content": "Please respond using the Thought/Action/Action Input format."
            })
            continue

        # Check if the model wants to finish
        if action == "finish":
            return action_input

        # Execute the tool
        if action not in TOOLS:
            observation = f"Error: Unknown tool '{action}'. Available tools: {list(TOOLS.keys())}"
        else:
            observation = TOOLS[action](action_input)

        # Append the observation and loop back
        messages.append({
            "role": "user",
            "content": f"Observation: {observation}"
        })

        print(f"--- Step {i + 1} ---")
        print(f"Thought + Action: {assistant_message}")
        print(f"Observation: {observation}\n")

    return "Agent stopped: reached maximum iterations without finishing."


def parse_action(text: str):
    """Extract action and action input from model output."""
    action = None
    action_input = None

    for line in text.strip().split("\n"):
        if line.startswith("Action:") and "Action Input" not in line:
            action = line.split("Action:", 1)[1].strip().lower()
        elif line.startswith("Action Input:"):
            action_input = line.split("Action Input:", 1)[1].strip()

    return action, action_input

Step 4: Run it.

if __name__ == "__main__":
    answer = run_react_agent(
        "What is the combined population of France and Germany? "
        "Give me the exact sum."
    )
    print(f"\nFinal Answer: {answer}")

When you run this, here's what happens inside the loop:

--- Step 1 ---
Thought: I need to find the population of France first.
Action: search
Action Input: population of France
Observation: The population of France in 2024 is approximately 68.4 million.

--- Step 2 ---
Thought: Now I need the population of Germany.
Action: search
Action Input: population of Germany
Observation: The population of Germany in 2024 is approximately 84.5 million.

--- Step 3 ---
Thought: I have both numbers. Let me add them.
Action: calculator
Action Input: 68.4 + 84.5
Observation: 152.9

--- Step 4 ---
Thought: I now have the combined population.
Action: finish
Action Input: The combined population of France and Germany is approximately 152.9 million.

Final Answer: The combined population of France and Germany is approximately 152.9 million.

4 steps, 3 tool calls, and a correct answer grounded in actual data. A pure chain of thought approach would have guessed the numbers and probably gotten them wrong.

Failure Modes (And How to Handle Them)

ReAct isn't bulletproof. Here are the ways it breaks and what to do about each one:

Failure Mode

What Happens

Fix

Infinite loops

Model keeps searching without making progress

Set max_iterations (we used 10)

Hallucinated tool names

Model calls google_search instead of search

Return an error message listing valid tools

Wrong arguments

Calculator gets "sixty eight plus eighty four" instead of 68 + 84

Add examples in the system prompt

Context window overflow

History gets too long after many steps

Summarize old steps or use sliding window

Stuck in a rut

Model repeats the same failed action

Track action history, force different approach after 2 repeated failures

The max_iterations guard is non negotiable. Without it, one bad prompt can burn through your entire API budget in a loop. In production, also add a timeout and a token budget cap.

💡 Key Insight: The system prompt is your agent's entire personality and capability. 90% of agent debugging is prompt debugging. If your agent picks the wrong tool, don't change the code. Change the system prompt. Add examples. Be more explicit about when to use each tool.

How Production Agents Use ReAct

Claude Code is basically a ReAct agent. It receives your request, thinks about what to do (read a file? run a command? edit code?), takes the action, observes the result, and loops. The "tools" are things like file read, file write, bash execution, and search.

ChatGPT with plugins followed the same pattern. The model would reason about which plugin to call, call it, read the response, and decide whether to call another or give the final answer.

The difference between a toy ReAct agent and a production one comes down to:

Toy Agent

Production Agent

2 to 3 tools

10 to 50 tools with clear descriptions

Text parsing for actions

Structured output or native tool calling API

Simple while loop

State machine with error recovery

No memory

Conversation history + long term memory

No guardrails

Rate limits, cost caps, content filtering

Production agents also use the model's native tool calling API instead of text parsing. Our parse_action function works, but it's fragile. OpenAI, Anthropic, and Google all support structured tool calling where the model returns a JSON object specifying the tool and arguments. That's way more reliable than regex parsing model output.

Where ReAct Fits in Your Agentic AI Journey

If you've been following this series, here's how ReAct connects to everything else:

ReAct is the foundation. Once you understand this pattern, everything else in agentic AI is a variation on top of it. Multi agent systems? Multiple ReAct loops coordinating. Memory? Storing and retrieving past ReAct trajectories. Planning? A more structured version of the "Thought" step.

Build this from scratch once. Run it. Break it. Fix it. You'll understand 90% of what every agent framework is doing under the hood.

Sources:

Job Openings

Follow me on Youtube ; LinkedIn ; X ; Instagram to stay updated.

See you in the next one!
Scortier, Signing Off!

Subscribe to keep reading

This content is free, but you must be subscribed to Grind Engineer to continue reading.

Already a subscriber?Sign in.Not now

Reply

Avatar

or to participate

Keep Reading