ReAct patterns dissected: how reasoning and actions merge
What you need to know first
This article falls under module 6 (Responsible & Outlook). To fully grasp the mechanics of ReAct, it helps to have a clear understanding of the fundamentals of autonomous systems. First read the difference between an agent and a chatbot to understand the shift from one-way traffic to iterative processing. In addition, this methodology builds directly on structured prompting; check out the analysis on reasoning in intermediate steps via chain-of-thought to see how lines of thought are built up internally.
Language models excel at language understanding and pattern recognition, but they are inherently closed systems. Without external connections, they have no knowledge of current facts, cannot guarantee deterministic calculations, and cannot perform physical or digital actions. Early attempts to solve this split into two directions: generating internal reasoning traces on one hand, and making direct API calls on the other. Both approaches on their own proved vulnerable to hallucinations or blind failure.
The ReAct pattern (derived from Reasoning and Acting) solves this by weaving reasoning and acting together into a continuous, dynamic feedback loop. Instead of drawing up a static plan in advance or immediately calling a tool, the model first formulates a thought about the current status, chooses a targeted action, observes the actual system output, and adjusts its next thought accordingly. In this article, we dissect the internal anatomy of this cycle, analyze concrete implementations, and map out the structural vulnerabilities.
The anatomy of the ReAct cycle: Thought, Action, Observation
The core of the ReAct paradigm consists of three fundamental building blocks that continuously follow one another until a final answer is reached. The model runs in a controlled execution loop in which each step enriches the context for the next step. This prevents a language model from having to 'guess' at intermediate results.
The three steps manifest as follows in the interaction history:
- Thought: The model reflects on the task and the current state. It explicitly formulates what it knows, what is missing, and which sub-question needs to be answered first. This forces the model to direct its attention vectors toward logical coherence.
- Action: The model selects a specific tool and formulates the exact call parameters. This can be a search query, a database query, an API call, or a Python execution.
- Observation: The outside world (the runtime environment) executes the action and adds the raw result to the context. The language model therefore does not generate this part itself; it acts as external input.
This alternation creates a self-correcting system. When an observation produces an error message or an unexpected result, the model notices this in the next Thoughtstep, and can formulate an alternative action instead of getting stuck in a hallucinated assumption.
Why pure action or pure reasoning fails
To understand the value of ReAct, we look at the two extremes that preceded this pattern: pure Chain-of-Thought (CoT) and uncoordinated tool execution (Action-only).
With a pure CoT approach, the model generates a long series of intermediate steps without interacting with external systems. This works excellently for closed mathematical problems or logic puzzles where all variables are contained in the prompt. However, as soon as the context requires external facts (such as exchange rates, current database records, or specific domain knowledge), CoT leads to compounding errors: a small inaccuracy in step two mutates into a complete hallucination by step five.
With an Action-only approach, the model skips the reasoning step and generates API calls directly based on the initial question. As a result, it lacks the ability to break complex goals down into subgoals. The model lacks the token 'workspace' needed to determine which parameter choices make sense. The result is often a series of inefficient, repetitive, or faulty calls, because the system does not evaluate along the way whether an action has brought it closer to the goal.
| Property | Pure Chain-of-Thought | Action-only (Direct APIs) | ReAct (Fluid integration) |
|---|---|---|---|
| External fact verification | None (fully dependent on weights) | Direct via tools, but without synthesis | Continuous via intermediate observations |
| Self-correction | Very low with faulty premises | Limited to syntax error handling | High: model reinterprets errors |
| Context overhead | Low to medium | Low | High due to accumulating history |
| Suitable for multi-hop tasks | Moderate (errors stack up) | Poor (no planning capacity) | Excellent |
Constructing the execution loop: from prompt to tool call
In practice, implementing a ReAct system requires a tight control layer outside the model itself. The runtime injects a system prompt that describes the available tools and enforces the desired output format. Modern implementations rely heavily on native schemas for this; consult the analysis on tool calling and structured output to discover how models today parse JSON schemas instead of plain text.
Below is a conceptual Python loop demonstrating how a ReAct orchestrator drives the interaction between generation, parsing, and execution:
def run_react_agent(user_query: str, max_steps: int = 5) -> str:
context = f"Gebruikersvraag: {user_query}\n"
for step in range(max_steps):
# 1. Genereer Thought + Action
response = llm.generate(
prompt=context,
stop_sequences=["Observation:"]
)
context += response.text
# Controleer of het model klaar is
if "Final Answer:" in response.text:
return response.text.split("Final Answer:")[-1].strip()
# 2. Parse de gewenste tool-aanroep
action_name, action_input = parse_action(response.text)
# 3. Voer de tool extern uit (niet door LLM gegenereerd)
tool_result = execute_tool(action_name, action_input)
# 4. Injecteer de observatie terug in de context
observation_str = f"\nObservation: {tool_result}\n"
context += observation_str
return "Taak niet voltooid binnen het maximaal aantal stappen."
When we set this up in a production-ready way within distributed architectures, the implementation often shifts to API parameters that trigger specific actions. See the overview on function calling via the API for the exact payload structures and status codes needed to trigger functions programmatically.
A practical example: retrieving Dutch real estate information
Let's walk through a concrete multi-hop question: "What is the average energy label in the neighborhood of Utrecht's city hall, and how many active monuments are registered there?"
A standard model would immediately start hallucinating neighborhood statistics. A ReAct agent solves this step by step:
Thought: Ik moet eerst achterhalen in welke wijk het stadhuis van Utrecht ligt.
Action: bag_lookup(locatie="Stadhuis Utrecht")
Observation: {"adres": "Korte Minrebroederstraat 2", "wijk": "Binnenstad", "gemeente": "Utrecht"}
Thought: Het stadhuis ligt in wijk 'Binnenstad'. Nu moet ik twee datapunten verzamelen: het gemiddelde energielabel en het aantal monumenten in 'Binnenstad, Utrecht'. Ik begin met het energielabel.
Action: ep_online_query(wijk="Binnenstad", gemeente="Utrecht", metric="gemiddeld_label")
Observation: {"wijk": "Binnenstad", "gemiddeld_label": "C", "dekkingsgraad": "68%"}
Thought: Het energielabel is bekend (C). Nu moet ik het aantal actieve monumenten ophalen voor dezelfde wijk.
Action: monumenten_register_query(wijk="Binnenstad", gemeente="Utrecht", status="actief")
Observation: {"wijk": "Binnenstad", "totaal_monumenten": 1421}
Thought: Ik heb beide gegevens verzameld. Ik kan nu het volledige antwoord formuleren voor de gebruiker.
Action: finish()
Final Answer: Het stadhuis van Utrecht bevindt zich in de wijk Binnenstad. In deze wijk is het gemiddelde geregistreerde energielabel C (bij een dekkingsgraad van 68%) en staan er momenteel 1.421 actieve monumenten geregistreerd.
In this scenario, each observation acts as an anchor point. If the BAG lookup produces no result (for example due to a typo), the next Thought could read: "The location was not recognized; let me search for 'Korte Minrebroederstraat' as an alternative search term."
Pitfalls and failure modes in ReAct architectures
Although ReAct is considerably more reliable than static prompts, the dynamics introduce new risks that are inherent to iterative context building.
The main failure mechanisms are:
- Infinite loops (Action Loops): When a tool returns an empty result or a generic error message, the model regularly falls into repetition. It tries the exact same action again with minimal variation in spacing or capitalization. A hard limit on the number of steps and deduplication of actions are necessary precautions.
- Context Window Exhaustion: Each step adds a thought, tool parameters, and the full payload of the observation to the context window. With large API responses (such as big JSON lists), the window fills up quickly, leading to loss of the initial instructions and exponentially rising latency and costs.
- Wandering rationalization (Goal Drift): After three or four intermediate steps, the associative nature of the language model can cause the focus to shift. The model dives deeper into a detail from an earlier observation that is not relevant to the user's main question.
- Premature convergence: The model already decides after a single thin observation that it knows enough, draws premature conclusions in the
Thoughtstep, and generates an incomplete final answer.
ReAct versus Plan-and-Solve: architectural trade-offs
ReAct is not the only paradigm for autonomous agents. An important alternative is the Plan-and-Solvepattern (which includes approaches such as BabyAGI and plan-driven decomposition). Where ReAct operates opportunistically and reactively — deciding step by step what is useful right now — a Plan-and-Solve architecture first builds a complete graph of all required subtasks and then executes them sequentially or in parallel.
| Dimension | ReAct | Plan-and-Solve |
|---|---|---|
| Planning horizon | Short (1 step ahead) | Long (full task decomposition upfront) |
| Flexibility with errors | Very high: can switch strategy immediately | Moderate: requires dynamic replanning of the entire graph |
| Token usage | Often higher due to repeated context accumulation | Often lower per subtask, provided the plan is correct from the start |
| Parallelization | Difficult (each step depends on the previous observation) | Easy for independent subgoals |
| Ideal use case | Exploratory research, vague data, debugging | Structured workflows with predictable steps |
In modern systems, we often see hybrid forms: a high-level planner breaks a complex task down into three sub-projects, after which a ReAct loop runs within each sub-project to handle the actual execution and error resolution.
Testing, evaluating, and observing ReAct agents
Because a ReAct agent navigates a search space non-deterministically, classic software tests fall short. An agent can still arrive at the same correct answer via three different routes with different numbers of steps. To measure effectiveness, engineers analyze both the final result and the trajectory followed. Read the guidelines on evaluations for agents and testing strategies for a methodical approach to unit and integration tests.
At the system level, we use specific metrics to quantify the reliability of the ReAct loop:
- Task success ratio (Pass@k): The percentage of tasks successfully completed within the set step limit according to a validation set.
- Trajectory efficiency (Path Length): The average number of steps needed to reach the solution. A rising trend for otherwise unchanged tasks indicates redundancy in the reasoning phase.
- Tool Call Precision: The percentage of actions that contain valid parameters and do not cause runtime exceptions.
- Hallucination Rate in Thoughts: How often does a
Thoughtcontain assumptions that directly contradict the precedingObservation?
For deeper benchmark methodologies and automated evaluation of multi-step interactions, the article on evaluating AI agents through trajectory analysis offers detailed protocols and scoring rubrics.
Continue reading with
Now that the dynamic between reasoning and actions is clear, you can dive deeper into optimizing the reasoning process itself. See how advanced search structures are set up in tree-of-thoughts and graph-reasoning structures. Do you work with models that independently produce long reasoning traces before the output? Then read the analysis on built-in test-time compute in modern reasoning models.


