ReAct: Synergizing Reasoning and Acting
Paper: Yao et al., 2022 - arXiv:2210.03629 Venue: ICLR 2023 Field: NLP · AI Agents · Tool Use
Production Viability Rating
| Dimension | Rating | Notes |
|---|---|---|
| Compute | 🟢 Consumer GPU | Works with any LLM, including small open-source models |
| Implementation | 🟢 Days | The pattern is simple; integrations take time |
| Production Ready | 🟢 Ship it now | This pattern is in production everywhere |
Plain English Summary
Before ReAct, LLMs either thought (chain-of-thought reasoning) or acted (called tools). ReAct interleaves both. The model thinks, acts, observes the result, thinks again, acts again - in a loop - until it reaches an answer.
That's it. That's the whole paper. But the implications are enormous.
The Core Idea
Thought: I need to find the population of Paris.
Act: Search["population of Paris 2024"]
Obs: Paris has a population of approximately 2.1 million.
Thought: Now I have the data. I can answer the question.
Act: Finish["Paris has approximately 2.1 million people."]
The model generates Thought:, Act:, and Obs: as plain text. The system intercepts Act: lines, runs the tool, and injects the Obs: back into context. The model never "knows" it called a tool - it just sees more text.
Why It Matters for Engineers
Before ReAct: Agents were either pure reasoning (hallucinating facts) or pure tool-calling (no reasoning about what to do next).
After ReAct: The reasoning trace is part of the context. This means:
- The model can recover from failed tool calls by reasoning about the failure
- You can debug agents by reading the thought trace
- The pattern works with any LLM that follows instructions
The dirty secret: ReAct is not a framework. It's a prompting pattern. Everything called an "agent framework" today (LangChain, LlamaIndex, AutoGen) is essentially ReAct + tooling.
Implementation Notes
Minimal implementation
REACT_PROMPT = """You are an agent. Respond using this format:
Thought: [your reasoning]
Action: [tool_name]([input])
Observation: [result of action - provided by system]
... repeat until done ...
Final Answer: [answer]
Available tools: {tools}
"""
def run_react_agent(query, tools, llm, max_steps=10):
history = [{"role": "user", "content": REACT_PROMPT.format(tools=tools) + f"\nQuestion: {query}"}]
for _ in range(max_steps):
response = llm.complete(history)
history.append({"role": "assistant", "content": response})
if "Final Answer:" in response:
return response.split("Final Answer:")[-1].strip()
if "Action:" in response:
tool_name, tool_input = parse_action(response)
observation = tools[tool_name](tool_input)
history.append({"role": "user", "content": f"Observation: {observation}"})
return "Max steps reached without answer."
Where it breaks in production
- Looping - The model can get stuck in a thought-action loop. Always set
max_steps. - Context overflow - Long agent runs fill the context window. Summarize history periodically.
- Tool error handling - If a tool fails, the model often hallucinates the observation. Inject explicit error messages.
- Prompt sensitivity - The
Thought/Act/Obsformat must be consistent. Deviations cause parsing failures.
Limitations the Paper Glosses Over
- Evaluated on simple question-answering benchmarks (HotpotQA, Fever). Real-world tasks are much messier.
- Assumes the LLM reliably follows the
Thought/Act/Obsformat. Smaller models often don't. - No mention of context management for long task horizons.
- Tool errors are not systematically addressed.
What Changed After
| Paper | Year | What it added |
|---|---|---|
| Toolformer (Schick et al.) | 2023 | LLMs that learn when to call tools, not just how |
| OpenAI Function Calling | 2023 | Structured tool calling, no prompt hacking needed |
| AutoGPT | 2023 | Long-horizon autonomous agents built on ReAct |
| LangGraph | 2024 | Stateful, cyclical agent graphs replacing linear ReAct |
