Function calling across Claude, GPT and GLM: what actually differs

The tool-calling format depends on the endpoint you hit, not the model behind it. Comparing /v1/messages, /v1/chat/completions and /v1/responses.

4 min · · Updated · development

The tool-calling format is decided by the endpoint, not by which model answers. Through the gateway you can call glm-5.3 in the Anthropic shape or claude-opus-5-5 in the OpenAI shape — both modes work at once for every model in the catalog. The difference that actually matters is between the three request formats, not between vendors.

The Anthropic shape: /v1/messages

A tool is described with input_schema:

bash
curl https://api.zukko.pro/v1/messages \
  -H "Authorization: Bearer fetch_your_key" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-sonnet-5",
    "max_tokens": 512,
    "tools": [{
      "name": "get_weather",
      "description": "Weather in a city",
      "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
      }
    }],
    "messages": [{"role": "user", "content": "Weather in Almaty?"}]
  }'

The model answers with a block of "type": "tool_use" carrying id, name, input. You send the function's result back as a separate message from user with content of type tool_result, referencing the same tool_use_id. The anthropic-version: 2023-06-01 header is mandatory — without it the request fails outright, which people often mistake for a problem in the tool description itself.

The OpenAI shape: /v1/chat/completions

Here a tool is type: "function" with parameters instead of input_schema:

bash
curl https://api.zukko.pro/v1/chat/completions \
  -H "Authorization: Bearer fetch_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "glm-5.3",
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Weather in a city",
        "parameters": {
          "type": "object",
          "properties": {"city": {"type": "string"}},
          "required": ["city"]
        }
      }
    }],
    "messages": [{"role": "user", "content": "Weather in Almaty?"}]
  }'

The call arrives in message.tool_calls — an array of objects with id and function.arguments, where arguments is a string holding JSON, not a ready-made object — you parse it yourself. The answer goes back as a message with "role": "tool" and the same tool_call_id. This is the first thing that trips people up porting code from the Anthropic shape: input there is already an object, arguments here is a string.

/v1/responses: a third format, not a blend of the first two

/v1/responses is a separate OpenAI format, accepting input as either a string or an array of messages, plus tools. A function call doesn't show up in message.tool_calls — it's a separate item in the output list carrying call_id. The response shape differs from /v1/chat/completions, and code written for Chat Completions won't drop into Responses without changes.

What actually differs, and what doesn't

Differs: the field name for the schema (input_schema vs parameters), the type of arguments/input (string vs object), where the call shows up in the response (content vs tool_calls vs output), which header is mandatory (anthropic-version, only for /v1/messages).

Doesn't differ: every model in the catalog can be called through any of the three endpoints — the format you pick depends on what's convenient for your code, not on who shipped the model. If you already have OpenAI tool-calling code, calling claude-opus-5-5 through it doesn't require rewriting for the Anthropic shape.

Practical gotchas

A typo in the model name when switching formats. Switching endpoints, it's easy to forget the model name doesn't change — claude-sonnet-5 stays claude-sonnet-5 whether you're on /v1/messages or /v1/chat/completions. A model_not_found error in that situation almost always means a typo somewhere else, not in the model name.

Duplicate function calls on retries. If your agent loop retries a request after a network error, add an Idempotency-Key to that specific request's headers — one unique key per logical request, and the same one on its retries (not in the client's default_headers, and not a fresh one per retry) — otherwise the model might be asked to invoke the same function twice, and if the function isn't idempotent on its own (charging money, sending an email), that duplicates on your side, not the gateway's.

Malformed JSON in settings.json or the agent config. A trailing comma silently breaks the whole file, and then tools never reach the model at all — check the JSON is valid before debugging why the model "won't call the function."

Parallel calls in one response

Both formats allow several function calls in a single response, if the model decides it needs to hit, say, get_weather for three cities at once. In the Anthropic shape that's several tool_use blocks in a row inside content; in the OpenAI shape it's several entries in the tool_calls array. Handle each one separately and return a result for every tool_use_id/tool_call_id — answering only the first call leaves the model waiting on the rest.

Caching and a repeated tool description

If an agent loop resends the same tool description alongside a growing message history on every turn, that's extra tokens every time. The gateway cache (the X-Zukko-Cache: true header; temperature: 0 only enables it on models that accept that parameter — not Claude) won't help here: it only works on a fully identical request body, and identical tools with a new history make a different request that's billed in full; there's no partial or prefix caching. On long conversations with a large set of functions, the way to save is to trim the tool descriptions and the history themselves. Also keep in mind: vendors charge ~10% of the input price for prompt-cache reads, while Zukko bills those tokens at the regular price — for agents with a large repeated context, the real price gap is smaller than a head-to-head price-list comparison suggests.

The short version: the difference between Claude, GPT and GLM in function calling is a difference between three request formats on one gateway, not a difference in what the models themselves can do.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

Function calling: Claude vs GPT vs GLM · Zukko