Ответсообщение недоступно
Померил 17 моделей на честность вызова инструментов. Не «отвечает ли модель», а соблюдает ли она контракт tools — потому что ломается именно он, и ломается молча.
Десять проверок на модель: возврат истории, параллельные вызовы, поток вместе с параллельными, вызов без аргументов, очень длинный аргумент, юникод в аргументах, tool_choice=required, tool_choice=named, 25 инструментов сразу, обрыв по лимиту токенов.
Главное нашлось не в моделях, а в маршрутизации. Если брать доступ через реселлера, часть каналов молча выкидывает поле tools — и модель вместо вызова функции сочиняет правдоподобный ответ. На запросе погоды с обязательным tool_choice один такой канал шесть раз из шести вернул выдуманную погоду. По HTTP это 200 OK, по метрикам провайдера — успех, по факту агент сломан, и вы об этом не узнаете.
Второе, на чём легко потерять день: у части каналов arguments приходят склеенными, вида {}{"city":"Paris"}. Обычный json.loads падает ровно там, где агент должен был вызвать инструмент.
Третье, самое неприятное: ответ 200 с идеально оформленным потоком, где finish_reason=stop, есть usage, есть [DONE] — и ни одного символа содержимого. Клиенту не на что ругаться, он просто ничего не показывает. Если у вас агент иногда «думает и молчит» — скорее всего это оно.
Итог по моделям: 162 проверки из 170, без единого замечания 14 из 17. Аутсайдеры — grok-4.5 (6 из 10) и glm-5.2 (7 из 10, tool_choice не соблюдается вообще).
Как проверить у себя, если пользуетесь любым шлюзом: ставьте tool_choice=required и убеждайтесь, что в ответе реально пришёл tool_calls, а не текст. По содержанию текста не отличить — сломанный канал отвечает правдоподобно.
Полная таблица с разбором каждого провала и методикой: llmass.arbitron.dev/tool-calls
Это данные моего шлюза, замер идёт каждый час.