ReAct Tool-Calling Agent Loop
A minimal, provider-agnostic reason-act-observe loop with a step budget, tool registry, and safe termination.
The ReAct pattern (Yao et al., 2022) interleaves model reasoning with tool calls. Modern APIs expose this as native tool calling; the loop below works with any chat API that returns tool calls.
Steps
- Define tools with a name, a JSON Schema for arguments, and a Python function.
- Send the conversation plus tool schemas to the model.
- If the model returns tool calls, execute each, append the results as
toolmessages, and repeat. - Stop when the model returns a plain answer or the step budget is exhausted.
- Log every step (input, tool, args, result, latency) for debugging.
Code
import json
MAX_STEPS = 8
TOOLS = {}
def tool(schema):
def wrap(fn):
TOOLS[fn.__name__] = {"fn": fn, "schema": schema}
return fn
return wrap
@tool({"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]})
def get_weather(city: str) -> str:
return json.dumps({"city": city, "forecast": "replace with a real API call"})
def run(llm_call, user_msg: str) -> str:
messages = [{"role": "system", "content": "Use tools when needed. Answer concisely."},
{"role": "user", "content": user_msg}]
specs = [{"type": "function", "function": {"name": n, "parameters": t["schema"]}} for n, t in TOOLS.items()]
for _ in range(MAX_STEPS):
reply = llm_call(messages, specs) # returns an assistant message dict
messages.append(reply)
calls = reply.get("tool_calls") or []
if not calls:
return reply["content"]
for c in calls:
name = c["function"]["name"]
try:
args = json.loads(c["function"]["arguments"])
out = TOOLS[name]["fn"](**args)
except Exception as e: # feed errors back so the model can recover
out = f"ERROR: {e}"
messages.append({"role": "tool", "tool_call_id": c["id"], "content": out})
return "Stopped: step budget exhausted."Notes
- Always cap steps; unbounded loops are the most common agent failure.
- Return tool errors as text instead of raising, so the model can retry with corrected arguments.
Failure modes
- Infinite tool loops without a step budget.
- Raising on tool errors instead of returning them stops the agent from recovering.
- Large tool outputs blow up the context; truncate.
Markdown version: /blueprints/react-agent-loop.md
Want a deeper build of this?
Vote for an extended, fully tested version (more tools, a full workflow file). Votes decide what we build next.
Get one fully built blueprint a week
A weekly email: one blueprint with config, cost, setup time and failure modes, plus a vote on what we build next.
Related blueprints
Agent Tracing and Evaluation Setup
Capture structured traces of every LLM and tool call and run a small regression eval suite on each change.
Evaluator-Optimizer Refinement Loop
One model generates, another grades against a rubric, and the loop repeats until the rubric passes or a limit is hit.
Human-in-the-Loop Approval Gate
Let agents draft freely but require explicit human approval before any irreversible or external action.