Tool calling

Tool calling lets a model reach beyond text — look something up, hit your API, query a database, run a calculation. You describe the functions available; the model decides when to call one and with what arguments. Pendra is OpenAI-shaped, so it works exactly like the OpenAI tools API.

Describe your tools

Pass a tools array, each entry a function with a name, description, and JSON Schema parameters. When the model wants to use one, it replies with a tool_calls array on the assistant message instead of a plain answer.

from pendra import Pendra

client = Pendra()

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

response = client.chat.completions.create(
    model="qwen3.6:27b",
    tools=tools,
    messages=[{"role": "user", "content": "What's the weather in Cardiff?"}],
)

call = response.choices[0].message.tool_calls[0]
print(call.function.name, call.function.arguments)
Tool call
get_weather {"city": "Cardiff"}

Instead of answering directly, the model asks you to run get_weather with city: "Cardiff". You run it and pass the result back.

Run the loop

Tool calling is a round trip you drive:

  1. Send the conversation with your tools.
  2. If the reply contains tool_calls, run each one in your code.
  3. Append a { "role": "tool", "tool_call_id": ..., "content": ... } message with each result.
  4. Call the endpoint again so the model can use the results to answer.

Use tool_choice to force or forbid a call, and parallel_tool_calls to allow several at once. Tool calling works on tool-capable chat models (for example Qwen Instruct) — browse available models to see which advertise it.

Set "tool_choice": "required" when your code needs a tool call rather than a text answer. The reply then contains tool calls only. Name a function, as in { "type": "function", "function": { "name": "get_weather" } }, to pin the call to it.

Reasoning models still think first, and the call comes once their thinking ends, so give them enough max_tokens to finish. If the limit is reached mid-thought, the reply stops with finish_reason: "length" and no call. The gpt-oss models use their own tool-call format and treat a forced choice as a strong instruction rather than a guarantee. Leave tool_choice unset (or "auto") to let the model decide whether a call is needed at all.

For the full request and response schema, see the Chat completions API reference.