Back to blog
Tutorial·7 min read·1201 words

How to Use Function Calling With AI APIs: A Complete Python Tutorial

Learn how AI function calling (tool use) works and build a working agent in Python — schemas, the tool loop, streaming, common pitfalls, and model picks.

How to Use Function Calling With AI APIs: A Complete Python Tutorial — illustration

How to Use Function Calling With AI APIs: A Complete Python Tutorial

Function calling (also called tool use) is the feature that turns a chatbot into an agent. Instead of just answering in text, the model can ask your code to run a function — look up an order in a database, call a weather API, send an email — and then use the result to compose its answer.

In this tutorial you'll learn how function calling works under the hood, and build a working weather assistant in Python using an OpenAI-compatible API. Everything here works with GPT, Claude, Gemini, GLM, DeepSeek, and any other model exposed through the Qubax AI gateway.

How Function Calling Actually Works

A common misconception: the model does not execute your functions. The flow is a loop between the model and your code:

  1. You send the user's message plus a list of available tools (names, descriptions, JSON schemas).
  2. The model replies with a special tool_calls message instead of normal text — it picks a tool and generates JSON arguments.
  3. Your code executes the real function (query the DB, hit the API).
  4. You send the result back to the model as a tool role message.
  5. The model answers the user — or requests another tool call (that's the loop).

The model provides intelligence about what to call and with what arguments; your code provides the actual capabilities. Keeping that separation clear prevents most confusion.

Prerequisites

bash
pip install openai  # works with any OpenAI-compatible endpoint

You'll need an API key from a provider, or from Qubax AI if you want to test the same code against multiple model families by changing one string.

Step 1: Define Your Tools

A tool definition has three parts: a name, a natural-language description (this is what the model reads to decide when to use the tool — make it precise), and a JSON Schema for the arguments.

python
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "Get the current weather for a given city. Use this whenever the user asks about weather conditions, temperature, or forecasts.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name, e.g. 'San Francisco'",
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit. Defaults to celsius.",
                    },
                },
                "required": ["city"],
            },
        },
    }
]

Tips that dramatically improve reliability:

  • *Describe when to use the tool*, not just what it does. The description is the model's only guidance.
  • Use enums wherever possible to constrain outputs.
  • Keep required fields minimal — let the model infer optional parameters when reasonable.

Step 2: Implement the Actual Functions

python
import json

def get_current_weather(city: str, unit: str = "celsius") -> str:
    # Real implementation: call OpenWeather, WeatherAPI, etc.
    # Here we return mock data for the demo.
    return json.dumps({
        "city": city,
        "temperature": 22 if unit == "celsius" else 72,
        "unit": unit,
        "condition": "Partly cloudy",
    })

AVAILABLE_TOOLS = {
    "get_current_weather": get_current_weather,
}

Two production rules:

  1. Never trust model-generated arguments blindly. Validate them (Pydantic is great) before executing — especially for tools that write, pay, or delete.
  2. Return errors as strings, not exceptions. If the tool fails, telling the model why lets it apologize, retry, or try a different approach.

Step 3: Build the Agent Loop

This is the part everyone gets wrong. You must handle the possibility of multiple tool calls in one response, and loop until the model produces a final answer.

python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.qubax.ai/v1",
    api_key="YOUR_QUBAX_KEY",
)

def run_agent(user_message: str, model: str = "gpt-5.6-luna", max_turns: int = 5):
    messages = [{"role": "user", "content": user_message}]

    for _ in range(max_turns):
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
        )
        msg = response.choices[0].message

        # No tool calls? The model is done — return the final answer.
        if not msg.tool_calls:
            return msg.content

        messages.append(msg)  # include the assistant's tool_calls message

        # Execute every requested tool and feed results back
        for tc in msg.tool_calls:
            fn = AVAILABLE_TOOLS[tc.function.name]
            args = json.loads(tc.function.arguments)
            result = fn(**args)
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": result,
            })

    return "Agent stopped: too many tool turns."

Try it:

python
print(run_agent("What's the weather in Tokyo right now? Should I bring a jacket?"))
# -> "It's currently 22°C and partly cloudy in Tokyo. A light jacket is a good idea..."

Notice what happened: the model never "knew" the weather. It decided on its own that it needed the tool, extracted city="Tokyo" from the user's message, waited for your code to run, and folded the result into a natural answer.

Step 4: Add Streaming (Production-Grade UX)

For real apps you'll want tokens streaming to the UI. Streaming with tools works the same way — you just accumulate tool-call deltas until the call is complete. We covered the full pattern in our streaming tutorial — the key addition here is buffering delta.tool_calls chunks and stitching their argument fragments together before executing.

Common Pitfalls (and Fixes)

  • Infinite tool loops. The model keeps calling the same tool. Fix: cap turns (max_turns above) and include turn count in the system prompt.
  • Hallucinated arguments. The model invents values for missing info. Fix: tighten schemas, mark required fields, and validate with Pydantic before executing.
  • Wrong tool chosen. Usually a description problem. Rewrite descriptions as "Use this when X, not when Y."
  • Parallel calls mishandled. Modern models often request several tools at once — always loop over msg.tool_calls (as above), never assume exactly one.
  • Leaking secrets into tool results. Tool outputs go into the model's context. Sanitize API keys and PII before appending results.

Choosing Models for Function Calling

Tool-calling quality varies more between models than raw chat quality does. As a rule of thumb for 2026:

  • Frontier models (GPT-5.6 Sol, Claude Opus 5, Gemini 3.7 Flash) handle complex multi-tool chains reliably.
  • Flash-tier and distilled models (GPT-5.6 Luna, GLM 5.3 Flash, DeepSeek V4 Flash) are excellent for single-tool routing and high-volume agents at 5–20× lower cost.
  • Budget tip: the same agent code runs unchanged on all of them through Qubax AI — benchmark your real workload across several models and pick the cheapest one that passes.

FAQ

What is function calling in AI APIs?

It's a feature where the model outputs structured JSON indicating which of your functions to run and with what arguments, then incorporates your results into its answer.

Does the model execute my code?

No. The model only requests a function call. Your application executes it and returns the result to the model.

Is function calling the same across providers?

The concept is universal, and OpenAI-compatible gateways like Qubax AI normalize the request/response format so one codebase works with GPT, Claude, Gemini, GLM, DeepSeek, and more.

How do I stop the model from calling tools when it shouldn't?

Sharpen tool descriptions ("Use this only when..."), and state in the system prompt: "If the question is answerable from context, answer directly without tools."


Next steps: grab an API key at Qubax AI, swap the model string, and test your agent against a dozen models in minutes. Full API reference lives in the Qubax docs.

🤖

Try Claude on Qubax

Anthropic models on Qubax. Up to 74% off.

View pricing

Article tags

#function calling#tutorial#Python#AI agents#API development
Share:Post on XTelegramLinkedInYHacker NewsReddit
Qubax AI

Qubax AI

AI Models at up to 99% off · Pay with crypto

Reading about Claude and Gemini? Access them — plus 340+ other models — through one API. Anthropic models on Qubax. Up to 74% off.

Related articles