Skip to content
trending

Guides

Function Calling in AI: How Models Call Your Functions

Function calling lets AI models request your functions instead of executing code themselves. Working Python example with weather, calculator, and Wikipedia.

By 11 min read
Function Calling in AI: How Models Call Your Functions

👁 115 reads

Function calling (called tool use in Claude) is a mechanism where a language model doesn’t execute code itself - it responds with “call function X with these arguments.” Your program executes the function, sends back the result, and the model composes an answer from it. Below we show the complete flow with a working Python example (weather, calculator, Wikipedia), with real test runs from October 4, 2026. This is the second step in the AI Agents from Scratch learning path.

Status as of October 4, 2026

  • Function calling is supported by all major APIs: OpenAI, Anthropic (Claude), Google (Gemini), Mistral, DeepSeek, Qwen. The format is nearly identical: function descriptions in JSON Schema.
  • We ran the example on Windows 11, Python 3.13, openai library 3.24.0, model openai/gpt-5.6-luna through OpenRouter.
  • Cost of three test queries: 0.001 USD total.
  • Code is in the ai-agents-starter package (file 01_function_calling.py).

What is function calling in simple terms

A language model can only do one thing: accept text and return text. It has no internet, clock, or calculator. Function calling is an agreement: in your API request you attach a list of functions you have on your side, with descriptions and parameter lists. The model, instead of guessing, can respond with a structured request: “I want weather(city="Gdansk")“.

The most important thing to understand from the start: the model doesn’t run anything. Your code reads this request, calls the real function, and appends the result to the conversation. This way you decide what the model can do. If you don’t give it a “delete file” function, there’s no way to delete anything.

If you don’t yet know what an API request and token are, start with the guide to AI model APIs and the article about tokens.

The complete flow in 5 steps

  1. You describe functions. Name, human-readable description, and parameters in JSON Schema. The description is an instruction to the model about when to use the function.
  2. You send a query along with the function list (tools parameter).
  3. The model responds with a call request (tool_calls) or plain text if functions aren’t needed.
  4. Your code executes the function and appends the result to the conversation as a message with role tool, with the same tool_call_id.
  5. You send the conversation again. The model either requests more functions or gives a final answer.

Steps 3-5 repeat in a loop. If you hand this loop to the model and let it decide how many times and why to reach for tools, you have an agent. That’s covered in the article How to Build an AI Agent in Python.

What a model’s function call request looks like

We asked “What is 15% of 2340 zł?” (we tested in Polish), giving the model a calculator. Here’s the raw API response, without any processing:

# raw model message on the question "What is 15% of 2340 zł?"
{
  "role": "assistant",
  "tool_calls": [
    {
      "id": "call_m25uxE3pxeXOqgLd33CWNInn",
      "function": {
        "arguments": "{"wyrazenie":"2340*0.15"}",
        "name": "kalkulator"
      },
      "type": "function",
      "index": 0
    }
  ]
}
finish_reason: tool_calls cost 6.58e-05

Note three things:

  • the content field is empty: the model wrote nothing, only requested a function;
  • arguments is text with JSON, not a ready object. You need to parse it (json.loads), and in case of error send the model a message;
  • finish_reason has value tool_calls. This tells you the conversation isn’t finished.

Working example: weather, calculator and Wikipedia

We wrote three ordinary Python functions. None require an API key: weather comes from free Open-Meteo, search uses the Wikipedia API (we tested in Polish), and the calculator computes locally. The calculator deliberately doesn’t use eval(), but parses expressions through the ast module. If the model (or someone feeding it malicious text) entered a system command there, eval would execute it.

"""01 - Function calling (tool use): model requests your function call, you execute it.
Run: python 01_function_calling.py "What's the weather in Gdansk and what is 15% of 2340 zł?"
"""
import ast, json, operator, os, sys
import requests
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
MODEL = os.getenv("MODEL", "openai/gpt-5.6-luna")
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"])
UA = {"User-Agent": "ai-agents-starter/1.0"}

