AI / LLM

ReAct: Reasoning and Acting in One LLM Loop

6 min readAILLM

Chain-of-thought reasoning made language models visibly better at multi-step problems. It also made a specific failure mode visible: reasoning that proceeds correctly from a premise the model fabricated. A plausible chain reaches a confident answer based on a fact that is not true, because the model had no way to check. The fix, later argued by Yao and collaborators in the 2022 ReAct paper, is to let the model act. If the next step of reasoning needs a fact, the model issues a tool call for it. The observation flows back in, and the next step of reasoning is grounded.

ReAct names this pattern: reason, act, observe, repeat. A short thought precedes each action. The action is a tool call. The observation is the tool's return value. The loop continues until the model emits a final answer. The paper reports substantial gains over pure chain-of-thought on multi-hop QA benchmarks like HotpotQA and on text-based environments like ALFWorld (Yao et al., 2022). ReAct is the foundational agent pattern in the taxonomy and the one every production framework implements under its covers.

What the loop looks like in motion

Consider the question: "What is the GDP of France multiplied by two?" A pure chain-of-thought answer is susceptible to a fabricated number. The ReAct trace, by contrast, has three turns that alternate reasoning with grounded calls.

Thought: I need the GDP of France.
Action:  search("GDP of France 2024")
Observation: France GDP is $3.03 trillion (2024).

Thought: Now multiply by two.
Action:  calculator(3.03 * 2)
Observation: 6.06

Thought: I have the answer.
Action:  finish("The GDP of France times 2 is $6.06T.")

Each Action call grounds the next Thought. The model never has to invent a number; it asks for one. If the search returns a surprising result, the next thought adapts. This is the property the paper names as self-correction: the agent can observe that an action failed or produced unexpected output and adjust its plan on the fly.

The production shape

flowchart LR
    IN([Query]) --> M[LLM with tools bound]
    M -->|tool call| T[Tool: search, calc, API]
    T -->|observation| M
    M -->|final answer| OUT([Response])

Modern implementations do not parse "Thought/Action/Observation" from free text. They use the LLM's native tool-calling capability, in which the model emits a structured tool call that the harness executes and returns as a tool message. The loop is three lines of code; the intelligence sits in the model's choices at each turn.

Two versions in code

The excerpt below is a minimal ReAct-style loop without any framework. The model is given tools. The loop exits when the response has no tool calls.

from openai import OpenAI
import json

client = OpenAI()

tools = [
    {"type": "function", "function": {
        "name": "search", "description": "Search the web for a fact.",
        "parameters": {"type": "object",
                       "properties": {"query": {"type": "string"}},
                       "required": ["query"]}}},
    {"type": "function", "function": {
        "name": "calculator", "description": "Evaluate a numeric expression.",
        "parameters": {"type": "object",
                       "properties": {"expression": {"type": "string"}},
                       "required": ["expression"]}}},
]

def run_tool(name: str, args: dict) -> str:
    if name == "search":     return do_search(args["query"])
    if name == "calculator": return str(eval(args["expression"]))
    return "unknown tool"

def react_agent(query: str, max_steps: int = 10) -> str:
    messages = [{"role": "user", "content": query}]
    for _ in range(max_steps):
        r = client.chat.completions.create(
            model="gpt-4o-mini", messages=messages, tools=tools)
        msg = r.choices[0].message
        if not msg.tool_calls:
            return msg.content
        messages.append(msg)
        for call in msg.tool_calls:
            args = json.loads(call.function.arguments)
            result = run_tool(call.function.name, args)
            messages.append({"role": "tool", "tool_call_id": call.id,
                             "content": result})
    return "step budget exhausted"

The LangGraph version uses the prebuilt ReAct agent. The framework handles the loop, adds checkpointing and streaming, and exposes interrupt hooks the raw loop does not have.

from langchain_core.tools import tool
from langchain.chat_models import init_chat_model
from langgraph.prebuilt import create_react_agent

@tool
def search(query: str) -> str:
    """Search the web for a fact."""
    return do_search(query)

@tool
def calculator(expression: str) -> str:
    """Evaluate a numeric expression."""
    return str(eval(expression))

agent = create_react_agent(
    model=init_chat_model("gpt-4o-mini"),
    tools=[search, calculator],
)

