Skip to content
Menu
Base URLhttps://api.getworg.com/v1

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

  1. Append the assistant message as you received it, with its tool_calls.
  2. For each call, append a message with role: "tool", the call's tool_call_id, and the result as text in content.
  3. 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.

Next: Structured outputStreaming