---
url: https://docs.freetheai.org/concepts/tool-calls.md
description: >-
  Function calling on FreeTheAI with the OpenAI tools format, the Responses API,
  and Anthropic tool use. A complete working loop in Python and TypeScript.
---

# Tool calls

Tool calls (function calling) let a model ask your code to run a function and use the result. FreeTheAI supports them on Chat Completions, Responses, and Anthropic Messages, streamed or not, in each format's standard shape. Coding agents such as Claude Code, Codex, Cline, and OpenCode rely on them.

## Which models can call tools

`fta/zai/glm-5.3` and `fta/kimi/k3` handle tool calls well and were tested with Claude Code and Codex. The `fta/dog/` and `fta/lion/` models cannot make tool calls.

For agents, turn on **Tool-capable models only** on your key ([key settings](/getting-started/account#key-settings)). `/v1/models` then lists only models that can call tools, and any other model is refused with:

```text
This API key is set to tool-capable models only, and this model cannot take tool calls.
```

## The loop

1. Send the conversation with a `tools` list.
2. If the reply has `tool_calls`, run each function with its arguments.
3. Append the assistant message and one `tool` message per call, then send again.
4. Repeat until the model answers with plain text.

::: code-group

```python [Python]
import json
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.freetheai.org/v1", api_key=os.environ["FTAI_KEY"])

def get_weather(city: str) -> dict:
    # Replace with a real lookup.
    return {"city": city, "temperature_c": 18, "conditions": "cloudy"}

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": "Should I take an umbrella in Lisbon today?"}]

while True:
    reply = client.chat.completions.create(
        model="fta/zai/glm-5.3", messages=messages, tools=tools,
    )
    message = reply.choices[0].message
    if not message.tool_calls:
        print(message.content)
        break
    messages.append(message.model_dump(exclude_none=True))
    for call in message.tool_calls:
        args = json.loads(call.function.arguments or "{}")
        result = get_weather(**args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result),
        })
```

```ts [TypeScript]
import OpenAI from 'openai'
import type { ChatCompletionMessageParam, ChatCompletionTool } from 'openai/resources/chat/completions'

const client = new OpenAI({ baseURL: 'https://api.freetheai.org/v1', apiKey: process.env.FTAI_KEY })

function getWeather(city: string) {
  // Replace with a real lookup.
  return { city, temperature_c: 18, conditions: 'cloudy' }
}

const tools: ChatCompletionTool[] = [{
  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'],
    },
  },
}]

const messages: ChatCompletionMessageParam[] = [
  { role: 'user', content: 'Should I take an umbrella in Lisbon today?' },
]

while (true) {
  const reply = await client.chat.completions.create({ model: 'fta/zai/glm-5.3', messages, tools })
  const message = reply.choices[0].message
  if (!message.tool_calls?.length) {
    console.log(message.content)
    break
  }
  messages.push(message)
  for (const call of message.tool_calls) {
    if (call.type !== 'function') continue
    const args = JSON.parse(call.function.arguments || '{}')
    messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(getWeather(args.city)) })
  }
}
```

:::

The assistant message with a tool call looks like this:

```json
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": { "name": "get_weather", "arguments": "{\"city\":\"Lisbon\"}" }
    }
  ]
}
```

`finish_reason` is `tool_calls` when the model stops to call a tool.

## Options

| Field | What it does |
| --- | --- |
| `tools` | The functions the model may call, each with a JSON Schema for its parameters. |
| `tool_choice` | `"auto"` (default), `"none"`, `"required"`, or `{"type": "function", "function": {"name": "get_weather"}}` to force one. |
| `parallel_tool_calls` | Whether the model may ask for several calls in one turn, where the model supports it. Handle every entry in `tool_calls` either way. |

Arguments arrive as a JSON string. Parse them defensively: a model can produce invalid or incomplete JSON, so catch the error and send it back as the tool result.

## Streaming tool calls

When streaming, tool calls arrive in pieces in `delta.tool_calls`, keyed by `index`. Join the `function.arguments` pieces for each index before parsing. The OpenAI SDKs' helpers (for example `client.chat.completions.stream(...)` in Python) do this for you.

## Responses and Anthropic Messages

The other two formats use their own standard tool shapes:

* **Responses:** `tools: [{"type": "function", "name": "get_weather", "parameters": {...}}]`. Calls come back as `function_call` output items; send results as `function_call_output` input items with the same `call_id`.
* **Anthropic Messages:** `tools: [{"name": "get_weather", "description": "...", "input_schema": {...}}]`. Calls come back as `tool_use` content blocks; send results in a `user` message with `tool_result` blocks. See the [Anthropic SDK example](/sdks/anthropic#tool-use).

## Web search as a tool

The `fta/dog/` and `fta/lion/` models can search the web on their own when you ask for it with `web_search_options` (Chat Completions) or a `web_search` tool type (Responses and Messages). For other models, call [`/v1/search`](/api/search) yourself, for example from a tool. See [Web search](/concepts/web-search).
