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:
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:
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.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Промпт-инжиниринг: примеры и 12 готовых шаблонов для работыДвенадцать шаблонов промптов для рабочих задач: что в них написано, почему именно так и сколько стоит один вызов по API.