Docs
Guides

Function calling

Connect the Tase model to your own functions: define tools, receive structured tool calls, execute them in your app, and feed results back for a final answer.

Function calling (also called tool use) lets the model do things instead of just describing them. You describe the functions your application can run in the tools array of a request, and when the model decides one of them is the right way to respond, it returns a structured tool_calls payload instead of prose. Your application executes the function, sends the result back, and the model turns it into a final answer.

Tool use is where Tase differs most from a generic model. The schemas of Tase’s own built-in tools — tasks, health, finance, memory, agents, connections, and world knowledge — are trained into the model’s weights, so you never need to send those schemas with a request (sending them anyway is accepted). Custom tools that you define yourself are sent in the request exactly as on any OpenAI-compatible API. This page covers the mechanics; Built-in tools covers the tool families that ship inside the model.

When to use function calling

  • Live data — anything the model cannot know on its own: weather, prices, inventory, the user’s own records.
  • Actions with side effects — creating a task, logging a meal, drafting an email, scheduling an event.
  • Deterministic computation — exact math, database queries, code execution: anywhere “roughly right” is wrong.
  • Structured extraction — force output that matches a JSON Schema instead of free text by defining a tool whose arguments are the structure you want.

The tool call round trip

  1. 1

    Send messages and tools

    POST to /v1/chat/completions with the conversation so far and, for custom tools, their schemas in the tools array.
  2. 2

    The model returns tool calls

    Instead of a text answer, choices[0].message carries a tool_calls array — each entry has a function name and JSON-encoded arguments — and finish_reason is "tool_calls".
  3. 3

    Your application executes

    Parse the arguments and run the real function in your own code. The model never executes anything itself — it only emits an intent.
  4. 4

    Return the result

    Append the assistant message you received, then one {"role": "tool", "tool_call_id": ..., "content": ...} message per call, and send the extended history back.
  5. 5

    The model answers

    With the results in context, the model either writes the final response or requests more tool calls — loop until it stops asking.

The model proposes, your app disposes

Execution, permissions, and side effects always live in your application. That separation is what makes patterns like confirmation-before-delete possible — see Tool use patterns.

Defining a tool

Each entry in tools has type: "function" and a function object with a name, a description, and JSON Schema parameters. The description is what the model reads to decide when to call the tool — invest in it.

curl https://api.tase.app/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TASE_API_KEY" \
  -d '{
    "model": "tase-8b",
    "messages": [
      { "role": "user", "content": "What is the weather in Istanbul right now?" }
    ],
    "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, e.g. Istanbul" },
              "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
            },
            "required": ["city"]
          }
        }
      }
    ]
  }'

Reading the tool call response

{
  "id": "chatcmpl-9f2c1a7e",
  "object": "chat.completion",
  "model": "tase-8b",
  "choices": [
    {
      "index": 0,
      "finish_reason": "tool_calls",
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_abc123",
            "type": "function",
            "function": {
              "name": "get_weather",
              "arguments": "{\"city\": \"Istanbul\", \"unit\": \"celsius\"}"
            }
          }
        ]
      }
    }
  ]
}

Two details matter here. First, arguments is a JSON string, not an object — parse it before use. Second, keep the id: you must echo it back as tool_call_id so the model can match each result to the call that produced it.

Completing the loop in code

A minimal but complete loop in JavaScript (Node 18+) and Python. Both keep calling the endpoint until the model stops requesting tools.

const BASE_URL = 'https://api.tase.app/v1/chat/completions';

const 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, e.g. Istanbul' },
        },
        required: ['city'],
      },
    },
  },
];

async function chat(messages) {
  const res = await fetch(BASE_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: 'Bearer ' + process.env.TASE_API_KEY,
    },
    body: JSON.stringify({ model: 'tase-8b', messages, tools }),
  });
  const data = await res.json();
  return data.choices[0].message;
}

async function runTool(name, args) {
  if (name === 'get_weather') {
    // Replace with a real weather lookup.
    return { city: args.city, temp_c: 24, condition: 'sunny' };
  }
  return { status: 'error', message: 'Unknown tool: ' + name };
}

const messages = [{ role: 'user', content: "What's the weather in Istanbul?" }];

let message = await chat(messages);

