# Tool calling (function calling)

> Let GLM, Kimi, DeepSeek and other models call your functions through BigModel.io with OpenAI tools or Anthropic tool use, including the full request loop.

Web version: https://bigmodel.io/docs/tool-calling · Updated 2026-10-18

Tool calling lets the model ask your code to run a function and then use the result. BigModel.io forwards the standard OpenAI `tools` format on Chat Completions and Anthropic `tools` on Messages. Whether a model calls tools well depends on the model itself.

| What you send | Where it works |
|---|---|
| `tools`, `tool_choice` (and legacy `functions`, `function_call`) | `/v1/chat/completions` on every group |
| Anthropic `tools` with `tool_use` / `tool_result` blocks | `/v1/messages` on groups that serve Messages |

On GLM and DeepSeek groups the request body is passed to the model provider unchanged, so every tool option the model supports works. On groups where Chat Completions is converted to another protocol, `tools`, `tool_choice`, `functions` and `function_call` are kept, but other fields such as `parallel_tool_calls` are dropped.

Check the model's page in the [catalog](https://bigmodel.io/models) for a Tool use tag before you build on it.

## Make a tool call

The loop has three steps: send the tools, run the function the model asks for, then send the result back.

```python [Python]
import json, os
from openai import OpenAI

client = OpenAI(base_url="https://bigmodel.io/v1", api_key=os.environ["BIGMODEL_API_KEY"])

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

def get_weather(city):
    return {"city": city, "temp_c": 21, "sky": "clear"}  # your real lookup here

messages = [{"role": "user", "content": "What's the weather in Hangzhou?"}]
first = client.chat.completions.create(model="glm-5.3", messages=messages, tools=tools)
msg = first.choices[0].message

if msg.tool_calls:
    messages.append(msg)  # keep the assistant turn with its tool_calls
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(get_weather(**args)),
        })
    final = client.chat.completions.create(model="glm-5.3", messages=messages, tools=tools)
    print(final.choices[0].message.content)
else:
    print(msg.content)
```

```js [Node.js]
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://bigmodel.io/v1", apiKey: process.env.BIGMODEL_API_KEY });

const tools = [{
  type: "function",
  function: {
    name: "get_weather",
    description: "Get the current weather for a city",
    parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] },
  },
}];
const getWeather = ({ city }) => ({ city, temp_c: 21, sky: "clear" });

const messages = [{ role: "user", content: "What's the weather in Hangzhou?" }];
const first = await client.chat.completions.create({ model: "glm-5.3", messages, tools });
const msg = first.choices[0].message;

if (msg.tool_calls?.length) {
  messages.push(msg);
  for (const call of msg.tool_calls) {
    const result = getWeather(JSON.parse(call.function.arguments));
    messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
  }
  const final = await client.chat.completions.create({ model: "glm-5.3", messages, tools });
  console.log(final.choices[0].message.content);
} else {
  console.log(msg.content);
}
```

```bash [cURL]
curl https://bigmodel.io/v1/chat/completions \
  -H "Authorization: Bearer $BIGMODEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "glm-5.3",
    "messages": [{"role": "user", "content": "What is the weather in Hangzhou?"}],
    "tools": [{"type":"function","function":{"name":"get_weather","description":"Get the current weather for a city","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}]
  }'
```

`tool_choice` controls the call: `"auto"` (default), `"none"`, `"required"`, or `{"type":"function","function":{"name":"get_weather"}}` to force one function. Support for `"required"` and forced functions varies by model.

## Tool use with the Messages API

On groups that serve `/v1/messages`, use Anthropic's format. The model replies with a `tool_use` block; you answer with a `tool_result` block that carries the same `id`.

```python [Python]
import os
from anthropic import Anthropic

client = Anthropic(base_url="https://bigmodel.io", api_key=os.environ["BIGMODEL_API_KEY"])
tools = [{
    "name": "get_weather",
    "description": "Get the current weather for a city",
    "input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
}]
messages = [{"role": "user", "content": "What's the weather in Hangzhou?"}]

first = client.messages.create(model="glm-5.3", max_tokens=512, tools=tools, messages=messages)
if first.stop_reason == "tool_use":
    call = next(b for b in first.content if b.type == "tool_use")
    messages += [
        {"role": "assistant", "content": first.content},
        {"role": "user", "content": [{"type": "tool_result", "tool_use_id": call.id, "content": '{"temp_c": 21}'}]},
    ]
    final = client.messages.create(model="glm-5.3", max_tokens=512, tools=tools, messages=messages)
    print(final.content[0].text)
```

## Streaming tool calls

When you stream, tool call arguments arrive in pieces in `choices[0].delta.tool_calls[i].function.arguments`. Concatenate the pieces per `index` and parse the JSON only after `finish_reason` is `tool_calls`. See [Streaming](https://bigmodel.io/docs/streaming).

## Verify the setup

Run the cURL example. The response should have `"finish_reason": "tool_calls"` and a `tool_calls` array with `get_weather` and `{"city":"Hangzhou"}` as arguments. If you get plain text instead, the model chose not to call the tool; try `"tool_choice": "required"` or another model.

## Troubleshooting

**The model answers in text and never calls the tool.** The model may not support tools, or the description is too vague. Check the model page for a Tool use tag, describe when to use the tool, and try `tool_choice`.

**`json.loads` fails on the arguments.** Models sometimes return invalid JSON in arguments. Catch the error and send it back as the tool result so the model can retry, or validate with a schema library.

**400 from the provider about the tool result.** Every `tool` message needs the `tool_call_id` of a call in the previous assistant turn, and that assistant turn must be included in `messages`.

**409 `malformed_session_state` on `/v1/messages`.** The history has a `tool_result` whose id does not match a `tool_use`, or edited thinking blocks. Send the assistant content back unchanged.

**Only one tool is called when you expected several.** Parallel calls depend on the model. On converted groups `parallel_tool_calls` is dropped, so you cannot switch it.

**503 `api_error` "No available accounts".** The model is not served to your key's group. See [Model IDs](https://bigmodel.io/docs/model-ids).

## Related

- [Structured output](https://bigmodel.io/docs/structured-output): use a tool schema to get JSON on any group.
- [Streaming](https://bigmodel.io/docs/streaming) and [Reasoning & thinking](https://bigmodel.io/docs/reasoning).
- [Chat Completions API reference](https://bigmodel.io/docs/api-chat-completions).