result = agent.invoke({"messages": [("user", "GDP of France times two?")]})
print(result["messages"][-1].content)

Full runnable versions live at github.com/subodhjena/agentic-patterns under examples/08_react_agent.py and examples/08_react_agent_langgraph.py.

Why ReAct beats reasoning alone

The 2022 paper measured three effects that explain the pattern's staying power.

Grounding reduces hallucination. A pure chain-of-thought answer on HotpotQA frequently invents an intermediate fact that sounds right and leads to a wrong final answer. ReAct's tool calls anchor the chain in real retrieval results, so the model cannot silently fabricate.

Observations correct plans. When a tool returns an unexpected result, the next thought can change direction. Chain-of-thought without tools has no such mechanism; once a plan is articulated, the remaining tokens commit to it.

Traces are interpretable. Every step has a named thought, a named action, and a named observation. Debugging a failed ReAct trace is far easier than debugging a failed single-pass reasoning chain.

The paper also reports that an ensemble of ReAct and chain-of-thought, where chain-of-thought serves as a fallback when ReAct gets stuck, outperforms either alone. That finding motivates several later patterns in this series, including Reflexion and plan-and-execute.

Where the loop struggles

ReAct is the default agent pattern, not a universal solution. The failure modes below recur in production.

Runaway loops. Without a step budget, a confused agent calls the same tool in slightly different ways until the context window fills. Always set max_steps, a token budget, and a detector for repeated calls.

Over-eager tool calls. A model with many tools sometimes invokes one for a question the model could answer from its own weights. This is wasteful and slow. Reduce the tool list for tasks that do not need tools, or use routing upstream to pick the tool subset.

Under-eager tool calls. The opposite failure: the model hallucinates an answer rather than searching for it. The fix is usually in the system prompt ("never answer factual questions without consulting the search tool") and in tool descriptions that read as imperatives rather than options.

Cascade errors. A bad tool output steers the next several reasoning steps. The model rarely detects that the observation itself was wrong. Tool outputs need their own quality checks where the stakes warrant it.

Shallow reasoning between tool calls. The "Thought" between actions sometimes degenerates into a one-word restatement. Prompt the model to articulate a plan before calling each tool, or switch to plan-and-execute, covered shortly, where planning is a distinct phase.

Tool definitions that are ambiguous. If a human cannot decide between two tools for a request, neither can the agent. Tool design receives a full article later in this series; for ReAct specifically, overlapping tools are the single most common cause of confused traces.

What ReAct trades

The pattern is the entry point to dynamic control flow. The axes below describe the trade against the Workflows patterns earlier in this series.

Axis Workflow (chaining, routing) ReAct agent
Control flow Author-determined Emergent from the model
Latency Low, bounded by steps in code Variable, bounded only by max_steps
Cost per task Predictable Variable, can run up with confused traces
Grounding Depends on retrieval or tool design Native via the tool-calling loop
Observability Per-step native Requires tracing to reconstruct the decision path
Failure recovery Must be authored Emergent from the next reasoning step

For open-ended tasks where the number and identity of tool calls depend on the input, ReAct is usually the right answer. For tasks with a fixed structure, the Workflow patterns earlier in this series remain preferable.

Neighbors in the series

The tool-calling agent loop, covered in the next article, describes the modern production implementation of ReAct in detail, including guardrail placement and handoff handling. Plan-and-execute, two articles later, separates planning from acting: a full plan first, then execution, possibly with replanning. The Agent-Computer Interface article covers tool design, which determines whether a ReAct agent thrives or flails. Reflexion, in the Reasoning stage, extends ReAct with an explicit self-critique phase after failures. Chain-of-thought, the starting point Yao's paper was reacting against, appears in the same stage.

References

  1. Yao, Shunyu, et al. ReAct: Synergizing Reasoning and Acting in Language Models. ICLR 2023.
  2. Anthropic. Building effective agents. December 2024.
  3. OpenAI. Assistants and the Agents SDK. 2024.
  4. LangChain. create_react_agent: prebuilt ReAct in LangGraph. 2024.
  5. Google. Agent Development Kit (ADK). 2024.
agentic-patternsreactagentstool-usereasoningaillm
← Back to all posts