How to build an AI agent in Python from scratch: tool calling, loop, budget

A minimal AI agent in Python with no frameworks: tools, a call loop, step and budget limits. Full code on the OpenAI SDK and an estimate of what one task costs.

5 min · · агенты · development

How do you build an AI agent in Python without dragging a big framework into the project right away? A minimal agent is about a hundred lines: a model, a few tool functions, and a loop that calls them. Below: full working code on the OpenAI SDK, a walkthrough of each part, and two safeguards you shouldn't leave an agent unattended without — a step limit and a dollar budget.

What an AI agent is

A regular model request is a question and an answer. An agent is different: the model itself decides which actions to take, sees their results, and decides what to do next. Technically it's three things:

  • A model that can call tools (tool calling).
  • Tools — ordinary Python functions described with a JSON schema.
  • A loop: send a request → the model asks to call a tool → call it, return the result → send another request. Repeat until a final answer or until a safeguard trips.

How to build an AI agent in Python: the full code

Install the SDK and put your key in an environment variable:

bash
pip install openai
export ZUKKO_API_KEY=fetch_your_key

The agent below can do three things: list the files in the data folder, read a file from there, and evaluate an arithmetic expression. That's enough, for example, to ask it to add up the amounts from several invoices.

bash
import ast
import json
import operator
import os
from pathlib import Path

from openai import OpenAI

client = OpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["ZUKKO_API_KEY"],
)

MODEL = "gpt-6.1-sol"

The code needs Python 3.9 or newer (because of Path.is_relative_to). Create a data folder next to it, put a couple of text files with invoices in it, and run the script.

How the code works

Tools

Each tool is a Python function plus its description in TOOLS. The model sees only the description: the name, the description text, and the parameter schema. How clearly they're written determines whether the model calls the tool at the right moment. If the agent stubbornly ignores a tool, rewrite the description first — more in the article on why an agent doesn't see files.

Note the protection inside the functions. read_file won't let the agent outside the data folder, even if the model asks for ../.env. calculate parses the expression with ast and doesn't use eval, so it can't be used to run arbitrary code. The rule is simple: anything coming from the model is untrusted input.

The loop

On every step the agent sends the whole history: the system prompt, the task, its past tool calls, and their results. If the response contains tool_calls, an assistant message with those calls is added to the history first, then one role: "tool" message per call with the matching tool_call_id. If there are no calls, that's the final answer.

Tool errors don't crash the program; they go back to the model as text. That way it can correct itself, for example by picking the right file name after "file not found".

Two safeguards

  • MAX_STEPS caps the number of model requests. Without it, an agent going in circles keeps running until the money runs out.
  • MAX_COST tracks the actual cost from the usage field of every response. For GPT models the gateway accounts for cache reads (prompt_tokens_details.cached_tokens), so the cached part of the input is billed at its own price. For Claude in the OpenAI format there's no cache discount and all input is billed at the input price — if you switch the agent to Claude, set PRICE_CACHED equal to PRICE_IN.

The third safeguard lives outside the code: a separate API key for the agent with a daily and monthly dollar limit and a list of allowed models. If the code accidentally goes into an endless loop, the gateway responds with a 429 and code key_daily_limit_reached and stops it. How to set it up is in the key limits docs.

What one agent task costs

Take the worst case for our example: the agent used all 10 steps, each with up to 4,000 input and 300 output tokens (input grows as the history grows, so we take an upper estimate). Prices as of October 7, 2026; formula steps × (input × input price + output × output price) / 1,000,000, ignoring cache:

ModelInput / output, $ per 1M10 steps
deepseek-v4.1-flash0.013 / 0.03810 × (4,000 × 0.013 + 300 × 0.038) / 1M = $0.000634
gpt-6.1-sol0.27 / 1.3210 × (4,000 × 0.27 + 300 × 1.32) / 1M = $0.01476
claude-sonnet-50.4 / 210 × (4,000 × 0.4 + 300 × 2) / 1M = $0.022

A MAX_COST = 0.05 budget covers such a task with room to spare, but cuts the agent off if it starts reading large files or going in circles. Prices are an example calculation; for the current list and prices, call GET https://api.zukko.pro/v1/models or open the Models page in the dashboard.

Which model to put in MODEL depends on your tasks. As a reference, the catalog has an intelligence index (the Artificial Analysis Intelligence Index): Claude Opus 5.5 — 58, GPT-6.1 Sol — 52, DeepSeek V4.1 Flash — 39. Run the same task on two or three models and compare how many steps it took, whether the answer is correct, and how much was charged. Switching models is one line: the gateway accepts any model in the OpenAI format.

What to add next

  • Streaming, if answers are long: when looping over chunks, check if chunk.choices: before chunk.choices[0] — the last chunk arrives with usage and an empty choices.
  • Confirmation for dangerous actions. Tools that change something (writing files, calling external APIs) are better run after a human confirms.
  • History compaction. A long history gets more expensive with every step: old tool results can be replaced with a short summary.
  • Standard ready-made tools. Instead of hand-written functions you can plug in MCP servers — what they are is covered in the article what an MCP server is.

More on the client in the Python SDK reference and the chat/completions docs. To run the example, create a key for the agent in the dashboard with a small daily limit.

FAQ

Do I need LangChain or another framework for an agent?

Not to start: the loop from the example fits in a few dozen lines, and you see every request. Frameworks help when the number of tools and agents grows.

Which model should I use for an agent?

Start with gpt-6.1-sol as a mid-price option. If the tasks are simple, try deepseek-v4.1-flash; if they're hard, claude-opus-5-5. Decide based on results on your own tasks.

Can I let the agent run terminal commands?

Technically yes, but it's the most dangerous tool there is. If you need it, run the agent in an isolated environment, restrict the command list, and confirm every run.

Why does the agent answer in text instead of calling a tool?

Most often the tool description is unclear to the model, or the task is phrased so that the tool seems unnecessary. Sharpen the description, explicitly ask for tool use in the system prompt, and try another model.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

How to build an AI agent in Python: code and limits · Zukko