Tool calling
Tool calling lets the AI ask your program for help. You describe a few things your program can do, like "look up the weather" or "search my notes". When the AI needs one, it replies with a request to run it. Your program runs it, sends back the result, and the AI writes its answer with that result.
The AI never runs anything itself. It only asks. Your code decides what actually happens, so you stay in control.
I just want to use it in an app or agent
You don't need to write any of the code below. Apps and agents that use tools handle all of it for you. Paste the same three things you'd paste anywhere else: the address https://api.aiprovider.org/v1, your key, and the model aiprovider-1. See Agents and coding tools for where those go.
How the round trip works
- You send the conversation plus a list of
tools: each one a name, a plain description, and the inputs it takes (as a JSON Schema, a standard way to describe data). - The AI replies either with a normal answer, or with
tool_callsandfinish_reason: "tool_calls". Each call has anid, a function name and itsargumentsas a JSON string. - You run each call and add two things to the conversation: the assistant message with its
tool_calls, then one{ "role": "tool" }message per call with the matchingtool_call_idand the result. - You send again. The AI reads the results and answers, or asks for another tool. Repeat until it answers.
A reply that asks for a tool looks like this. Any text the AI writes before the call stays in content.
{
"object": "chat.completion",
"model": "aiprovider-1",
"choices": [{
"index": 0,
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": null,
"reasoning_content": "The user wants Oslo's weather. I'll call get_weather.",
"tool_calls": [{
"id": "call_8f2a9c4e1b7d3a6c5e0f9b2d",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\": \"Oslo\"}" }
}]
}
}],
"usage": { "prompt_tokens": 182, "completion_tokens": 41, "total_tokens": 223 }
}Complete example: a weather lookup
This runs as it is. Swap get_weather for your own code.
# pip install openai
import json, os
from openai import OpenAI
client = OpenAI(base_url="https://api.aiprovider.org/v1", api_key=os.environ["AIPROVIDER_API_KEY"])
# 1. Describe what your program can do.
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city. Use it whenever the user asks about weather.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, like Oslo"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
},
}]
def get_weather(city, unit="celsius"):
# Your real code goes here: call a weather service, read a database...
return {"city": city, "temperature": 12, "unit": unit, "sky": "light rain"}
messages = [{"role": "user", "content": "Do I need an umbrella in Oslo today?"}]
# 2. Ask. The AI may answer, or ask you to run a tool.
reply = client.chat.completions.create(model="aiprovider-1", messages=messages, tools=tools)
msg = reply.choices[0].message
# 3. Run each tool it asked for and send the results back.
while msg.tool_calls:
messages.append(msg) # keep the assistant's request in the history
for call in msg.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)})
reply = client.chat.completions.create(model="aiprovider-1", messages=messages, tools=tools)
msg = reply.choices[0].message
# 4. The final answer, written with your tool's result.
print(msg.content)Streaming with tools
With "stream": true, the reasoning and any text before a tool call stream live, as usual. Tool calls arrive near the end as delta.tool_calls pieces, each with an index, the call id, the function name and its arguments. The final chunk has finish_reason: "tool_calls". Join the pieces by index:
stream = client.chat.completions.create(
model="aiprovider-1", messages=messages, tools=tools, stream=True,
)
calls = {} # index -> {"id", "name", "arguments"}
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="", flush=True) # any text before the tool call
for tc in delta.tool_calls or []:
c = calls.setdefault(tc.index, {"id": None, "name": "", "arguments": ""})
c["id"] = tc.id or c["id"]
if tc.function and tc.function.name:
c["name"] += tc.function.name
if tc.function and tc.function.arguments:
c["arguments"] += tc.function.arguments
for c in calls.values():
print("\nwants to call", c["name"], "with", c["arguments"])Choosing when tools run
| Setting | What happens |
|---|---|
"tool_choice": "auto" | The default when you send tools. The AI decides whether it needs one. |
"tool_choice": "none" | No tools this time. The AI answers in words. |
"tool_choice": "required" | We ask the AI firmly to call a tool. If it answers in words anyway, you get that answer. |
{"type": "function", "function": {"name": "get_weather"}} | We ask the AI to call that one tool. Same as above if it doesn't. |
"parallel_tool_calls": false | At most one call per reply. By default the AI can ask for several at once. |
functions, function_call | The older style still works. Replies then use function_call and finish_reason: "function_call". |
Agents and coding tools
Agents, coding assistants and workflow tools that accept an "OpenAI-compatible" provider work with AI Provider, tools included. Most ask for the same three things. Some read them from environment variables (settings your computer passes to programs); others use a settings screen or a config file:
# Many agents and coding tools read these two settings.
export OPENAI_BASE_URL="https://api.aiprovider.org/v1"
export OPENAI_API_KEY="ap_your_key_here"
# Then pick the model "aiprovider-1" in the tool's settings or with its --model option.- Set the model to
aiprovider-1. If the tool shows a model list, it finds it athttps://api.aiprovider.org/v1/models. - Agents send long conversations. Each request can be up to about 4.5 MB, and replies can run long, so leave
max_tokensgenerous. - Give each tool its own key from your Keys page, with a monthly cap, so a runaway agent can't spend more than you planned.
Tips for good results
- Keep the list short. Five well-chosen tools work better than fifty. Send only the ones that fit the conversation.
- Write descriptions for a new colleague. Say what the tool does and when to use it: "Current weather for a city. Use it whenever the user asks about weather."
- Keep inputs simple. Strings, numbers, true or false, and short lists work best. Use
enumfor fixed choices. - Check the arguments before you act on them, as you would with anything a user types.
- Return small, clear results. A short JSON object beats a whole web page.
- Plan for no call. Even with tools, the AI may just answer. That's normal.
Limits
- Up to 128 tools per request, each named with letters, digits,
_or-(up to 64 characters). - Only
"type": "function"tools. The schema guides the AI but isn't enforced, andstrictis ignored, so check arguments yourself. - Arguments are text the AI writes. We turn numbers, true or false, lists and objects into the right JSON types using your schema. If a call comes back garbled, you get it as plain text in
contentinstead of an error. - Tool definitions count as input tokens, so long descriptions cost a little more on every request.
- Tool results and arguments go through the same safety check as every message, and are never stored.