# --- 1. Regular Python functions ---------------------------------------------
def pogoda(miasto: str) -> dict:
    geo = requests.get("https://geocoding-api.open-meteo.com/v1/search",
                       params={"name": miasto, "count": 1, "language": "pl"}, headers=UA, timeout=15).json()
    if not geo.get("results"):
        return {"blad": f"don't know city {miasto}"}
    m = geo["results"][0]
    w = requests.get("https://api.open-meteo.com/v1/forecast", params={
        "latitude": m["latitude"], "longitude": m["longitude"],
        "current": "temperature_2m,wind_speed_10m,precipitation", "timezone": "Europe/Warsaw"},
        headers=UA, timeout=15).json()
    return {"miasto": m["name"], **w["current"]}

OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
       ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg}
def kalkulator(wyrazenie: str) -> dict:
    def licz(n):  # safe calculation: only numbers and + - * / **
        if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
        if isinstance(n, ast.BinOp): return OPS[type(n.op)](licz(n.left), licz(n.right))
        if isinstance(n, ast.UnaryOp): return OPS[type(n.op)](licz(n.operand))
        raise ValueError("forbidden expression")
    return {"wyrazenie": wyrazenie, "wynik": licz(ast.parse(wyrazenie, mode="eval").body)}

def szukaj_wikipedia(fraza: str) -> dict:
    r = requests.get("https://pl.wikipedia.org/w/api.php", params={
        "action": "query", "list": "search", "srsearch": fraza, "srlimit": 3, "format": "json"},
        headers=UA, timeout=15).json()
    return {"wyniki": [{"tytul": h["title"], "fragment": h["snippet"].replace('<span class="searchmatch">', "").replace("</span>", "")}
                       for h in r["query"]["search"]]}

FUNKCJE = {"pogoda": pogoda, "kalkulator": kalkulator, "szukaj_wikipedia": szukaj_wikipedia}

# --- 2. Function descriptions for the model (JSON Schema) ----------------------------------
def narzedzie(nazwa, opis, param, opis_param):
    return {"type": "function", "function": {"name": nazwa, "description": opis, "parameters": {
        "type": "object", "properties": {param: {"type": "string", "description": opis_param}}, "required": [param]}}}

TOOLS = [
    narzedzie("pogoda", "Current weather in given city (temperature, wind, precipitation).", "miasto", "city name, e.g. Krakow"),
    narzedzie("kalkulator", "Calculates arithmetic expression. Use instead of mental math.", "wyrazenie", "e.g. 2340*0.15"),
    narzedzie("szukaj_wikipedia", "Searches Polish Wikipedia, returns 3 titles with snippets.", "fraza", "what to search for"),
]

# --- 3. Conversation: model -> function calls -> results -> answer --------------
pytanie = sys.argv[1] if len(sys.argv) > 1 else "What's the weather now in Gdansk and what is 15% of 2340 zł?"
messages = [{"role": "system", "content": "Answer in Polish, briefly. Use tools for facts and calculations."},
            {"role": "user", "content": pytanie}]
koszt = 0.0
for runda in range(1, 6):
    r = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS)
    koszt += getattr(r.usage, "cost", 0) or 0
    msg = r.choices[0].message
    if not msg.tool_calls:  # model doesn't want more tools = ready answer
        print(f"n[round {runda}] ANSWER:n{msg.content}")
        break
    messages.append(msg.model_dump(exclude_none=True))
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        print(f"[round {runda}] model requests: {call.function.name}({json.dumps(args, ensure_ascii=False)})")
        try:
            wynik = FUNKCJE[call.function.name](**args)
        except Exception as e:  # return error to model too, let it decide
            wynik = {"blad": str(e)}
        print(f"           result: {json.dumps(wynik, ensure_ascii=False)[:300]}")
        messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(wynik, ensure_ascii=False)})
