ReAct
TL;DR — ReAct interleaves free-form reasoning with real tool calls in one text stream:
Thought → Action → Observation, repeated untilfinish[answer]. The model writes the thought and the action; your harness executes the action and splices the observation back in. It beats acting alone on every task tested because reasoning guides which action to take, and it beats reasoning alone on fact verification (60.9 vs 56.3 accuracy on FEVER) because the observations are grounded — but it loses to chain-of-thought on HotpotQA (27.4 vs 29.4 EM), because the fixed interleaved structure constrains reasoning. Its characteristic failure is not hallucination, it is repeating the same thought and action until the budget runs out.
1. Why this exists
Two approaches to a knowledge task, and each fails in a specific way.
Reasoning alone (chain-of-thought). The model writes a rationale and an answer from its weights. On HotpotQA this is fluent and often structurally correct, and it is also ungrounded: the paper’s own description is that CoT “is a static black box, in that the model uses its own internal representations to generate thoughts and is not grounded in the external world, which limits its ability to reason reactively or update its knowledge” (Yao et al., arXiv:2210.03629, §1). The measured consequence is stark: when 100 CoT trajectories on HotpotQA (50 correct, 50 incorrect) were hand-labelled, 56% of its failures were hallucinations — facts asserted with no source anywhere.
Acting alone (tool calls, no reasoning). The model can search and look up, but it never pauses to compare what it found to what the question asked. In the paper’s running example the Act baseline retrieves exactly the same pages as ReAct and still names the wrong answer, because nothing in the trajectory forces it to synthesise. Its HotpotQA EM is 25.7 — the worst of the four prompting formats.
The failure is not the model’s knowledge or its retrieval. It is that the two capabilities were studied and deployed separately. ReAct is the (now obvious) observation that a language model can emit both in the same stream, and that the interleaving changes what each one can do:
“Reasoning traces help the model induce, track, and update action plans as well as handle exceptions, while actions allow it to interface with external sources, such as knowledge bases or environments, to gather additional information.”
That is the whole idea. The rest of this chapter is the mechanics, the exact prompt shape, the measured trade-offs, and what still survives in 2026 when the action channel itself is a structured tool call rather than free text.
2. How it works
2.1 The augmented action space
ReAct’s formal move is one line:
- — the environment’s action space: the real calls that change something
outside the model. In the paper’s HotpotQA setup this is exactly three
actions:
search[entity],lookup[string], andfinish[answer]. - — the space of language: all text the model can generate. This is the new ingredient.
- — the augmented action space. The policy now chooses an element of rather than of .
The distinction that makes it work is what happens after an action in :
- A thought “does not affect the external environment, thus leading to no observation feedback.” It only extends the context. You get extra computation in the action channel for free, and it is inspectable because it is plain text in the trajectory.
- A real action produces an observation from the environment, which is also appended.
This asymmetry is the entire design. If you delete you get the Act baseline: no planning, no synthesis, no exception handling. If you delete you get CoT: no grounding. ReAct is the strict superset, and — usefully for interviews — the paper’s ablations are literally derived by deleting components from ReAct trajectories, which makes the comparison clean rather than convenient.
The one-line relation to memorise. CoT is ReAct with the observation channel deleted. Act is ReAct with the language space deleted. Plain
while tool_calls:loops are Act, and their weakness is synthesising a final answer even when the facts are all present in the context.
2.2 The exact prompt format
A ReAct prompt is a few-shot prefix of human-written trajectories. The project page states it flatly: “A ReAct prompt consists of few-shot task-solving trajectories, with human-written text reasoning traces and actions, as well as environment observations in response to actions” (react-lm.github.io, verified 2026-10). The interleaved shape is:
Question: <task>
Thought 1: <free-form reasoning about what to do next>
Action 1: <tool_name>[<argument>]
Observation 1: <tool output, inserted by the harness>
Thought 2: <reason over the observation; update the plan; handle exceptions>
Action 2: <tool_name>[<argument>]
Observation 2: <tool output>
...
Thought N: <synthesise>
Action N: finish[<answer>]
A concrete exemplar in exactly that format (reconstructed in the paper’s style; the verbatim exemplars are in ReAct Appendix C):
Question: Were Pavel Litvinov and Aleksandr Ginzburg both charged with
anti-Soviet activity?
Thought 1: I need to find out what each of them was charged with. Let me
search for the first.
Action 1: search[Pavel Litvinov]
Observation 1: Pavel Litvinov is a Russian physicist, writer, and
translator. He was a member of the Human Rights Movement in the Soviet
Union. In 1968 he was arrested and charged with anti-Soviet agitation and
propaganda. He was sentenced to five years of internal exile.
Thought 2: Litvinov was charged with anti-Soviet agitation and propaganda.
Now I need to check Ginzburg.
Action 2: search[Aleksandr Ginzburg]
Observation 2: Aleksandr Ginzburg was a Soviet journalist and a founding
member of the Moscow Helsinki Group. He was arrested in 1968 and charged
with anti-Soviet agitation and propaganda.
Thought 3: Ginzburg was also charged with anti-Soviet agitation. Both
were charged, so the answer is yes.
Action 3: finish[yes]
Three details here are load-bearing and easy to get wrong:
Observationis not generated by the model. The harness detects theAction:line, executes it, and splices the result in. The model is then conditioned on it on the next forward pass. This is the mechanism by which acting grounds reasoning: the observation is real text from the world sitting in the context, not a fact the model recalled.- The action syntax is task-specific and human-chosen. For HotpotQA/FEVER
the authors designed a Wikipedia API with three actions:
search[entity](“returns the first 5 sentences from the corresponding entity wiki page if it exists, or else suggests top-5 similar entities from the Wikipedia search engine”),lookup[string](“returns the next sentence in the page containing string, simulating Ctrl+F functionality on the browser”), andfinish[answer](ReAct §3.1). Note the paper’s own caveat, which is a good honesty model for your tool design: “this action space mostly can only retrieve a small part of a passage based on exact passage name, which is significantly weaker than state-of-the-art lexical or neural retrievers.” - Thought density is a hyperparameter, not a constant. For reasoning-primary tasks the paper “alternate[s] the generation of thoughts and actions … (i.e. dense thought)”; for decision-making tasks with many actions, “thoughts only need to appear sparsely in the most relevant positions … so we let the language model decide the asynchronous occurrence of thoughts and actions for itself.”
Few-shot counts, from the paper: 6 exemplars for HotpotQA, 3 for FEVER, with the footnote “We find more examples do not improve performance.” For ALFWorld and WebShop, “two or even one-shot ReAct prompting” was used (abstract).
2.3 The loop
flowchart TD
Q["Question"] --> CTX["context: prompt + trajectory"]
CTX --> LM["model generates next line(s)"]
LM --> TH["Thought (no environment effect)"]
TH --> CTX
LM --> ACT["Action: tool[arg]"]
ACT --> PARSE{"parse succeeded?"}
PARSE -->|"no"| ERR["Observation: parse error"]
ERR --> CTX
PARSE -->|"yes"| EXEC["execute tool"]
EXEC --> OBS["Observation: result"]
OBS --> CTX
LM --> FIN["Action: finish[answer]"]
FIN --> DONE["return answer"]
CTX -.-> CAP{"step cap reached?"}
CAP -->|"yes"| BAIL["abort, optionally back off to CoT-SC"]
The loop is: generate until an Action: line completes, execute it, append the
observation, repeat. Everything that makes ReAct work is inside that cycle;
everything that makes it fail is the absence of a good exit.
2.4 How it terminates, and what to do when it does not
Stopping is the design decision ReAct leaves thinnest, and the paper’s answer is worth copying exactly.
Primary exit — the model declares completion with finish[answer]. Cheapest
and least reliable: a model that has lost the plot will not emit finish.
Backstop — a step cap, chosen from data. The paper used 7 steps for HotpotQA and 5 for FEVER as its backoff threshold, “as we find more steps will not improve ReAct performance,” with the supporting measurement: “Of all trajectories with correct final answers, those with 7 steps on HotpotQA and 5 on FEVER only take up 0.84% and 1.33% respectively.” Long trajectories are almost never the productive ones. Set the cap just past where the success distribution ends and you lose under 1.5% of successes while deleting the expensive tail.
Hybrid backoff — move to a different method instead of retrying the same one. The paper’s best configurations are hybrids, and the heuristics are concrete enough to implement:
- ReAct → CoT-SC when ReAct fails to return an answer within the step cap. Translation: the agent could not ground the answer, so try the model’s own knowledge with self-consistency.
- CoT-SC → ReAct “when the majority answer among CoT-SC samples occurs less than times.” Here is the number of sampled CoT traces (21 in the paper, at temperature 0.7) and the modal-answer count is the number of those traces that agree on the single most common answer. If fewer than half agree, the model’s internal knowledge is not confident, so go retrieve.
The second rule is worth generalising because it is a pattern, not a trick: a cheap, self-contained method is used as a confidence probe, and the expensive grounded method is invoked only when the probe is uncertain. That is the same structure as speculative decoding and as routing by difficulty.
2.5 The prompt-format text-parse loop (and what replaced it)
In 2022 the action channel was a regex over generated text. In 2026 the same
loop is usually driven by the provider’s tool-calling API, where the model emits
a structured tool_calls object instead of an Action: line. The conceptual
loop is unchanged; the parsing is now grammar-constrained rather than
best-effort. See Tool / Function Calling for the schema
mechanics.
What the text format buys and costs:
Text Action: tool[arg] | Structured tool_calls | |
|---|---|---|
| Validity guarantee | none — you parse and pray | constrained decoding to a JSON schema |
| Streaming | the action completes at a newline | completes when the JSON closes |
| Parallel calls | not expressible cleanly | first-class, gated by parallel_tool_calls |
| Provider support | any completion model | models trained/fine-tuned for it |
| Debuggability | the trajectory is literally one string | args arrive as a JSON string inside a message |
| Fine-tuning value | format is learnable (paper §3.3) | format is the training target |
The deeper point survives the API change: the trajectory format is a learnable interface, not just a prompt trick. ReAct’s finetuning result is that bootstrapping on 3,000 correct trajectories made “PaLM-8B finetuned ReAct outperform all PaLM-62B prompting methods, and PaLM-62B finetuned ReAct outperform all 540B prompting methods” (ReAct §3.3). The pattern is what transfers; the delimiter is not.
2.6 The trade, stated honestly
The paper is unusually candid that interleaving is a trade, not a free lunch:
“While interleaving reasoning, action and observation steps improves ReAct’s groundedness and trustworthiness, such a structural constraint also reduces its flexibility in formulating reasoning steps, leading to more reasoning error rate than CoT.”
The human-labelled evidence on HotpotQA (Table 2, 200 sampled trajectories):
| Outcome | ReAct | CoT |
|---|---|---|
| True positive (correct trace, correct answer) | 94% | 86% |
| False positive (hallucinated trace) | 6% | 14% |
| Failure: reasoning error | 47% | 16% |
| Failure: search result error | 23% | — |
| Failure: hallucination | 0% | 56% |
| Failure: label ambiguity | 29% | 28% |
Read the two bold cells together: ReAct eliminates hallucination as a failure
mode and triples the reasoning-error rate, because fixing the output structure
to Thought → Action → Observation forbids some of the reasoning shapes CoT can
take. That is why the paper’s best result is a hybrid, and why “ReAct beats CoT”
is a claim you should never make without a task qualifier.
2.7 Where the pattern went next
Three follow-ons are the vocabulary the later chapters reuse.
Reflexion — verbal reinforcement learning. Adds a persistent
reflections buffer consulted at the start of the next attempt, so the agent
“verbally reflect[s] on task feedback signals, then maintain[s] their own
reflective text in an episodic memory buffer,” reinforcing “not by updating
weights, but instead through linguistic feedback”
(Shinn et al., arXiv:2303.11366). Reported:
91% pass@1 on HumanEval vs 80% for the previous GPT-4 state of the art. The
critical difference from ReAct is that the signal comes from a compiler or a
test, not from the model’s opinion of itself — see the caveat below.
Self-Refine — self-feedback without an environment. One LLM as generator, refiner, and critic, reported “~20% absolute on average” over seven tasks (Madaan et al., arXiv:2303.17651). The mandatory caveat is Huang et al., “Large Language Models Cannot Self-Correct Reasoning Yet” (arXiv:2310.01798): self-correction without external feedback does not reliably improve reasoning and can degrade it. Reflexion is the version with a signal; Self-Refine is the version without. Prefer the former.
Plan-and-Solve and Tree of Thoughts. Plan-and-Solve makes the plan a first-class artefact separate from execution — “first, devising a plan to divide the entire task into smaller subtasks, and then carrying out the subtasks according to the plan” (Wang et al., arXiv:2305.04091) — which is what later becomes a plan node plus executor nodes. Tree of Thoughts replaces the single trajectory with a search over thoughts (Yao et al., arXiv:2305.10601), decoupling proposal from selection; for the search machinery see Monte Carlo and Search.
3. Implementation
Two runnable pieces: the faithful text-format ReAct loop, and the modern
structured equivalent. Both are against openai 3.29.0 [as of 2026-10].
3.1 The faithful loop (text actions, as the paper did it)
"""Minimal ReAct harness in the original text format.
Requires: openai==3.29.0 [as of 2026-10].
The point is the harness, not the model: parsing, executing, and appending."""
import re
import openai
client = openai.OpenAI()
MODEL = "gpt-5.5" # [as of 2026-10]
PROMPT = """Answer the question using the tools below. Use this exact format:
Thought: <your reasoning>
Action: <one of: search[entity], lookup[string], finish[answer]>
Repeat Thought/Action until you can answer, then use finish[answer].
Question: Were Pavel Litvinov and Aleksandr Ginzburg both charged with anti-Soviet activity?
Thought 1: I need what each was charged with. Search the first name.
Action 1: search[Pavel Litvinov]
Observation 1: Pavel Litvinov is a Russian physicist and writer. In 1968 he was charged with anti-Soviet agitation and propaganda.
Thought 2: That answers the first half. Now check the second name.
Action 2: search[Aleksandr Ginzburg]
Observation 2: Aleksandr Ginzburg was a Soviet journalist. He was arrested in 1968 and charged with anti-Soviet agitation and propaganda.
Thought 3: Both were charged, so the answer is yes.
Action 3: finish[yes]
"""
ACTION_RE = re.compile(r"^\s*Action\s*\d*\s*:\s*(\w+)\s*\[(.*)\]")
def last_action(text: str):
"""(action, arg) for the last 'Action: tool[arg]' line, or None."""
found = [m for m in (ACTION_RE.match(ln) for ln in text.splitlines()) if m]
return (found[-1].group(1), found[-1].group(2).strip()) if found else None
SEARCH_INDEX: dict[str, str] = {} # populate from your retrieval backend
def wikipedia(action: str, arg: str) -> str:
"""Stand-in for the paper's three-action Wikipedia API.
SEARCH_INDEX maps entity name -> first five sentences of its page."""
if action == "search":
return SEARCH_INDEX.get(arg, "No relevant entity found. Try another name.")
if action == "lookup":
page, _, needle = arg.partition("|")
for sentence in SEARCH_INDEX.get(page, "").split(". "):
if needle.lower() in sentence.lower():
return sentence.strip()
return f"No sentence containing {needle!r} on page {page!r}."
return f"error: unknown action {action!r}"
def react(question: str, max_steps: int = 7) -> str:
"""max_steps=7 and 5 are the paper's HotpotQA/FEVER backoff thresholds."""
seen: set[tuple[str, str]] = set()
trajectory = PROMPT + f"\nQuestion: {question}\n"
obs_no = 0
for _ in range(max_steps):
completion = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": trajectory}],
temperature=0.0,
stop=["\nObservation"], # the model must not invent observations
)
step = completion.choices[0].message.content
# One completion can contain several Thought/Action pairs; execute the
# last action it produced, then let the observation close the cycle.
parsed = last_action(step)
if parsed is None:
return f"[no parsable Action; aborting]\n{step}"
action, arg = parsed
if action == "finish":
return arg
if (action, arg) in seen:
return f"[loop detected on {action}[{arg}]; aborting]"
seen.add((action, arg))
observation = wikipedia(action, arg)
obs_no += 1
trajectory += step + f"\nObservation {obs_no}: {observation}\n"
return "[step cap reached; no answer]"
Four details in that harness matter more than the prompt:
stop=["\nObservation"]prevents the model from writing its own observations. Without it, a model that has seen many exemplars will happily continue the pattern and hallucinate the tool output — and a hallucinated observation is worse than no observation, because it looks grounded.- Parsing only the generated suffix. The prompt itself contains
Action 3: finish[yes]; a naivere.searchover the whole concatenation finds the exemplar’s action first and the agent answers “yes” to every question. - The loop detector keys on
(action, arg). Repetition is ReAct’s most frequent failure; catching it in the harness is free. max_stepsis a backoff threshold, not a hard failure. The paper’s best configuration falls back to CoT-SC at this point rather than giving up.
3.2 The modern equivalent (structured actions)
The same control flow with the action channel moved to tool calling. The differences are all in the harness, not the concept:
"""ReAct loop with structured tool calls. Requires: openai==3.29.0 [as of 2026-10]."""
import json
import openai
client = openai.OpenAI()
MODEL = "gpt-5.5"
TOOLS = [{
"type": "function",
"function": {
"name": "search",
"description": "Search Wikipedia. Returns the first sentences of the "
"matching page, or up to 5 suggested entity names.",
"parameters": {
"type": "object",
"properties": {"entity": {"type": "string"}},
"required": ["entity"],
"additionalProperties": False,
},
"strict": True,
},
}]
FINISH = {"type": "function", "function": {
"name": "finish",
"description": "Return the final answer. Call this only when certain.",
"parameters": {"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
"additionalProperties": False},
"strict": True,
}}
# ReAct's Thought channel, re-expressed as an instruction: with no free-text
# action format, the "think before you act" part has to be asked for in prose.
SYSTEM = (
"Before each tool call, state in your message what you still need and why "
"this call gets it. Call `finish` only when the context already contains "
"every fact that appears in the answer."
)
# Reuse the wikipedia() stand-in from section 3.1: this loop calls the same
# tool, it just picks it via a structured tool_calls object instead of a regex.
def wikipedia_lookup(entity: str) -> str:
return wikipedia("search", entity)
def act(goal: str, max_steps: int = 7) -> str:
messages = [{"role": "system", "content": SYSTEM},
{"role": "user", "content": goal}]
seen: set[tuple[str, str]] = set()
for _ in range(max_steps):
r = client.chat.completions.create(
model=MODEL, messages=messages,
tools=TOOLS + [FINISH], tool_choice="auto",
parallel_tool_calls=False, temperature=0.0,
)
msg = r.choices[0].message
messages.append(msg.model_dump(exclude_none=True))
if not msg.tool_calls:
return msg.content or "[no answer and no tool call]"
for call in msg.tool_calls:
args = json.loads(call.function.arguments) # may raise; see tool-calling.md
key = (call.function.name, call.function.arguments)
if key in seen:
return f"[loop detected on {key[0]}; aborting]"
seen.add(key)
if call.function.name == "finish":
return args["answer"]
try:
out = wikipedia_lookup(args["entity"])
except Exception as e: # noqa: BLE001 - errors are observations
out = f"error: {type(e).__name__}: {e}"
messages.append({"role": "tool", "tool_call_id": call.id,
"content": out})
return "[step cap reached; back off to CoT-SC or escalate]"
3.3 The knobs
| Knob | Realistic range | What moving it does |
|---|---|---|
max_steps | 5-7 (paper); 10-25 for long-horizon agents | Caps both cost and the tail of failing runs |
| Exemplar count | 1-6 | 6 HotpotQA / 3 FEVER in the paper; more did not help |
| Thought density | dense / sparse / model-decided | Dense for reasoning tasks; sparse for long action sequences |
temperature | 0.0-0.7 | 0 for replayable debugging; higher only for CoT-SC voting |
stop sequences | required in text mode | Prevents self-generated observations |
| Backoff method | CoT-SC, escalation, human | Turns a dead end into a different attempt |
4. Numbers and trade-offs
4.1 The measured results
All from ReAct Table 1, PaLM-540B prompted, question-only setup (no gold paragraphs) — verified directly against the paper’s HTML at arxiv.org/html/2210.03629v3:
| Prompt method | HotpotQA (EM) | FEVER (Acc) |
|---|---|---|
| Standard (no thoughts/actions/observations) | 28.7 | 57.1 |
| CoT (Wei et al. 2022) | 29.4 | 56.3 |
| CoT-SC (21 samples, T=0.7, majority vote) | 33.4 | 60.4 |
| Act (no thoughts) | 25.7 | 58.9 |
| ReAct | 27.4 | 60.9 |
| CoT-SC → ReAct | 34.2 | 64.6 |
| ReAct → CoT-SC | 35.1 | 62.0 |
| Supervised SoTA | 67.5 | 89.5 |
Five readings of that table, in the order they matter:
- ReAct alone loses to CoT on HotpotQA (27.4 vs 29.4) and beats CoT on FEVER (60.9 vs 56.3). The paper’s explanation is specific: “Fever claims for SUPPORTS/REFUTES might only differ by a slight amount … so acting to retrieve accurate and up-to-date knowledge is vital.” Do not overclaim ReAct as universally better than CoT.
- ReAct beats Act consistently (27.4 vs 25.7; 60.9 vs 58.9) — “demonstrating the value of reasoning to guide acting, especially for synthesizing the final answer.”
- The best configurations are hybrids, and each wins on one task. ReAct → CoT-SC takes HotpotQA (35.1); CoT-SC → ReAct takes FEVER (64.6).
- A hybrid reaches CoT-SC’s 21-sample accuracy with 3-5 samples, which is the efficiency argument: the grounded method spends the samples better.
- The gap to supervised SoTA is enormous (35.1 vs 67.5 EM on HotpotQA; 62.0 vs 89.5 on FEVER). ReAct’s contribution is the paradigm, not the score.
A caveat about small gaps. The paper’s own footnote records a different CoT-SC implementation reporting 27.1 / 28.9 / 33.8 for Standard / CoT / CoT-SC. That is roughly a 1-EM-point prompt-implementation variance — the same order as several gaps in the table. Quote differences of this size with care, and never present a ~1-point win as evidence of a general principle.
Decision-making tasks. “On two interactive decision making benchmarks (ALFWorld and WebShop), ReAct outperforms imitation and reinforcement learning methods by an absolute success rate of 34% and 10% respectively, while being prompted with only one or two in-context examples” (abstract, verified). Those baselines were trained on to task instances — a 1-2 shot prompt beating a trained policy is the surprising part.
4.2 Token arithmetic: what a thought costs and what it saves
Let
- — number of thought-action-observation cycles.
- — tokens per cycle for the action and its observation (constant-ish).
- — tokens per thought (the reasoning trace).
- — tokens in the few-shot prefix.
Total context after cycles is . The term is pure added cost: thoughts buy no environment effect. Its justification is on the savings side: a thought is what lets the model notice that a retrieved page is off-topic and reformulate, which is the difference between one wasted action and a derailed run. With the failure data above — 23% of ReAct’s error cases are non-informative search results it cannot recover from — the value of is precisely the reduction of that number. If your traces show the model never reformulates a failed search, the thoughts are not earning their tokens; try sparse thought density or a cheaper model for them.
Anthropic’s context-engineering guidance gives the same instruction from the other direction: give the model “enough tokens to ‘think’ before it writes itself into a corner” — but a scratchpad that is re-sent every step is paid for every step, which is the argument for compaction (“one of the safest lightest touch forms of compaction” is clearing stale tool results, Anthropic, 2025-09-29).
4.3 Which axis ReAct moves
| Axis | ReAct vs CoT | ReAct vs Act |
|---|---|---|
| Factual grounding | better (hallucination 0% vs 56% of failures) | comparable |
| Reasoning flexibility | worse (reasoning error 47% vs 16%) | better |
| Answer synthesis | better on FEVER, worse on HotpotQA | much better |
| Tokens | higher (thoughts + observations) | higher (thoughts) |
| Latency | higher (tool round trips) | higher (thoughts) |
| Interpretability | trajectory is inspectable and editable | actions-only is opaque about intent |
The right summary sentence is the paper’s: ReAct trades reasoning flexibility for factual grounding, and the trade is worth it exactly when the model’s parametric knowledge is the bottleneck.
5. Pitfalls
Repetition loops.
Symptom: the same Thought/Action pair regenerated verbatim, or a thought
that says “let me try again” and does exactly the same thing. The paper names
this as “one frequent error pattern specific to ReAct, in which the model
repetitively generates the previous thoughts and actions,” and suspects greedy
decoding. Detect at the harness level on (action, arg); a step cap converts an
infinite loop into a bounded failure.
Parsing failures misdiagnosed as reasoning failures.
Symptom: the run dies with no parsable Action on a step whose thought was
correct, usually because the model wrote Action: search [x] with a space, used
curly brackets, or wrapped the call in markdown. The regex is the bug, not the
model. The modern fix is structural: use tool calling, where the JSON is
grammar-constrained and this class of failure disappears.
Self-generated observations.
Symptom: an Observation: line that no tool could have produced — plausible
content, no matching execution in your logs. Caused by missing stop sequences
and a prompt full of exemplars that demonstrate the pattern. This is the failure
mode that makes text-format ReAct dangerous rather than merely inefficient,
because the fabricated observation is indistinguishable from a real one at
read time.
Non-informative retrieval the model cannot escape.
Symptom: a search returns an empty result or a list of similar entities, and
the next thought does not change the query. This is 23% of ReAct’s error cases.
Put the recovery instruction in the tool’s returned string
("No matching entity. Suggestions: ..."), not only in the prompt.
Observation channels as an injection surface. Symptom: the agent reads a web page and then takes an action nobody asked for. Observations are attacker-controllable data — the whole point of ReAct is to put the world in the context. Delimit and label observations as data, never place them in a system-prompt position, and assume the model may obey them anyway; see Guardrails.
Overclaiming the comparison. Symptom: an interview answer or a slide that says “ReAct beats CoT” without a task qualifier, and a follow-up you cannot answer. The paper’s own table says the opposite on HotpotQA. Say: grounding improves, flexibility degrades, the hybrid wins.
Dense thoughts on a long-horizon task. Symptom: the context fills with reasoning traces while the agent has not moved in the environment; cost climbs, progress stalls. Switch to sparse, model-decided thought density, or cap the thought length — the paper explicitly uses sparse thoughts for decision-making tasks with “a large number of actions.”
Prompt-implementation variance read as a real effect. Symptom: you “reproduced” a paper and got a number 1-2 points off, then built a conclusion on the delta. The paper’s own footnote shows ~1 EM point of variance between two implementations of the same method.
6. Using it in a project
When ReAct is the right pattern.
- The answer requires facts the model cannot have (fresh, private, or numeric) and requires more than one hop to assemble them.
- The number of steps genuinely varies with what each step returns. If it does not, you want a workflow, not an agent.
- You can afford the latency of tool round trips and you want the trajectory to be auditable — ReAct’s inspectable format is a real product feature, not an implementation detail.
- A wrong answer is recoverable (research, drafting, triage). Do not use it where a wrong action is irreversible without a human gate.
When NOT to use it — the more useful list.
- The answer is in the model’s weights. ReAct lost to plain CoT on HotpotQA. If your task is close to that distribution, retrieval is adding latency and constraining reasoning for no accuracy gain. Measure CoT first; it is one call.
- One retrieval plus a generation suffices. That is RAG: embed the query, fetch chunks, generate. It is lower latency, more predictable, and testable per stage.
- The sequence is fixed. Search → fetch → summarise → format is four
functions and a
forloop. Writing it as an agent lets the model vary the order of steps that do not benefit from varying. - You need a structured output, not an action. Extraction, classification, and scoring are single calls with a schema. See Tool / Function Calling for why “one constrained call” is a different tool from “a loop.”
- The model is too small for the format. The paper’s own finetuning section reports that with PaLM-8B/62B, “prompting ReAct performs worst among four methods due to the difficulty to learn both reasoning and acting from in-context examples.” If your model is small, either fine-tune on trajectories or use a simpler single-tool pattern.
- Every step must be reproducible for audit. ReAct’s trajectories are not deterministic even at temperature 0 across provider versions; if you need a signed audit trail, a workflow with recorded inputs is stronger.
Practical posture in 2026. Use the ReAct idea (interleave reasoning with
tool calls, stop on a finish action, cap the steps) with the modern channel
(tool calling instead of Action: text parsing). Keep the text format for
offline evaluation and for models without tool-calling support. The prompt that
says “state what you still need and why this call gets it before each call” is
the surviving artefact of the Thought channel.
7. How you would prove it worked
Metrics, in the order an interviewer will ask for them.
- Task success / EM / accuracy against a held-out set, graded on the final answer or end state.
- Hallucination rate on a human-labelled sample. This is ReAct’s distinctive claim (0% vs CoT’s 56% of failures on HotpotQA); if you cannot measure it, you cannot claim the pattern’s main benefit. Sample 50-200 trajectories, label failure modes, and report the distribution.
- Steps and tokens per success — the efficiency axis, and the one that exposes thought density that is not earning its cost.
- Non-informative-retrieval rate — the fraction of runs where a tool returned nothing useful and the trajectory did not recover, mapped from ReAct’s 23%.
Baselines you must include. (1) Standard prompting, to establish the task is non-trivial. (2) CoT with the same model and exemplar budget — this is the arm that beats ReAct on HotpotQA and is the honest comparison. (3) Act, obtained by deleting the thoughts from your own prompt, so the comparison is a true ablation rather than a re-implementation. (4) A hybrid backoff, which is the configuration that actually won.
Ablations that answer the interesting questions.
- Delete the thoughts (→ Act). If quality does not drop, your thoughts are decorative.
- Delete the observations (→ CoT). If quality does not drop, your tools are decorative.
- Vary the step cap (3, 5, 7, 12). The paper’s data predicts a knee: little lost below the threshold, little gained above it.
- Vary exemplar count (1, 3, 6). The paper’s footnote predicts a ceiling around 6 for HotpotQA.
Reporting template (example - replace with your own numbers). “On 150 multi-hop support questions, ReAct reached 64% exact match vs 58% for CoT with the same exemplars and 49% for Act, at 3.1× the tokens and 2.4 s p50 latency (example). Human-labelled failure modes over 100 trajectories: reasoning error 41%, non-informative retrieval 18%, hallucination 2% (example). A step cap of 7 lost 1 of 96 successful runs (example).”
8. Interview drill
“Why does interleaving reasoning and acting beat either alone?” Strong answer: they fix each other’s failure modes. Reasoning alone is ungrounded — on HotpotQA 56% of CoT’s failures were hallucinations — and acting alone cannot synthesise, which is why Act scores 25.7 EM. Interleaving puts verified facts into the context between reasoning steps, and puts planning between tool calls. It is a trade, not a free lunch: ReAct’s reasoning-error rate is 47% vs CoT’s 16% because the fixed format constrains the reasoning. Common wrong answer: “giving the model more tokens to think makes it smarter.” The gain is groundedness, not compute; the paper’s own data shows ReAct losing to CoT on HotpotQA.
“How does the loop terminate?”
Strong answer: two mechanisms, by design. The model emits finish[answer] as
an action, and the harness enforces a step cap — 7 for HotpotQA, 5 for FEVER in
the paper, chosen because fewer than 1.5% of correct trajectories exceeded them.
The best configuration also changes method at the cap: ReAct → CoT-SC.
Common wrong answer: “it stops when the model is done.” Nothing in the
architecture stops it; the model forgetting to emit finish is exactly how runs
blow their budget.
“Is ReAct better than chain-of-thought?” Strong answer: task-dependent, and the paper says so. ReAct wins on FEVER (60.9 vs 56.3) where up-to-date facts matter; CoT wins on HotpotQA (29.4 vs 27.4) where the reasoning structure matters more. The best configuration is a hybrid that switches on a confidence probe. Common wrong answer: “yes, ReAct is the successor to CoT.” That claim is falsified by the paper’s own table; making it in an interview reads as having read the abstract and not the results.
“Why does ReAct hallucinate less than CoT?” Strong answer: because the trajectory contains observations the model did not generate. A fact that came from the environment is real text in the context; a fact from the weights has no provenance. The measured effect is 0% of ReAct’s failures being hallucination versus 56% for CoT. Follow-up: “what stops the model inventing an observation?” — the harness: stop sequences plus parsing only the generated suffix, and in a tool-calling system the observation is never model-generated at all.
“What is ReAct’s most common failure?” Strong answer: repetition — regenerating the same thought and action, which the paper classifies under reasoning error and attributes to greedy decoding. Next is non-informative retrieval (23% of error cases) where the model cannot reformulate a bad query. Common wrong answer: “hallucination.” That is CoT’s signature failure; a well-harnessed ReAct agent rarely hallucinates facts, though it can still assert a conclusion the context does not support.
“How does ReAct relate to function calling?”
Strong answer: the same loop with a better action channel. ReAct parsed
Action: tool[arg] from free text; modern tool calling constrains the model to
emit a schema-valid JSON call, which removes the parsing-failure class and adds
parallel calls. The Thought channel survives as a system-prompt instruction to
reason before calling.
Follow-up: “what did the text format have that JSON lacks?” — a single
inspectable string, and it worked on any completion model without provider
support; and the format is fine-tunable, which is what let PaLM-8B finetuned
ReAct beat all 540B prompted methods.
“When would you not use ReAct at all?” Strong answer: when the answer is in the weights (use CoT), when one retrieval suffices (use RAG), when the step sequence is fixed (write the workflow), and when the model is too small to learn the format from few-shot examples (fine-tune or simplify). The paper’s finetuning result is evidence that the prompt form is the weak point at small scale.
Next: Tool / Function Calling — how the free-text
Action: line became a schema-validated JSON contract, and the error-handling
path that separates a demo from a product.