while (message.tool_calls?.length) {
  messages.push(message);
  for (const call of message.tool_calls) {
    const args = JSON.parse(call.function.arguments);
    const result = await runTool(call.function.name, args);
    messages.push({
      role: 'tool',
      tool_call_id: call.id,
      content: JSON.stringify(result),
    });
  }
  message = await chat(messages);
}

console.log(message.content);
import json
import os

import requests

BASE_URL = "https://api.tase.app/v1/chat/completions"
HEADERS = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {os.environ['TASE_API_KEY']}",
}

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, e.g. Istanbul"}
                },
                "required": ["city"],
            },
        },
    }
]


def get_weather(city: str) -> dict:
    # Replace with a real weather lookup.
    return {"city": city, "temp_c": 24, "condition": "sunny"}


messages = [{"role": "user", "content": "What's the weather in Istanbul?"}]

while True:
    resp = requests.post(
        BASE_URL,
        headers=HEADERS,
        json={"model": "tase-8b", "messages": messages, "tools": tools},
    )
    message = resp.json()["choices"][0]["message"]
    tool_calls = message.get("tool_calls")
    if not tool_calls:
        break
    messages.append(message)
    for call in tool_calls:
        args = json.loads(call["function"]["arguments"])
        result = get_weather(**args)
        messages.append(
            {
                "role": "tool",
                "tool_call_id": call["id"],
                "content": json.dumps(result),
            }
        )

print(message["content"])

Parallel tool calls

When a request needs several independent lookups (“weather in Istanbul and London”), the model can return multiple entries in a single tool_calls array. Execute them in any order — concurrently if you like — and append one role: "tool" message per call. Every tool_call_id must be answered before you request the next completion.

[
  {
    "role": "assistant",
    "content": null,
    "tool_calls": [
      {
        "id": "call_ist",
        "type": "function",
        "function": { "name": "get_weather", "arguments": "{\"city\": \"Istanbul\"}" }
      },
      {
        "id": "call_lon",
        "type": "function",
        "function": { "name": "get_weather", "arguments": "{\"city\": \"London\"}" }
      }
    ]
  },
  { "role": "tool", "tool_call_id": "call_ist", "content": "{\"temp_c\": 24, \"condition\": \"sunny\"}" },
  { "role": "tool", "tool_call_id": "call_lon", "content": "{\"temp_c\": 18, \"condition\": \"cloudy\"}" }
]

Controlling tool use with tool_choice

  • "auto" (default) — the model decides whether to call a tool or answer directly.
  • "none" — tool calling is disabled; the model answers in text even when tools are present.
  • "required" — the model must call at least one tool before answering.
  • {"type": "function", "function": {"name": "get_weather"}} — force one specific tool.
{
  "model": "tase-8b",
  "messages": [{ "role": "user", "content": "Log 500 ml of water." }],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "log_water",
        "description": "Log a water intake entry for the current user.",
        "parameters": {
          "type": "object",
          "properties": {
            "amount_ml": { "type": "number", "description": "Amount of water in milliliters." }
          },
          "required": ["amount_ml"]
        }
      }
    }
  ],
  "tool_choice": { "type": "function", "function": { "name": "log_water" } }
}

When the model doesn’t call a tool

  • Check finish_reason"stop" means the model chose a plain text answer over a tool call.
  • Sharpen the description — if a call was genuinely needed, say when to use the tool explicitly: “Use this whenever the user mentions logging, tracking, or recording water.”
  • Force it — when your UX depends on a call, set tool_choice to "required" or name the exact tool.
  • Don’t retry blindly — sometimes text is the right answer. “What can you do?” deserves prose, not a tool call.

Validate arguments before executing

Model-produced arguments are well-formed JSON, but they are not guaranteed to satisfy your business rules — a date can be in the past, an amount can be negative, an enum can drift. Parse the arguments string, validate it against the same schema you declared (types, required fields, ranges), and on failure return a structured error as the tool result instead of throwing. The model reads the error and corrects itself on the next turn.

Treat arguments as untrusted input

Never pass model-produced arguments into a shell, SQL query, or interpreter without validation and parameterization — apply exactly the same rules you would to user-typed input.

Explore the built-in tools

Tase ships with tool families for tasks, health, finance, memory, agents, connections, and world knowledge — already trained into the model, no schemas required.

Built-in tools