Tool calling
You describe functions in tools. The model answers with the calls it wants made, your code runs them, and you send the results back.
Lanes
Tool calling works on deepseek-v4.1-flash:cached, deepseek-v4.1-flash:standard and qwen3.8-27b:standard. Each of them returned several calls in one reply, in a stream as well, and used the results it was sent back.
Ask for a call
cURL
curl https://api.getworg.com/v1/chat/completions \
-H "Authorization: Bearer $WORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash:cached",
"messages": [{"role": "user", "content": "What is the weather in Jakarta and in Bandung right now?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}],
"reasoning_effort": "none",
"max_tokens": 300
}'Python
import json
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.getworg.com/v1",
api_key=os.environ["WORG_API_KEY"],
)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
def get_weather(city):
# Your own code: call a weather service, read a database, and so on.
temps = {"Jakarta": 31, "Bandung": 24}
return {"city": city, "temp_c": temps.get(city, 28), "sky": "cloudy"}
messages = [{"role": "user", "content": "What is the weather in Jakarta and in Bandung right now?"}]
while True:
response = client.chat.completions.create(
model="deepseek-v4.1-flash:cached",
messages=messages,
tools=tools,
reasoning_effort="none",
max_tokens=500,
)
message = response.choices[0].message
if not message.tool_calls:
print(message.content)
break
# Send the assistant turn back unchanged, then one tool message per call.
messages.append(message.model_dump(exclude_none=True))
for call in message.tool_calls:
args = json.loads(call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(get_weather(**args)),
})The reply has finish_reason: "tool_calls" and one entry per call in message.tool_calls, each with an id, the function name, and its arguments as a JSON string. For the question above every lane asked for get_weather twice, once for Jakarta and once for Bandung.
Send the results back
- Append the assistant message as you received it, with its
tool_calls. - For each call, append a message with
role: "tool", the call'stool_call_id, and the result as text incontent. - Send the whole list again. Repeat until the reply has no
tool_calls.
The Python tab above runs this loop.
tool_choice
"auto", the default: the model decides whether to call a function."required": the model must call at least one function.{"type": "function", "function": {"name": "get_weather"}}: the model must call that function. It fills in arguments even when the conversation gives none, so check them.
"parallel_tool_calls": false asks for one call per reply. Only deepseek-v4.1-flash:standard follows it; on the other lanes, handle several calls in one reply.
Good to know
- Tool definitions count as input tokens on every request that carries them.
- In a stream, the arguments arrive in pieces. See Tool calls in a stream.
- The model only asks for a call. Nothing runs unless your code runs it, so check the arguments before you act on them.