Function calling у Claude, GPT и GLM: что по-разному

Формат tool calling зависит не от модели, а от эндпоинта, через который вы её вызываете. Разбираем разницу между /v1/messages, /v1/chat/completions и /v1/responses.

4 мин · · Обновлено · разработка

Формат вызова функций определяется эндпоинтом, а не тем, чья модель отвечает. Через шлюз можно дёрнуть glm-5.3 в Anthropic-формате или claude-opus-5-5 в OpenAI-формате — оба режима работают одновременно для любой модели из каталога. Разница, которая реально важна, — это разница между тремя форматами запроса, а не между вендорами.

Anthropic-формат: /v1/messages

Инструмент описывается через input_schema:

bash
curl https://api.zukko.pro/v1/messages \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-sonnet-5",
    "max_tokens": 512,
    "tools": [{
      "name": "get_weather",
      "description": "Погода в городе",
      "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
      }
    }],
    "messages": [{"role": "user", "content": "Погода в Алматы?"}]
  }'

Модель отвечает блоком с "type": "tool_use", внутри — id, name, input. Результат выполнения функции нужно вернуть отдельным сообщением от user с содержимым типа tool_result, указав тот же tool_use_id. Заголовок anthropic-version: 2023-06-01 обязателен — без него запрос не пройдёт вообще, что часто путают с ошибкой в самом описании инструмента.

OpenAI-формат: /v1/chat/completions

Здесь инструмент — это type: "function" с parameters вместо input_schema:

bash
curl https://api.zukko.pro/v1/chat/completions \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "glm-5.3",
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Погода в городе",
        "parameters": {
          "type": "object",
          "properties": {"city": {"type": "string"}},
          "required": ["city"]
        }
      }
    }],
    "messages": [{"role": "user", "content": "Погода в Алматы?"}]
  }'

Вызов приходит в message.tool_calls — массиве объектов с id и function.arguments, где arguments — это строка с JSON, а не готовый объект: её нужно парсить самостоятельно. Ответ на вызов отправляется сообщением с "role": "tool" и тем же tool_call_id. Это первое, на чём спотыкаются при переносе кода с Anthropic-формата: input там уже объект, arguments здесь — строка.

/v1/responses: третий формат, не смесь первых двух

/v1/responses — отдельный формат OpenAI, принимает input и строкой, и массивом сообщений, плюс tools. Вызов функции приходит не в message.tool_calls, а отдельным элементом в списке output с полем call_id — структура ответа отличается от /v1/chat/completions, и код, писанный под Chat Completions, не подойдёт без переделки под Responses.

Что реально по-разному, а что нет

По-разному: имя поля со схемой (input_schema vs parameters), тип arguments/input (строка vs объект), где искать вызов в ответе (content vs tool_calls vs output), какой заголовок обязателен (anthropic-version — только для /v1/messages).

Не по-разному: любую модель каталога можно позвать через любой из трёх эндпоинтов — выбор формата определяется тем, что удобнее вашему коду, а не тем, кто выпустил модель. Если у вас уже есть код под OpenAI tool calling, вызывать через него claude-opus-5-5 не требует переписывания под Anthropic-формат.

Практические грабли

Опечатка в имени модели при переключении формата. Меняя эндпоинт, легко забыть, что имя модели не меняется — claude-sonnet-5 остаётся claude-sonnet-5 что в /v1/messages, что в /v1/chat/completions. Ошибка model_not_found в такой ситуации почти всегда значит опечатку в другом поле, а не в имени модели.

Повторные вызовы функции при ретраях. Если ваш агентный цикл ретраит запрос после сетевой ошибки, добавляйте Idempotency-Key в заголовки конкретного запроса — один уникальный ключ на логический запрос и тот же самый при его повторах (не в default_headers клиента и не новый на каждый ретрай) — иначе один и тот же вызов функции модель может попросить выполнить дважды, и если функция не идемпотентна сама по себе (например, списание денег или отправка письма), это приведёт к двойному эффекту на вашей стороне, а не на стороне шлюза.

JSON без корректных отступов в settings.json или в конфиге агента. Лишняя запятая в конце объекта тихо ломает файл целиком, и тогда инструменты просто не долетают до модели — проверять валидность JSON стоит до того, как разбираться, почему модель «не вызывает функцию».

Параллельные вызовы в одном ответе

Оба формата допускают несколько вызовов функций в одном ответе, если модель решает, что нужно дёрнуть, скажем, get_weather для трёх городов сразу. В Anthropic-формате это несколько блоков tool_use подряд в content, в OpenAI-формате — несколько элементов в массиве tool_calls. Обрабатывать нужно каждый по отдельности и вернуть результат для каждого tool_use_id/tool_call_id — если ответить только на первый вызов, модель будет ждать результата и по остальным.

Кэш и повторяющееся описание инструментов

Если агентный цикл на каждом шаге диалога заново отправляет одно и то же описание инструментов вместе с растущей историей сообщений, это лишние токены каждый раз. Кэш шлюза (заголовок X-Zukko-Cache: true; temperature: 0 включает его только у моделей, которые принимают этот параметр, — не у Claude) здесь не поможет: он срабатывает только при полном совпадении тела запроса, а одинаковые tools при новой истории — это уже другой запрос, и он тарифицируется целиком; частичного или префиксного кэша нет. Экономить на длинных диалогах с большим набором функций можно сокращением самих описаний инструментов и истории. И учтите: у вендоров чтение из кэша промптов стоит ~10% цены входа, а в Zukko такие токены идут по обычной цене — для агентов с большим повторяющимся контекстом реальная разница в цене меньше, чем при сравнении прайсов «в лоб».

Итог простой: разница между Claude, GPT и GLM в function calling — это разница между тремя форматами запроса на одном шлюзе, а не разница в возможностях самих моделей.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.

Function calling: Claude vs GPT vs GLM · Zukko