Function calling: how an agent works with external systems
The model calls nothing itself: it only asks your program to run a function. The whole loop step by step, with Python code and a price estimate.
5 min · · агенты · development
When people say an agent "went to the database" or "created a ticket in the tracker", it can sound as if the model connects to systems on its own. In reality it is simpler and safer: interaction between an agent and external systems through function calling rests on the model only phrasing a request while your code carries it out. Below is the full loop step by step, a working Python example and an estimate of what such a loop costs.
The idea in one paragraph
You describe a set of functions to the model: name, purpose, parameters. The model reads the user's question and either answers right away or returns a structured request such as "call get_order_status with order_id = A-1042". Your program runs the call, sends the result back, and the model writes the final answer. The model starts nothing and gets no access to your systems, only to the text you return to it.
The loop step by step
- Describing tools. The request gets a
toolslist with a JSON schema of parameters for each function. - The model's decision. If a function is needed, the response contains a call, not text: a name and JSON arguments. The marker is the
tool_callsfield in the OpenAI format, or atool_useblock andstop_reason: "tool_use"in the Anthropic format. - Execution. Your code parses the arguments, validates them and calls the real function: a database query, an HTTP call, a file read.
- Returning the result. You append a message with the result, tied to the call's identifier.
- Repeat. You send the whole history again. The model either makes another call or writes an answer. The loop ends when the response contains no calls.
This loop is the agent: everything else (memory, planning, sub-agents) is built on top.
Python example (OpenAI format)
The code below works through /v1/chat/completions. The address ends in /v1, as for any OpenAI SDK client.
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.zukko.pro/v1", api_key="fetch_your_key")
def get_order_status(order_id: str) -> dict:
# a request to your system would go here
return {"order_id": order_id, "status": "shipped", "eta_days": 2}
REGISTRY = {"get_order_status": get_order_status}
tools = [{
"type": "function",
"function": {What matters here:
for _ in range(5)is a safety fuse. An agent that mistakenly calls a function again and again burns your balance. On protection, see an agent looped and spent the budget.- Arguments arrive as a JSON string and may be invalid: wrap
json.loadsin atry, and return the error to the model as data so it can correct itself. - Functions are taken from an allow-list,
REGISTRY, not througheval: a name from the model must never be executed directly. - The result is always a string. Return compact JSON, not an HTML page.
Parameters and response fields are in the chat completions reference.
The same in the Anthropic format
For Claude you can use /v1/messages. The structure differs in names, the idea of the loop is the same:
import anthropic
client = anthropic.Anthropic(base_url="https://api.zukko.pro", api_key="fetch_your_key")
tools = [{
"name": "get_order_status",
"description": "Returns the status of an order by its number.",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
}]
Here base_url has no /v1: the official Anthropic SDK adds the path itself. A detailed look at the format: tool use in Claude with Python.
Ollama and other local runtimes
The query "ollama tool calling" reflects the same principle: a local model also receives a description of functions and returns a request to call one. But support depends on the specific model and Ollama version, and the request format is defined by the program itself. Check the Ollama documentation for which models can call tools and how tools is passed. The loop on the side of your code stays the same.
What drives the cost
Each turn of the loop is a separate request that again includes the tool descriptions, the whole history and the function results. So the price grows not with the number of calls as such but with the length of the history.
Example calculation (prices as of 18 October 2026, gpt-6.1-sol: input $0.27, output $1.32 per 1M). Assumptions: tool description plus question is 500 tokens, the model's decision is 80 tokens, the function result is 150 tokens, the final answer is 200 tokens.
- Turn 1: input 500, output 80. 500 × 0.27 / 1,000,000 + 80 × 1.32 / 1,000,000 = $0.000135 + $0.0001056 = $0.0002406.
- Turn 2: input 500 + 80 + 150 = 730, output 200. 730 × 0.27 / 1,000,000 + 200 × 1.32 / 1,000,000 = $0.0001971 + $0.000264 = $0.0004611.
- Total $0.0007017, about $0.0007 per conversation; 1,000 conversations come to about $0.70.
On deepseek-v4.1-flash ($0.013 / $0.038) the same numbers give about $0.027 per 1,000 conversations, on claude-sonnet-5-5 ($0.4 / $2) about $1.05. Real amounts depend on the size of schemas and results.
How to reduce it:
- Shorten descriptions and do not attach tools that this conversation does not need.
- Return only the necessary fields from functions.
- For long agent sessions use the cache. For Claude the gateway sees the cache-read discount in the
/v1/messagesformat; in the OpenAI format all input for Claude is billed at the input price. For GPT cache reads are visible in both formats. More on the cache is on the limits and cache page. - Log the
usagefield of every response: it shows how the input grows from round to round and which tool bloats the history.
Which model handles your particular set of functions more reliably is impossible to say in advance: run the same scenarios on two or three models and compare the results and the cost.
FAQ
Does the model connect to the database or API itself?
No. It returns a function name and arguments, and your code makes the call. So you set the permissions and constraints yourself.
How is function calling different from MCP?
Function calling is a mechanism at the level of a request to the model. MCP is a standard by which a client gets tool descriptions from an external server and forwards calls to it, while the server runs the functions; the client still passes those descriptions to the model as tools.
Can several functions be called at once?
The model may return several calls in one response. Handle all of them and add one result message per tool_call_id.
What if the model invents arguments?
Validate against the schema on your side, make parameters strict (enums, required fields) and return a clear error so the model can correct itself. You can create a key for experiments in the Zukko dashboard.
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.
Related articles
- Prompt Injection: What It Is and How to Protect a Bot and an AgentHow prompt injection works through user input and third-party documents, and which measures limit the damage: data isolation, tool permissions, key limits.
- Claude Code YOLO: Permission Modes and Working Without PromptsClaude Code has no mode literally called YOLO, but it has several permission modes and flexible rules. How to stop clicking Yes without losing files.
- Claude Code: a checklist of best practices for daily workHabits that make working with Claude Code predictable: a short CLAUDE.md, a plan before edits, small tasks, checking results and keeping spend under control.
- An MCP server in Java: a minimal example with one toolWe write a minimal MCP server in Java with one tool, register it in Claude Code, and learn to find the cause when the agent doesn't see it.