print(f"nmodel: {MODEL}, rounds: {runda}, cost: {koszt:.6f} USD")

Installation is pip install openai requests python-dotenv and a .env file with key OPENROUTER_API_KEY. Through OpenRouter the same code works with OpenAI, Google, Anthropic or DeepSeek models: you only change the model name in the MODEL variable.

Test run 1: two functions at once

> python 01_function_calling.py "What's the weather now in Gdansk and what is 15% of 2340 zł?"
[round 1] model requests: pogoda({"miasto": "Gdańsk"})
           result: {"miasto": "Gdańsk", "time": "2026-10-04T13:45", "interval": 900, "temperature_2m": 19.0, "wind_speed_10m": 14.0, "precipitation": 0.0}
[round 1] model requests: kalkulator({"wyrazenie": "2340*0.15"})
           result: {"wyrazenie": "2340*0.15", "wynik": 351.0}

[round 2] ANSWER:
In Gdansk it's now **19°C**, wind **14 km/h**, no precipitation.  
**15% of 2340 zł = 351 zł.**

model: openai/gpt-5.6-luna, rounds: 2, cost: 0.000260 USD

The model in the first round requested two functions simultaneously (this is called parallel tool calls). Our code executed both, sent back results, and in the second round we got a ready answer. The temperature of 19 degrees is an actual reading from Open-Meteo at 13:45, not the model guessing.

Test run 2: tool returns garbage

This run is more interesting because it shows a typical problem:

