ReAct From Scratch: Make Your LLM Reason and Act in ~40 Lines (No LangChain)
ReAct (Reason + Act) is already baked into the frontier models you use every day — it started as a 2022 paper and became the backbone of modern AI agents. Here's what it actually is: the Thought → Action → Observation loop, a real trace, and a ~40-line build from scratch in Python, TypeScript, and Java — without the model's native tool-calling — so every moving part is visible.
Here's something most engineers don't realize: ReAct is already running inside the frontier models you use every day. When Gemini Flash, GPT, or Claude thinks, calls a tool, reads the result, and keeps going — that's ReAct. It started as a 2022 research paper (ReAct: Synergizing Reasoning and Acting in Language Models) and quietly became the backbone of nearly every modern agentic model.
So why build it from scratch? Because the built-in version is a black box. The fastest way to actually understand what an "agent" is doing is to wire the loop yourself — without the model's native tool-calling — so every moving part is visible. That's what this post does.
ReAct — short for Reason + Act — is the idea, and it's almost embarrassingly simple: instead of forcing the model to blurt one final answer, you let it think out loud and take actions in a loop, grounding each step in a real result before it continues.
The loop: Thought → Action → Observation
A ReAct agent doesn't answer. It iterates:
- Thought — reason about what it needs to do next.
- Action — call a tool (search, a calculator, an API, your own function).
- Observation — read the real result that tool returned.
- …then loop back to Thought with that new fact in hand — until it has enough to answer.
Here's a real trace for the Zurich question:
Thought: I don't know today's weather. I should look it up.
Action: get_weather("Zurich")
Observation: 18°C, raining
Thought: It's raining and mild. A jacket makes sense.
Answer: Wear a light waterproof jacket — it's 18°C and raining in Zurich.
Notice what changed. The final answer is no longer a guess pulled from training data — it's grounded in an observation the model actually fetched. And because each step is explicit, the model can self-correct: if get_weather had returned an error, the next Thought would react to that instead of barreling ahead.
Why not just chain-of-thought? Or just tool-calling?
ReAct sits exactly between the two things people usually reach for, and it fixes what each one is missing.
- Chain-of-thought makes the model reason ("let's think step by step") — but it still can't do anything. It reasons beautifully over stale, made-up facts. Great thinking, no hands.
- Raw tool-calling lets the model act — but a single tool call doesn't reason between calls. It fires one function and answers. It can't chain "look this up → now that I know X, look up Y."
ReAct interleaves both: reason, act, observe, reason again. That interleaving is the whole trick. It's why an agent can break a fuzzy request into steps, gather facts as it goes, and recover when a step fails.
ReAct in ~40 lines (no framework)
To see the machinery clearly, we hand-roll the loop — no LangChain AgentExecutor, no graph library, and crucially no native tool-calling API. We parse the model's plain-text Action: ourselves so nothing is hidden. The entire loop is a while, a prompt, and a tiny tool registry. Here it is end to end — in Python, TypeScript, or Java; click the tab for your stack:
import re
from openai import OpenAI # or any chat model client
client = OpenAI()
# 1. Your tools — just normal Python functions.
def get_weather(city: str) -> str:
fake = {"Zurich": "18°C, raining", "Cairo": "34°C, sunny"}
return fake.get(city, "unknown")
TOOLS = {"get_weather": get_weather}
# 2. The system prompt teaches the model the ReAct format.
SYSTEM = """You solve tasks in a loop. Each turn output exactly one of:
Thought: <your reasoning>
Action: <tool_name>(<arg>)
...or, when you can finally answer:
Answer: <final answer>
Available tools: get_weather(city)"""
def react(question: str, max_steps: int = 5) -> str:
messages = [{"role": "system", "content": SYSTEM},
{"role": "user", "content": question}]
for _ in range(max_steps):
reply = client.chat.completions.create(
model="gpt-4o-mini", messages=messages
).choices[0].message.content
messages.append({"role": "assistant", "content": reply})
if "Answer:" in reply: # the model is done
return reply.split("Answer:", 1)[1].strip()
m = re.search(r'Action:\s*(\w+)\((.*?)\)', reply) # parse the action
if not m:
continue
name, arg = m.group(1), m.group(2).strip().strip('"\'')
observation = TOOLS[name](arg) # run the real tool
messages.append({"role": "user",
"content": f"Observation: {observation}"})
return "Stopped: no answer within step budget."
print(react("What should I wear in Zurich today?"))The Java sample keeps
callModel(messages)as the one thing you wire to your provider — a plain HTTPS POST to the chat-completions endpoint that returns the assistant's text. The ReAct loop itself is identical across all three languages.
That's the whole pattern. The model emits a Thought and an Action; your code runs the tool and feeds the Observation back; the loop repeats until the model emits an Answer. The max_steps guard is the one thing people forget — without it, a confused agent loops forever.
Everything fancier — multiple tools, retries, memory, parallel actions, JSON-structured actions instead of regex parsing — is a refinement of these forty lines, not a replacement for them.
Where to go next
If you want to watch this get built line by line — including swapping the brittle regex for clean structured tool-calls — the full "ReAct From Scratch" coding episode walks through it in Python. The 60-second version is here if you just want the mental model.
In production you usually won't hand-write this — the frontier models already do ReAct natively through their built-in tool-calling, and that's what you'll reach for. But that's exactly why building it once, by hand, is worth it: an "agent" stops being a magic product and becomes something you can see. It's just a model allowed to reason, act, and look at what actually happened — in a loop — instead of guessing once. That loop is ReAct, it's been around since 2022, and you can hold the entire thing in your head.