To build ReAct agent GPT-4o with function calling, you need a model that supports parallel tool calls and a client loop that pipes model-generated calls to local functions. This tutorial walks through a minimal but production-shaped implementation using the OpenAI Python SDK, showing exact message shapes and a runnable script.
Prerequisites
- Python 3.10 or newer
openaiPython package >= 1.0.0 (pip install openai)- An API key from OpenAI, or any OpenAI-compatible gateway
- Basic familiarity with JSON Schema and chat completions
Set your key in the environment:
export OPENAI_API_KEY="sk-..."
If you route through n4n.ai, the same OpenAI-compatible endpoint works and adds automatic fallback when a provider is degraded.
ReAct in plain terms
ReAct (Reason + Act) is not a framework feature. It is a loop: the model emits a reasoning step and optionally one or more action requests (tool calls); your code executes those actions and returns observations; the model then reasons again. GPT-4o handles this natively through the tools parameter—no special prompt prefix required.
The simplest way to build ReAct agent GPT-4o is to treat the model as a state machine that either returns text (done) or returns tool_calls (continue).
Define your tools
Tools are local functions wrapped in JSON Schema. Keep descriptions precise; GPT-4o reads them literally.
import json
def get_weather(city: str) -> str:
# Mock: in production call a real API
return json.dumps({"city": city, "temp_c": 21, "condition": "clear"})
def calculate(expression: str) -> str:
# Demo-only safe eval; use a real math parser in prod
try:
return str(eval(expression, {"__builtins__": {}}))
except Exception as e:
return f"error: {e}"
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a given city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate a basic arithmetic expression.",
"parameters": {
"type": "object",
"properties": {"expression": {"type": "string"}},
"required": ["expression"],
},
},
},
]
Client and model configuration
Use the standard client. If you want fallback across providers, point the base URL at a gateway.
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
# Alternative: client = OpenAI(api_key=os.environ["N4N_API_KEY"], base_url="https://api.n4n.ai/v1")
Model name is gpt-4o. Tool choice left as "auto" so the model decides when to call.
The reasoning-action loop
The core loop sends messages, checks for tool_calls, executes them locally, and appends results with role: "tool". The tool_call_id must match the id emitted by the model.
def run_agent(user_msg: str, max_iter: int = 5) -> str:
messages = [{"role": "user", "content": user_msg}]
for _ in range(max_iter):
resp = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content
# Append the assistant message with tool calls
messages.append(msg)
for tc in msg.tool_calls:
fn = tc.function
args = json.loads(fn.arguments)
if fn.name == "get_weather":
result = get_weather(args["city"])
elif fn.name == "calculate":
result = calculate(args["expression"])
else:
result = "unknown tool"
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})
return "max iterations reached"
When you build ReAct agent GPT-4o for production, extract the execution switch into a registry keyed by function name. The loop above is intentionally flat for clarity.
Full runnable script
import os
import json
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
def get_weather(city: str) -> str:
return json.dumps({"city": city, "temp_c": 21, "condition": "clear"})
def calculate(expression: str) -> str:
try:
return str(eval(expression, {"__builtins__": {}}))
except Exception as e:
return f"error: {e}"
TOOLS = [ ... ] # as defined above
def run_agent(user_msg: str, max_iter: int = 5) -> str:
messages = [{"role": "user", "content": user_msg}]
for _ in range(max_iter):
resp = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content
messages.append(msg)
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
if tc.function.name == "get_weather":
result = get_weather(args["city"])
elif tc.function.name == "calculate":
result = calculate(args["expression"])
else:
result = "unknown tool"
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})
return "max iterations reached"
if __name__ == "__main__":
print(run_agent("What is the temperature in Tokyo and what is 23 * 7?"))
What the output looks like
First API response (abridged):
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{"id": "call_1", "function": {"name": "get_weather", "arguments": "{\"city\":\"Tokyo\"}"}},
{"id": "call_2", "function": {"name": "calculate", "arguments": "{\"expression\":\"23 * 7\"}"}}
]
}
}]
}
Your code executes both, appends two tool messages, and sends them back. Final response:
Tokyo is currently clear with a temperature of 21°C. 23 * 7 equals 161.
GPT-4o issued parallel calls in one turn—no sequential prompting needed. That is the key efficiency win when you build ReAct agent GPT-4o instead of hand-rolling chain-of-thought.
Failure modes and guardrails
- Malformed arguments:
json.loadscan throw. Wrap in try/except and return an error string so the model can self-correct. - Infinite loops: Always cap iterations. Five is sane for simple tasks; raise it only with explicit termination cues.
- Tool hallucination: If the model calls a tool not in your registry, return
"unknown tool"and let it recover. - Token bloat: Tool results accumulate in context. Trim old observations or summarize if the agent runs long.
Routing and metering notes
If you serve this behind an inference gateway, honor client routing directives and forward provider cache-control hints to cut latency on repeated tool schemas. When using n4n.ai, per-token usage metering is automatic and the gateway forwards your tools definition unchanged, so the loop code above requires zero modifications.
For any ReAct deployment, log resp.usage each turn. GPT-4o input tokens grow linearly with tool results; watch that curve before scaling.
Closing implementation tip
Keep the system prompt empty or minimal. GPT-4o with tools already knows the ReAct contract. Adding “You are a ReAct agent” rarely improves output and wastes tokens. The loop is the agent—not the prompt.