Function calling
Let Talos call your tools and return structured tool arguments
Both API surfaces support tool calling, where you describe functions and the model decides when to call them.
from openai import OpenAI
import json
client = OpenAI(
base_url="https://api.ablatic.ai/v1",
api_key="sk-ablatic-...",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
},
"required": ["city"],
},
},
}
]
messages = [{"role": "user", "content": "What is the weather in Linz?"}]
resp = client.chat.completions.create(
model="talos",
messages=messages,
tools=tools,
)
call = resp.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments) # arguments is a JSON-encoded STRING
weather = {"city": args["city"], "temp_c": 21}
messages.append(resp.choices[0].message)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(weather),
})
final = client.chat.completions.create(model="talos", messages=messages, tools=tools)
print(final.choices[0].message.content)OpenAI surface
On /v1/chat/completions you define each tool under function with a parameters JSON Schema. When the model calls a tool, the reply carries tool_calls, and each function.arguments value is a JSON-encoded STRING. Parse it with json.loads before use. You return the result as a message with role: "tool" and the matching tool_call_id.
Anthropic surface
On /v1/messages you define each tool with an input_schema. The model returns a tool_use content block whose input is already a parsed OBJECT, so no JSON parsing is needed. You return the result as a tool_result content block.
The divergence
The shapes diverge in two places. OpenAI tools use function.parameters and return function.arguments as a JSON STRING. Anthropic tools use input_schema and return tool_use.input as a parsed OBJECT. The tool_choice enums also differ: OpenAI uses required, Anthropic uses any.
On the OpenAI surface, function.arguments is a string. Parse it before use. On the Anthropic surface, tool_use.input is already an object.
Choosing which tool runs
tool_choice decides whether the model may call a tool at all.
| Value | Effect |
|---|---|
omitted or "auto" | The model decides whether to call a tool |
"none" | The model never calls a tool and answers in content |
"required" | The model must call one of the tools you passed |
{"type": "function", "function": {"name": "get_weather"}} | The model must call exactly that function |
Several tools in one turn
The model can return more than one entry in tool_calls. Run them all, then send one message with role: "tool" back per tool_call_id in the same next request. Iterate over the array instead of reading tool_calls[0], otherwise you drop calls and the model waits for a result that never arrives.
Token accounting across the loop
usage describes the one request it came with. Nothing accumulates on our side, because we keep no conversation state.
Two things matter when you sum tokens over the turns of a loop.
reasoning_tokens is part of completion_tokens, not a separate item. Sum prompt_tokens and completion_tokens and leave reasoning_tokens out, otherwise you count the reasoning twice. The field appears only when reasoning ran.
prompt_tokens grows with every turn, because you resend the full history plus every tool result.
When you stream, usage arrives in the final chunk before data: [DONE].
Reasoning inside a tool loop
Each turn reasons on its own budget, so the depth you choose is a per-turn cost. Omit reasoning_effort and you get medium, which allows roughly 16k reasoning tokens per turn. Send none or low for short mechanical tool steps and keep the deeper setting for the turn that writes the final answer.
Size max_tokens for the reasoning and the answer together. If a turn spends its whole budget thinking without producing output, you get finish_reason: "length" instead of an empty "stop". Retry that turn with a larger max_tokens.
Do not send reasoning_content from an earlier turn back in the history.