The ReAct Pattern
Reason + Act
ReAct (Reasoning and Acting, 2022) made the agent loop popular. The model writes a Thought, then an Action (a tool call), then reads the Observation (the tool result), and repeats until it writes a Final Answer.
Question → Thought → Action → Observation → Thought → Action → Observation → ... → Final Answer
Example
Example
A ReAct trace. The early ReAct agents parsed plain text like Action: get_order[SE1042] with a regex, as below. fake_model is a stand-in that returns scripted steps.
import re
POLICIES = {"refund": "Refunds reach the original payment method in 5-7 working days.",
"return": "Most items can be returned within 10 days of delivery."}
ORDERS = {"SE1042": {"status": "delivered", "delivered_on": "12 Oct", "item": "Boat Airdopes 141"}}
def search_policy(query):
hits = [text for key, text in POLICIES.items() if key in query.lower()]
return hits[0] if hits else "No policy found."
def get_order(order_id):
return str(ORDERS.get(order_id.strip(), "Order not found."))
TOOLS = {"search_policy": search_policy, "get_order": get_order}
SCRIPT = [ # a stand-in for the model: one ReAct step per call
"Thought: I need the order details first.\nAction: get_order[SE1042]",
"Thought: It was delivered on 12 Oct. Now I need the return window.\nAction: search_policy[return window]",
"Thought: 12 Oct + 10 days = 22 Oct, so a return is still allowed.\n"
"Final Answer: Yes. Your Boat Airdopes were delivered on 12 Oct and can be returned until 22 Oct.",
]
transcript = "Question: Can I still return order SE1042?"
print(transcript)
for step in range(5):
out = SCRIPT[step] # model call would go here
print(out)
transcript += "\n" + out
if "Final Answer:" in out:
break
name, arg = re.search(r"Action: (\w+)\[(.*?)\]", out).groups()
obs = TOOLS[name](arg)
print("Observation:", obs)
transcript += "\nObservation: " + obsOutput
Question: Can I still return order SE1042?
Thought: I need the order details first.
Action: get_order[SE1042]
Observation: {'status': 'delivered', 'delivered_on': '12 Oct', 'item': 'Boat Airdopes 141'}
Thought: It was delivered on 12 Oct. Now I need the return window.
Action: search_policy[return window]
Observation: Most items can be returned within 10 days of delivery.
Thought: 12 Oct + 10 days = 22 Oct, so a return is still allowed.
Final Answer: Yes. Your Boat Airdopes were delivered on 12 Oct and can be returned until 22 Oct.ReAct Today
Modern APIs give you the same loop without text parsing. The model returns structured tool_use blocks (name + JSON input), and you send back tool_result blocks. Current Claude models also think between tool calls (adaptive thinking), which plays the role of the "Thought" lines.
Note: Never eval() text from a model to run an action. Map the tool name to a function you wrote, and validate the input.
Exercise
Exercise: Add a third tool days_left(delivered_on) to the trace and change the script so the model calls it instead of doing the date arithmetic in its Thought. Why is this better?
Show answer
def days_left(delivered_on):
day = int(delivered_on.split()[0])
return f"{10 - (21 - day)} days left" # today is 21 Oct in this demo
TOOLS["days_left"] = days_left
# Script step 3: "Thought: I should compute the days left.\nAction: days_left[12 Oct]"
# Better because LLMs are unreliable at arithmetic; code is exact and testable.