> python 01_function_calling.py "Who built the Palace of Culture and Science and how old is this building in 2026?"
[round 1] model requests: szukaj_wikipedia({"fraza": "Pałac Kultury i Nauki kto zbudował ukończony 1955"})
           result: {"wyniki": [{"tytul": "Siedem Sióstr (Moskwa)", "fragment": "2019-09-05]  (ros.). Jarosław Zieliński: Pałac Kultury i Nauki, Księży Młyn 2012, s.16. Pałac Kultury i Nauki w Warszawie [online], www.pkin.pl [dostęp"}, {"tytul": "Drezno", "fragment": " kościół Krzyża (do 1955), Zwinger (do 1963), kated
[round 2] model requests: kalkulator({"wyrazenie": "2026-1955"})
           result: {"wyrazenie": "2026-1955", "wynik": 71}

[round 3] ANSWER:
The Palace of Culture and Science was built by the **Soviet Union** as a gift to Poland. The design was created by **Lev Rudnev**, and construction was carried out by Soviet workers.

It was opened **July 22, 1955**, so in 2026 it will be **71 years old** - exactly from July 22; earlier in that year it will still be 70 years old.

model: openai/gpt-5.6-luna, rounds: 3, cost: 0.000662 USD

The model entered a too-long query into the search (“Palace of Culture and Science who built completed 1955” in Polish) and Wikipedia returned articles about Moscow skyscrapers and Dresden. The model didn’t notice this aloud, just answered from its own knowledge. This time the facts are correct (opening July 22, 1955, designed by Lev Rudnev), but if it were about fresh data, we’d have a hallucination with the appearance of a source. Conclusions we drew from this:

  • in the search function description add: “use short queries, 2-4 words”;
  • in the system prompt tell the model to say when a tool didn’t provide an answer;
  • log all calls and results. Without the log we wouldn’t have noticed the answer didn’t come from Wikipedia.

Test run 3: model knows when functions are unnecessary

> python 01_function_calling.py "Write one sentence encouraging learning to code."

[round 1] ANSWER:
Start learning to code today and create your own ideas, applications and new career opportunities!

model: openai/gpt-5.6-luna, rounds: 1, cost: 0.000080 USD

A request for an encouraging sentence didn’t require any function, so the model answered immediately, in one round, for 0.00008 USD. You don’t have to decide yourself when to give the model tools and when not. You always give them, and the model chooses.

How to write function descriptions so the model uses them well

  • Description is an instruction. “Calculates arithmetic expression. Use instead of mental math” works better than “calculator”.
  • Parameters with examples. "e.g. 2340*0.15" in the parameter description cuts out most format errors.
  • Few functions at once. 3-10 well-described functions is a safe range. With dozens the model starts confusing similar tools.
  • Result in JSON, short. Every character of the result comes back to the model as input tokens and costs money. Trim long responses.
  • Error is also a result. Instead of breaking the program, send back {"error": "don't know city"}. The model usually corrects the argument itself.

Function calling in OpenAI, Claude and Gemini: differences

API Name Where function description goes How request arrives How you send result back
OpenAI (Chat Completions, also OpenRouter) function calling / tools tools=[{"type":"function","function":{...}}] message.tool_calls message role: "tool" with tool_call_id
Anthropic (Claude) tool use tools=[{"name","description","input_schema"}] tool_use block in content, stop_reason: "tool_use" tool_result block in user message
Google (Gemini) function calling function_declarations functionCall in response functionResponse

Underneath it’s the same idea. That’s why libraries like LangGraph or OpenAI Agents SDK can switch between providers, and the MCP protocol lets you describe tools once for all applications.

Security: what to remember

  • The model can call a function with arguments you don’t expect. Validate them in code (ranges, paths, types).
  • Functions that change something (send email, pay, delete) should ask a human for confirmation.
  • Text from web pages and documents that reaches the model through a tool can contain hidden commands (prompt injection). Treat it as data, not as orders.

Most common questions

How does function calling differ from tool use?

Nothing substantial. OpenAI and Google say “function calling”, Anthropic says “tool use”. In both cases the model returns the tool name and arguments, and code executes them.

Does function calling work in other languages?

Yes. We wrote function descriptions and parameters in Polish, the model understood them without problems and entered Polish city names itself. Function names are better kept without special characters (szukaj_wikipedia), because some APIs only allow Latin letters, digits and underscores in names.

Does every model support function calling?

No. Large commercial models do. Local models in Ollama support tools if they have it in their template (e.g. Llama 3.1, Qwen, Mistral), but smaller ones more often confuse arguments. On OpenRouter the model list has a “tools” filter.

How much does function calling cost?

As much as regular tokens, but each round is a separate request, and function descriptions add to input on every request. For us a question with two functions cost 0.00026 USD, and with three rounds 0.00066 USD.

Can the model run code on my computer by itself?

No. The model only requests. Your program runs, and only those functions you connect yourself.

Function calling vs AI agent: how do they differ?

Function calling is a mechanism: one request, one function request. An agent is a loop where the model repeatedly chooses tools, looks at results and decides what’s next, until it finishes the task. Agent examples are in the article AI Agent: Examples.

Google · Your sourcesQuantslant higher in your Google - one clickAdd to preferred sources →

Sources and verification date

Kinetyra

// read next

More on this from QUANTSLANT

Piotr Olszewski

Editor and publisher · Quantslant

I edit and publish Quantslant. AI tools help with research, drafts and translation; choosing topics, running the tests, checking facts and the decision to publish are mine, and so is the responsibility for what appears here.

read nextBuild an AI Agent in Python Step by Step (76 Lines)guidesBuild an AI Agent in Python Step by Step (76 Lines)An AI agent in 76 lines of Python: loop, tools, step limit, decision log. Real runs and versions using OpenAI Agents SDK and Claude Agent SDK.

Most read

  1. 1Best AI Video Generator Apps for Your Phone (2026)
  2. 2Claude Opus 5.5 Motion Graphics: 60s Showreel from One Prompt
  3. 3Wan 2.2 on RTX 4090: timings, VRAM and four test clips
// site map

This page in other languages1

Quantslant in numbers 1,423posts in archive22news from the last 7 days18video models in the observatory
🎬 AI Video Benchmarks: Higgsfield DoP vs OpenAI Sora • Higgsfield vs Runway Gen-3 • AI Video Credit Calculator
× ‹ enlarge ›