Tool calling — also called function calling — is the feature that turns a chatbot into an agent. Instead of just generating text, the model can request that your code runs a function: look up an order, query a database, send an email, call another API. Then it reads the result and continues.
The good news: nearly every major model API now supports tool calling through an OpenAI-compatible schema. Write it once, and it works across GPT, Claude, Gemini, DeepSeek, GLM, and more. Here's how to build it properly.
How Tool Calling Actually Works
The key mental model: the model never executes anything. It only emits a structured "I want to call this function with these arguments" message. Your code executes it and sends the result back. The loop looks like:
1. Your app sends: messages + a list of available tools
2. Model replies with: tool_call(name="get_weather", arguments={"city":"Paris"})
3. Your app runs get_weather("Paris") in real code
4. Your app sends the result back as a tool message
5. Model writes the final natural-language answerStep 1: Define Your Tools
A tool definition has three parts: name, description, and JSON Schema parameters. The description is doing more work than you think — it's how the model decides when to use the tool.
tools = [
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up the shipping status of a customer order by order ID. Use when the user asks where their order is.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order ID, e.g. 'ORD-12345'"
}
},
"required": ["order_id"]
}
}
}
]Description tips that measurably improve reliability:
- Say when to use the tool, not just what it does
- Describe argument formats precisely ("ISO 8601 date", "order ID like ORD-12345")
- If a tool should NOT be used for some cases, say so explicitly
Step 2: Send the Request
from openai import OpenAI
client = OpenAI(
base_url="https://api.qubax.ai/v1", # OpenAI-compatible endpoint
api_key="YOUR_QUBAX_API_KEY",
)
messages = [
{"role": "user", "content": "Where's my order ORD-48291?"}
]
response = client.chat.completions.create(
model="gpt-5.4", # works with claude, gemini, deepseek, glm too
messages=messages,
tools=tools,
)Step 3: Handle the Tool Call
import json
msg = response.choices[0].message
if msg.tool_calls:
messages.append(msg) # keep the assistant's tool_call in history
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
# dispatch to YOUR real function
result = get_order_status(args["order_id"])
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result),
})
# Second call: model now has the tool result
final = client.chat.completions.create(
model="gpt-5.4",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
# "Good news — order ORD-48291 shipped yesterday and arrives Thursday."Note the two-round-trip structure: the first response contains the tool call, the second produces the user-facing answer. Forgetting to append both the assistant's tool-call message and the tool result is the #1 bug in first implementations.
Step 4: Put It in a Loop
Real agents call tools repeatedly — search, then read, then calculate. Wrap the logic in a loop:
def run_agent(user_input, tools, max_turns=8):
messages = [{"role": "user", "content": user_input}]
for _ in range(max_turns):
resp = client.chat.completions.create(
model="gpt-5.4", messages=messages, tools=tools
)
msg = resp.choices[0].message
messages.append(msg)
if not msg.tool_calls:
return msg.content
for tc in msg.tool_calls:
result = execute_tool(tc.function.name, json.loads(tc.function.arguments))
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result),
})
return "Max turns reached."Always include max_turns — it prevents runaway loops (and runaway bills).
Production Checklist
- Validate arguments. The model generates JSON; it can be malformed or hallucinate values. Validate with
pydanticor JSON Schema before executing. - Enforce authorization server-side. Never let the model decide what a user may do. Check permissions in your
execute_tooldispatcher, based on the authenticated user — not the conversation. - Return errors as tool results. If the function fails, send
{"error": "order not found"}back to the model. It will usually recover and explain to the user instead of crashing your app. - Keep tool results compact. Don't dump a 50KB database row into context. Select the fields the model needs.
- Log every call. Tool name, arguments, latency, result size — you'll want this for debugging and cost tracking.
- Parallel tool calls. Most modern models can emit multiple tool calls at once. Execute independent ones concurrently.
Which Models Are Best at Tool Calling?
Tool-calling reliability varies by model and task complexity. Frontier models handle 10+ tools and multi-step chains well; small/cheap models are fine for 1–3 simple tools. Because Qubax exposes everything through one OpenAI-compatible interface, you can A/B models by changing one string:
client.chat.completions.create(model="gpt-5.4", ...) # try GPT
client.chat.completions.create(model="claude-sonnet-5", ...) # try Claude
client.chat.completions.create(model="glm-5.3", ...) # try a budget modelBenchmark tool-call accuracy with your real tools before committing — generic leaderboards don't test your schema descriptions.
Wrapping Up
Tool calling is the difference between a text generator and a system that acts. The pattern is stable across providers: define tools with clear JSON Schemas, execute calls in your own code, feed results back, and loop — with validation and authorization at every step.
Ready to build? Grab an API key and test tool calling across 100+ models from one endpoint — see Qubax AI Models and the full API reference in the Qubax docs.
FAQ
What's the difference between tool calling and function calling?
Nothing — the terms are used interchangeably. OpenAI originally called it "function calling," then generalized it to "tools." Other providers use either name for the same mechanism.
Can the model execute code itself?
No. The model only outputs a structured request to call a function. Your application code parses the request, executes the real function, and returns the output. You stay in control of security.
Which models support tool calling?
Virtually all current frontier and mid-tier models from major labs support it, and most expose an OpenAI-compatible schema. One integration on Qubax gives you tool calling across GPT, Claude, Gemini, DeepSeek, GLM, and more.
How do I stop the model calling tools too often?
Write negative guidance into the description ("Do not use for general questions"), allow the model to answer without tools, and consider a cheaper router model that decides whether tools are needed at all.
Where can I test tool calling cheaply?
Sign up at Qubax, get one API key, and test the same tool-calling code against multiple models — compare pricing at qubax.ai/models and docs at qubax.ai/docs.