Function calling & tool use
Declare tools on chat completions or the Responses API, read tool_calls back, and close the loop.
Which models can call tools
Tool use needs a tool-call parser in the serving recipe, and the catalog says which
models have one: tools, tool_choice and parallel_tool_calls are declared in a
model's supported_parameters only when its recipe parses tool calls. Check before
wiring a tool loop into production:
curl https://api.runaii.cloud/v1/models
At the time of writing the live catalog declares a tool-call parser on one model only:
| Model | tools |
tool_choice |
parallel_tool_calls |
|---|---|---|---|
glm-5.3 |
yes | auto | none | required |
yes |
deepseek-v4-flash |
not declared | — | — |
qwen3.8-flash |
not declared | — | — |
glm-5.3 reported is_ready: false when this page was written, and is_ready
is a live flag — re-check it (and Console → Models) before sending tool traffic.
On a model with no tool-call parser the engine ignores those parameters — it has
nothing to parse tool calls with. Only declare tools on a model whose catalog entry
declares them.
Declare a tool
Tools use the OpenAI shape on /v1/chat/completions — tools[], tool_choice and
parallel_tool_calls are forwarded to the engine as sent:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city.",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}
| Parameter | Accepted values |
|---|---|
tools |
array of { "type": "function", "function": { "name", "description"?, "parameters"? } } |
tool_choice |
"auto", "none", "required", or { "type": "function", "function": { "name": "get_weather" } } |
parallel_tool_calls |
boolean — allow more than one tool call per turn |
Runnable request
curl https://api.runaii.cloud/v1/chat/completions \
-H "Authorization: Bearer $RUNAII_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3",
"messages": [{"role": "user", "content": "What is the weather in Berlin?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}],
"tool_choice": "auto"
}'
What comes back
The model does not run your function — it returns the call it wants you to make.
finish_reason is "tool_calls" and message.content is null for a pure tool
turn:
{
"id": "rq-...",
"object": "chat.completion",
"model": "glm-5.3",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_...",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Berlin\"}" }
}
]
},
"finish_reason": "tool_calls"
}
]
}
The response also carries the standard usage object — the real token counts and
the exact cost you were charged, with the same fields as any other call (see
pricing). The tokens the model spends writing tool arguments are
completion tokens like any other output.
Close the loop
Execute the function, then send the assistant turn back verbatim followed by one
role: "tool" message per call, matched by tool_call_id:
{
"model": "glm-5.3",
"messages": [
{ "role": "user", "content": "What is the weather in Berlin?" },
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_...",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Berlin\"}" }
}]
},
{ "role": "tool", "tool_call_id": "call_...", "content": "{\"temp_c\": 14, \"sky\": \"overcast\"}" }
]
}
role: "tool" is accepted alongside the rest of the OpenAI chat role set
(system, user, assistant, developer, function). A message may carry
tool_calls instead of content — that is how you feed an assistant turn back in.
Streaming tool calls
With "stream": true the call arrives as tool_calls deltas: the first chunk
carries index, id, type and function.name, later chunks append
function.arguments fragments, and the final chunk reports
finish_reason: "tool_calls". Accumulate by index, then JSON.parse the joined
arguments. The terminal usage chunk behaves exactly as in
streaming — usage is only emitted when the client sets
stream_options: {"include_usage": true}.
Responses API (/v1/responses)
The Responses dialect flattens the tool — name, description and parameters
sit at the top level of the tool object, not nested under function:
curl https://api.runaii.cloud/v1/responses \
-H "Authorization: Bearer $RUNAII_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3",
"input": "What is the weather in Berlin?",
"tools": [{
"type": "function",
"name": "get_weather",
"description": "Current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}],
"tool_choice": {"type": "function", "name": "get_weather"}
}'
tool_choice accepts "auto", "none", "required" or the named form
{ "type": "function", "name": "get_weather" }. The model's answer comes back as
an output item, and you feed the result back as an input item — the endpoint is
stateless, so resend the whole conversation:
{
"id": "resp_...",
"object": "response",
"status": "completed",
"model": "glm-5.3",
"output": [
{
"id": "fc_...",
"type": "function_call",
"status": "completed",
"call_id": "call_...",
"name": "get_weather",
"arguments": "{\"city\":\"Berlin\"}"
}
]
}
Send the result back in the same input array — function_call echoes the call,
function_call_output carries what your code returned:
{
"model": "glm-5.3",
"input": [
{ "role": "user", "content": "What is the weather in Berlin?" },
{ "type": "function_call", "call_id": "call_...", "name": "get_weather", "arguments": "{\"city\":\"Berlin\"}" },
{ "type": "function_call_output", "call_id": "call_...", "output": "{\"temp_c\": 14, \"sky\": \"overcast\"}" }
]
}
The usage block reports the real token counts and the exact cost, exactly like
chat completions.
When the model emits only tool calls, output holds just the function_call
item; a message item appears when there is visible text. Streamed, the same call
arrives as response.output_item.added (with call_id and name),
response.function_call_arguments.delta, response.function_call_arguments.done
and response.output_item.done events.
What is not supported
- Hosted tools.
web_search,file_search,computer_use,code_interpreterandmcpare rejected with400 unsupported_tool— usetype: "function"tools, which are fully supported. - Tool loops are yours to run. The API never executes a tool; it returns the call and waits for you to send the result back.