AI Agent Tutorial

This hands-on AI agent tutorial builds one small but real system: a Python agent that can answer a goal by calling a calculator tool. The example is intentionally narrow. Once the loop is clear, you can swap in search, databases, code execution, or business APIs.
Understand the agent loop first →What You Will Build
The finished program has four moving parts: a goal from the user, a language model that chooses the next action, a calculator tool, and a bounded loop that feeds the tool result back to the model. The model can either request a tool or return a final answer. It cannot run forever because the program enforces a maximum number of turns.
You need Python 3.10+, an API key for your model provider, and a basic understanding of functions and JSON. For a framework-based version with memory and deployment, continue to the full create-your-own-agent guide.
1. Set Up the Project
Create a virtual environment and install the OpenAI Python client:
python3 -m venv .venv
source .venv/bin/activate
pip install openaiStore your key as OPENAI_API_KEY in the shell or a local environment file. Never put a real key in source control.
2. Define a Tool Contract
A tool contract tells the model the tool name, what it does, and the JSON arguments it may receive. Keep the function itself deterministic; the model should choose when to call it, not redefine how it calculates.
from openai import OpenAI
client = OpenAI()
TOOLS = [{
"type": "function",
"name": "calculate",
"description": "Evaluate a basic arithmetic expression.",
"parameters": {
"type": "object",
"properties": {"expression": {"type": "string"}},
"required": ["expression"],
"additionalProperties": False,
},
}]In production, do not pass arbitrary strings to Python's eval. Use a parser or a restricted expression library. The example below keeps the function small so the orchestration is easy to see.
3. Write the Reason-Act-Observe Loop
The loop sends the goal to the model, checks whether it requested a tool, executes that tool, and sends the result back as the next input. When the model returns text without a tool call, the task is complete.
import ast
import json
import operator
OPS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
}
def calculate(expression: str) -> float:
tree = ast.parse(expression, mode="eval").body
if not isinstance(tree, ast.BinOp) or type(tree.op) not in OPS:
raise ValueError("Only basic binary arithmetic is allowed")
left = tree.left.value if isinstance(tree.left, ast.Constant) else None
right = tree.right.value if isinstance(tree.right, ast.Constant) else None
if not isinstance(left, (int, float)) or not isinstance(right, (int, float)):
raise ValueError("Only numeric constants are allowed")
return OPS[type(tree.op)](left, right)
def run_agent(goal: str, max_turns: int = 6) -> str:
inputs = [{"role": "user", "content": goal}]
for _ in range(max_turns):
response = client.responses.create(
model="gpt-5",
input=inputs,
tools=TOOLS,
)
inputs += response.output
calls = [
item for item in response.output
if item.type == "function_call"
]
if not calls:
return response.output_text
for call in calls:
if call.name != "calculate":
raise ValueError(f"Unknown tool: {call.name}")
args = json.loads(call.arguments)
result = calculate(args["expression"])
inputs.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": str(result),
})
raise RuntimeError("Agent reached its turn limit")A request such as "What is 19 * 7?" may produce one tool call and then a final response. A harder task can produce several calls. The program always keeps the model on the same contract: request, execute, observe, and decide again.
4. Test the Agent
Test the tool separately before testing the model. Then add cases for a normal calculation, malformed input, an unknown tool request, a tool exception, and a task that exceeds the turn limit. Record the number of model calls, elapsed time, and tool failures; those three measurements explain most early surprises in cost and reliability.
run_agent("What is 19 * 7?")returns 133.- Invalid expressions fail closed with a useful error.
- A repeated tool request stops at
max_turns.
5. Make It Safe to Run
Tool access is a permission boundary. Give an agent only the tools it needs, validate every argument, and require confirmation before sending an email, changing a record, spending money, or deleting data. Add timeouts to network tools and redact secrets from logs.
Once the basic loop works, add structured output validation, short-term conversation state, and an evaluation set of real tasks. The architecture guide explains how perception, planning, tools, and memory fit around this minimal loop.
Frequently Asked Questions
Do I need a framework for this AI agent tutorial?
No. Building the loop with one SDK call makes the control flow easy to understand. Use a framework when you need durable state, tracing, retries, handoffs, or a larger team maintaining the agent.
Can I replace the calculator with a real tool?
Yes. Keep the same contract and replace the function with a search, database, or internal API call. Treat the external system as untrusted input and validate its response before sending it back to the model.
Why does the tutorial cap the number of turns?
A cap prevents an incorrect plan or failing tool from creating an expensive infinite loop. Production agents should also have time, token, and financial budgets.
Next Steps
Compare orchestration options in the AI agent framework comparison, then return to the homepage architecture guide for the full perceive-to-feedback workflow.
Compare frameworks before you scale →Read how AI agents work under the hood →ai agent tutorial — return to the complete AI agent architecture guide.
Was this helpful?
Your feedback stays on this page — no tracking.