AI Agents Fundamentals Basics
8 examples to get you started with AI agents - 5 basic and 3 intermediate.
You will call a model, expose one tool, run a tiny decide-act-observe loop, and see when the model answers directly versus when it asks for a tool.
Prerequisites
- Python 3.11+ recommended
- An API key for any OpenAI-compatible chat endpoint (provider-agnostic patterns below)
- Comfort reading JSON and function signatures
python -m venv .venv && source .venv/bin/activate
pip install openai pydantic
export OPENAI_API_KEY="sk-..."
# Optional: export OPENAI_BASE_URL="https://openrouter.ai/api/v1"Basic Examples
1. Call a Model Once (Not an Agent Yet)
Start with a plain completion so you can contrast it with a loop later.
from openai import OpenAI
client = OpenAI() # uses OPENAI_API_KEY; set base_url for gateways
resp = client.chat.completions.create(
model="gpt-4o-mini", # swap model id as needed; verify at build
messages=[{"role": "user", "content": "What is 17 * 24?"}],
)
print(resp.choices[0].message.content)- One request in, one text answer out.
- No tools, no loop, no observations.
- Useful baseline for cost and latency comparisons.
2. Define a Tool Schema
Tools are named functions plus JSON schemas the model can request.
from pydantic import BaseModel, Field
class MultiplyArgs(BaseModel):
a: float = Field(description="First number")
b: float = Field(description="Second number")
tools = [{
"type": "function",
"function": {
"name": "multiply",
"description": "Multiply two numbers exactly. Use for arithmetic.",
"parameters": MultiplyArgs.model_json_schema(),
},
}]- Descriptions steer tool selection.
- Pydantic keeps argument validation honest.
- One clear tool beats five vague ones at this stage.
3. Let the Model Choose: Answer or Call a Tool
Pass tools into the chat call and inspect tool_calls.
messages = [{"role": "user", "content": "What is 17 * 24? Use the tool."}]
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
)
msg = resp.choices[0].message
print("content:", msg.content)
print("tool_calls:", msg.tool_calls)- The model may return text, tool calls, or both depending on provider and prompt.
- For arithmetic, a good description makes a tool call likely.
- Log both branches; selection is the first agent decision.
4. Execute the Tool and Feed the Observation Back
Agency requires closing the loop: run the function, return the result as a tool message.
import json
def multiply(a: float, b: float) -> float:
return a * b
REGISTRY = {"multiply": (MultiplyArgs, multiply)}
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
args = REGISTRY[call.function.name][0].model_validate_json(call.function.arguments)
result = REGISTRY[call.function.name][1](**args.model_dump())
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps({"result": result}),
})
final = client.chat.completions.create(model="gpt-4o-mini", messages=messages, tools=tools)
print(final.choices[0].message.content)- Validate before execute.
- Observations become context for the next decision.
- Without this step you only have structured output, not an agent.
5. Bound the Loop with max_turns
Real agents repeat until done or until a hard stop.
def run_agent(user_text: str, max_turns: int = 5) -> str:
messages = [{"role": "user", "content": user_text}]
for _ in range(max_turns):
resp = client.chat.completions.create(
model="gpt-4o-mini", messages=messages, tools=tools
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content or ""
messages.append(msg)
for call in msg.tool_calls:
schema, fn = REGISTRY[call.function.name]
args = schema.model_validate_json(call.function.arguments)
out = fn(**args.model_dump())
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps({"result": out}),
})
return "stopped: max turns"
print(run_agent("Compute (17 * 24) + (3 * 9). Use tools for each product."))- Always set a turn limit before demos grow into production.
- Multi-step goals show why a loop exists.
- Returning a controlled stop is better than hanging.
Related: Autonomy Levels
Intermediate Examples
6. Prefer Tools Only When Needed
Compare a factual question that needs no tool with one that does.
print(run_agent("In one sentence, what is a tool call?"))
print(run_agent("What is 81 * 76?"))- Good agents skip tools when context already has the answer.
- Force-tool prompts are for demos; production prompts describe when tools help.
- Track tool-call rate as a product metric later.
7. Handle Tool Errors as Observations
Failed tools should become data for the next turn, not process crashes.
def multiply(a: float, b: float) -> float:
if a > 1_000_000:
raise ValueError("a too large for this demo tool")
return a * b
# Inside the loop, wrap execution:
try:
out = fn(**args.model_dump())
payload = {"result": out}
except Exception as e:
payload = {"error": str(e)}- Models often recover when they see a clear error object.
- Do not leak secrets or stack traces into observations.
- Count tool errors separately from model failures.
8. Log a Minimal Trace
Without a trace, agent debugging is guesswork.
trace = [] # append dicts: turn, tool, args, result/error
# After each tool execution:
trace.append({
"tool": call.function.name,
"args": args.model_dump(),
"observation": payload,
})
print(json.dumps(trace, indent=2))- A trace is the unit of review for agent behavior.
- Keep turn index, tool name, args, and observation.
- Later pages cover full observability platforms; start with structured logs.
Related
- What Actually Makes Software an "Agent"? - definition and autonomy spectrum
- Agents vs Chatbots vs Assistants vs Copilots - product labels vs mechanics
- What an Agent Can and Can't Do Reliably Today - capability boundaries
- AI Agents Fundamentals Best Practices - when not to build an agent
- Agent Loop Basics - deeper hands-on loop work
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.