WiozenAPI

How do you do function calling (tool use) with an AI API?

In short: You put your functions' names, descriptions and parameters (as JSON Schema) in the request's tools field. When the model decides it needs one, it returns tool_calls instead of text, saying which function it wants to call and with which arguments. You run the function, send the result back in a role: "tool" message, and the model writes the final answer. On the Wiozen API this works in both the OpenAI and the Anthropic format with the same key.

How does function calling work?

The model doesn't run your function, and neither does the API. The model only writes a request saying "call this function with these arguments". Your code makes the call, gets the result and hands it back to the model. That means you stay in full control of access to your database, internal services or files.

One round goes like this:

  1. You send the user's message and the tools list.
  2. The model either answers directly or returns tool_calls. In the second case finish_reason is "tool_calls".
  3. You run the function for each call.
  4. You add the model's message and each result, as a role: "tool" message matching the call's id, to the history and send the request again.
  5. The model reads the results and writes the final answer, or asks for another tool call.

The model can return several calls in one turn, for example asking for the weather in two cities at once. tool_calls is an array; run each element separately and answer each with its own tool_call_id.

A Python example

OpenAI's official Python library is enough; only base_url changes. The code below tells the model about a weather function, runs the function if the model asks for it, and sends the result back:

tools.py
import json
from openai import OpenAI

client = OpenAI(base_url="https://api.wiozen.com/v1", api_key="sk-gw-...")

def get_weather(city: str) -> dict:
    # In a real app you'd call a weather service here.
    return {"city": city, "temp_c": 14, "sky": "cloudy"}

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Returns the current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

messages = [{"role": "user", "content": "What's the weather like in Bursa?"}]
first = client.chat.completions.create(model="deepseek-v4.1-flash", messages=messages, tools=tools)
reply = first.choices[0].message

if reply.tool_calls:
    messages.append(reply)
    for call in reply.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_weather(**args)
        messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
    final = client.chat.completions.create(model="deepseek-v4.1-flash", messages=messages, tools=tools)
    print(final.choices[0].message.content)
else:
    print(reply.content)

arguments is a JSON string, not an object, so you have to decode it with json.loads. A model can sometimes write incomplete or malformed arguments. Wrap the decoding in try and, if it fails, report the error to the model as the tool result; most models fix it on the next attempt.

Parameter descriptions make a big difference to whether the model picks the right arguments. Use the description field to say in a plain sentence when the function should be used and which units it expects ("Celsius", "ISO date").

In the Anthropic format and the Responses API

If you use the Anthropic format (/v1/messages), tools still go in the tools field, but the schema is called input_schema. When the model wants a call, the response has a type: "tool_use" block and stop_reason: "tool_use"; the arguments are already decoded as an object in the input field. You send the result back in the next user message as a tool_result block with the same tool_use_id.

Wiozen guarantees two things in this format. If the response contains a tool_use block, stop_reason is always "tool_use", so you can build your loop on that field. If the arguments the model wrote aren't valid JSON, input comes back as an empty object ({}); treat an empty input as missing arguments.

In OpenAI's Responses format (/v1/responses), calls come back as type: "function_call" items in the output array. We cover the general differences between the formats in Anthropic Messages or Chat Completions.

What changes between streaming and non-streaming?

With stream: true a tool call arrives in pieces: the function name in the first chunk, the arguments in later chunks inside delta.tool_calls, each with an index field. Joining the pieces by index is up to you; the SDKs' streaming helpers do it for you.

In a non-streaming request Wiozen joins the pieces for you and returns a single tool_calls array. In that case message.content can be null rather than an empty string. Make sure your code handles null when it reads the content; that's why the example above checks tool_calls first.

How are tool calls billed?

There's no separate tool charge; everything is counted as tokens. The tools list goes to the model with every request and is billed as input tokens. The call the model writes (function name and arguments) is output. The function's result goes back as input in the next request.

So one tool round is at least two requests, and the second carries the whole history of the first. A long tool result (say, an entire web page or hundreds of rows of query output) is charged again on every turn. Trim the result down to the fields the model needs before you pass it on. For the full calculation see How do you calculate the cost of an AI API.

A response made only of a tool call, with no text at all, is a real answer and is billed normally. The "empty answer" case that Wiozen closes free of charge covers responses where the model produced neither text nor a tool call; it's described in the reasoning models guide.

Frequently asked questions

Does the model run my function itself?

No. The model only writes which function it wants to call and with which arguments. Running it and sending the result back is your code's job.

Which endpoints support function calling?

/v1/chat/completions, /v1/messages and /v1/responses. All three use the same key and the same model names.

Is the tools list charged?

Yes, it counts as input tokens on every request. Removing functions you don't use makes each request a little cheaper.

Why is message.content null in a non-streaming response?

When the model returns a tool call instead of text, the content is empty and comes back as null. Check the tool_calls field first.

What if the model writes the arguments wrong?

In the OpenAI format the arguments string comes through as it is, and decoding it may fail. In the Anthropic format invalid arguments arrive as an empty object. Either way, reporting the error to the model as the tool result is usually enough.

Get your key from the Keys page and try the example above with a single function. Every parameter is in the docs.

Create your account

More guides: How can you use an AI API from Turkey without a credit card? · How do you connect an OpenAI-compatible API to a tool you already use? · How do you calculate the cost of an AI API? · What should I do if my AI API key has leaked? · How do you generate images with an AI API, and what does one image cost? · How much does a speech-to-text API cost, and how is it billed per minute? · Why does my AI API return a 429 error, and how do I fix it? · Why do long AI API requests time out: streaming or non-streaming? · Anthropic Messages API or OpenAI Chat Completions: which one should I use? · What is a context window, and what is a 1 million token context good for? · Which AI model should I choose: deciding by price, context and quality · Why does a reasoning model return an empty answer, and why are thinking tokens charged? · Why is my API balance running out faster than expected? Tracking spend request by request · How do you convert a web page to Markdown to feed it to an AI model? · How do you get AI to write and publish articles in WordPress automatically? · How do you find blog topics from RSS feeds and Google Trends? · Does AI-written content show up on Google and in AI answers? · How do you get internal links into AI-written WordPress articles? · How do you get AI to generate a different featured image for every WordPress post? · How do you write a content brief when an AI writes your blog posts? · What should you watch for when an AI writes recipes for your food blog? · How do you fix errors when connecting a WordPress plugin to your AI API account? · Why does my AI article bot stop halfway in WordPress? "Run is stuck" and LiteSpeed · Why are my scheduled WordPress posts late? WP-Cron and a real server cron · How do I get an AI article bot to write blog posts in English or another language? · What does "Content Bot cannot generate articles" mean? Reading the bot's log · If I delete the WordPress AI article plugin, are the articles it wrote deleted? · How do I check my AI API balance from code and get a low-balance alert?