All posts
· AI Agents· OpenAI· Python· FastAPI

Building AI Agents With the OpenAI SDK

An "AI agent" sounds mystical, but underneath it is a simple idea: a loop that lets a language model decide what to do next and then actually do it. Instead of asking the model for one answer, you give it tools, run it in a loop, and let it work toward a goal.

This post breaks down the mechanism and shows the shape of a production-ready agent built with the OpenAI SDK and FastAPI.

The agent loop

Every agent, no matter how sophisticated, is built on the same cycle:

  1. Observe — collect the current state and the user's goal.
  2. Think — the model proposes an action (often a tool call).
  3. Act — your code executes that action and captures the result.
  4. Repeat — feed the result back in and loop until the goal is met.

The model never touches your database or your API directly. It only ever says "call this tool with these arguments." Your code decides whether — and how — to run it. That boundary is what keeps agents safe and debuggable.

Tools are just functions

A tool is a normal function plus a schema that tells the model when and how to use it. Keep them small and single-purpose:

def get_weather(city: str) -> str:
    """Return the current weather for a city."""
    ...

Describe the tool's name, its arguments, and what it's for. The quality of that description matters more than most people expect — it's the model's only guide to picking the right tool at the right time.

Memory: short-term vs. long-term

Two kinds of memory keep an agent coherent:

  • Short-term — the running message history for the current task. This is what fits in the context window.
  • Long-term — facts you persist (a vector store, Redis, a database) and retrieve on demand so the agent remembers across sessions.

Most "the agent forgot everything" bugs are really context management bugs: the history grew past the window, or the retrieval step returned nothing useful.

Wrapping it in an API

In production I expose the agent behind FastAPI so any frontend can call it:

@app.post("/agent")
async def run_agent(req: AgentRequest):
    return await agent.run(req.goal)

FastAPI gives you async I/O, request validation, and streaming responses — all of which matter when a single agent run can fan out into several tool calls and model turns.

Guardrails that actually matter

Agents fail in predictable ways. The ones worth building for on day one:

  • A step limit so a confused agent can't loop forever.
  • Validated tool inputs — never trust the arguments blindly; parse them.
  • Idempotent or reversible actions for anything that writes data.
  • Logging every step so you can replay exactly what the agent decided.

Takeaway

An agent is a loop, a set of tools, and a memory strategy. Start with one tool and a hard step limit, get that reliable, then add capability. The teams that ship useful agents aren't the ones with the cleverest prompts — they're the ones with the tightest loop and the best guardrails.

Mujtaba Chandio AI Engineer