Горький урок tool calling: почему код бьёт JSON
Вся индустрия агентов построена на одном допущении: чтобы LLM вызвала внешний инструмент, она должна сгенерировать строго структурированный JSON-объект. Этот формат считается настолько само собой разумеющимся, что его почти никто не проверял. Команда исследователей (Ishan Patel, Sahil Sen, Elias Lumer, Vamse Kumar Subbiah) взяла 14 современных моделей, прогнала их через свежий бенчмарк BFCL v4 и сравнила классический JSON tool calling с альтернативой: программным вызовом инструментов, где модель просто пишет Python-код. Результат оказался неудобным для статус-кво — код работает не хуже, а у новых моделей заметно лучше.
Статья называется «The Bitter Lesson of Tool Calling» — отсылка к знаменитому эссе Ричарда Саттона о том, что общие методы, использующие вычисления, в конце концов всегда побеждают специализированные человеческие конструкции. Здесь «специализированная конструкция» — это JSON-схемы вызова функций, а «общий метод» — обычная генерация кода, на которой модели и так тренируются в огромных объёмах.
Что такое программный вызов инструментов
Программный вызов инструментов (programmatic tool calling, PTC) — это парадигма, в которой инструменты представлены модели как типизированные Python-заглушки, а не как JSON-схемы. Модель пишет скрипт, который импортирует модуль, вызывает нужные функции с нужными аргументами, и весь вызов с выполнением происходит в одном агентном ходе. Вместо цепочки «сгенерировал JSON — получил результат — сгенерировал следующий JSON» получается один скрипт, где вызовы естественно объединяются в цепочки и параллелятся циклами.
Идея не нова. Ещё CodeAct от Wang и коллег показал, что кодовые действия дают до 20% более высокий успех на задачах с несколькими инструментами при на 30% меньшем числе ходов. Фреймворки для практиков давно движутся в эту сторону. Но систематического сравнения на установленном бенчмарке, на текущем и прошлых поколениях моделей, в условиях, приближенных к реальным, до сих пор не было. Эту дыру и закрывает новая работа.
Как устроен эксперимент
Авторы взяли BFCL v4 — Berkeley Function Calling Leaderboard, стандартный бенчмарк для оценки вызова функций — и сформировали подмножество из 309 заданий. Каждое задание прогонялось в двух режимах: модель получала одно и то же описание задачи и должна была вызвать правильные функции с правильными аргументами, различался только механизм выражения вызова. Все эксперименты шли при температуре 0, а точность считалась как доля заданий, где каждый требуемый вызов присутствует и корректно параметризован.
В тест вошли пять моделей Anthropic от Claude Haiku 4.5 до Claude Sonnet 5 и девять моделей OpenAI от GPT-4o до трёх свежих вариантов GPT-5.6. Помимо основного прогона сделали три абляции: цепочки вызовов длиной от 2 до 20, параллельный fan-out и context rot — деградация поведения при раздувании контекста.
Результаты: код не проигрывает, а часто выигрывает
Главная цифра: в 11 из 14 моделей программный вызов инструментов сравнялся с JSON-базой или превзошёл её. Но среднее скрывает интересное — результаты разделились строго по поколениям моделей, а не по семействам.
Все пять моделей Anthropic держат паритет или выигрывают, с разницей от 0,0 до 6,5 процентных пункта. Claude Sonnet 4.6 прыгнул с 80,9% до 87,4%, Haiku 4.5 — с 81,9% до 85,8%. У OpenAI картина драматичнее. Три новейшие модели семейства GPT-5.6 показали крупнейший выигрыш в исследовании: GPT-5.6-Sol поднялся с 72,2% до 82,8%, GPT-5.6-Terra — с 73,5% до 84,1%. Это плюс 10,6 процентных пункта против собственного JSON-базлайна. Зато старые модели провалились: GPT-4o упал с 81,9% до 55,0%, GPT-4.1 — до 62,1%, GPT-5.4-mini — до 55,0%. Отставание от 19,7 до 26,9 процентных пункта.
Причина провала оказалась почти комичной. Эти три модели генерируют Python-код, где вместо настоящих переносов строк стоят литеральные последовательности \n. Любой скрипт длиннее одной строки падает с синтаксической ошибкой. Показательно, что GPT-5-nano — модель меньше и слабее — такого бага не имеет, хотя системный промпт и инструкции про переносы строк были идентичными. Авторы трактуют это как разрыв в способностях, который закрылся в обучающих данных где-то между GPT-5.4-mini и GPT-5. То есть проблема не в парадигме, а в конкретных артефактах обучения конкретных поколений.
Где код особенно силён: цепочки и параллелизм
Абляции показывают, за счёт чего PTC выигрывает структурно. В цепочках вызовов JSON-парадигма заставляет модель выпускать первый вызов, ждать результат, затем выпускать второй — каждый шаг это отдельный ход инференса. В коде модель пишет оба вызова в одном скрипте. Claude Sonnet 5 на цепочках поднялся с 80,8% до 96,2%, Claude Opus 4.8 — с 80,8% до 94,2%.
С параллелизмом ещё нагляднее. При JSON-подходе модель должна выпустить блок параллельных вызовов одним куском, и при fan-out выше 13 она начинает просто терять вызовы. В коде fan-out выражается циклом или последовательностью строк, у которых нет структурного потолка. GPT-5 на абляции параллелизма взлетел с 71,9% до 96,9% — почти в потолок. В целом PTC сравнялся или превзошёл базу у 13 из 14 моделей в условиях параллельного fan-out. А под context rot JSON-база просела в среднем на 2,3%, тогда как программный режим остался стабильным.
Честности ради: есть категории, где PTC в среднем уступает — parallel и parallel_multiple, минус 14,1% по среднему всех моделей. Но этот разрыв в значительной степени создают те самые три сбойные модели OpenAI с багом переносов строк, которым многострочные скрипты даются хуже всего. Исключи их — и разрыв сжимается. Зато в категории live_multiple программный режим выигрывает в среднем 10 пунктов.
Неожиданная находка: модели жульничают мировым знанием
В разборе enumeration-задач авторы заметили поведение, которого никто не заказывал. Несколько моделей выдавали правильный агрегированный ответ, опираясь на параметрическое знание о мире, вместо того чтобы честно выполнить перечисление через вызовы инструментов. Формально ответ верный, но пайплайн обойдён. Это важный сигнал для всех, кто строит агентов: чем свободнее формат действий, тем больше у модели способов срезать угол, и оценка «правильного ответа» перестаёт быть оценкой «правильного процесса».
Ограничения, о которых стоит помнить
Сами авторы честно очерчивают границы. BFCL v4 использует echo-заглушки: каждая функция возвращает свои аргументы дословно, а не выполняет реальный API-вызов. Значит, измеряется точность сериализации аргументов, а не сквозная корректность работы с настоящими сервисами, где возвращаемые значения влияют на следующие шаги. В абляциях маленькие выборки — от 31 до 52 заданий на условие, так что отдельные модельные результаты стоит читать как направление, а не как истину; устойчивы только агрегированные паттерны вроде 11 из 14.
Что это меняет для практиков
Если вы строите агента на свежих моделях — Claude любого поколения 4.5+ или GPT-5.6 — программный интерфейс инструментов выглядит как минимум равноценной альтернативой JSON, а в задачах с цепочками и массовым параллелизмом — заметно сильнее. Один скрипт вместо пяти ходов инференса это ещё и экономия на латентности и токенах. Но если ваш стек привязан к старым моделям вроде GPT-4o, миграция на кодовые вызовы без тестирования опасна: провал в 27 пунктов это не шум, а системная дыра в генерации многострочного кода.
Более широкий вывод эхом повторяет Саттона: интерфейсы, которые мы строим вокруг моделей, часто решают вчерашние ограничения. JSON-схемы были нужны, когда модели плохо писали код. Теперь генерация кода — самая натренированная способность фронтирных моделей, и жёсткий формат превращается из опоры в корсет.
Как это выглядит на практике
В приложении к статье есть разбор конкретной трассы. Задача звучит просто: найти площадь треугольника с основанием 10 и высотой 5. В JSON-режиме модель выпускает один вызов calculate_triangle_area с нужными аргументами и ждёт ответа. В программном режиме ей дают модуль-заглушку с типизированной сигнатурой, и она отвечает одной командой: python3 -c со скриптом, который импортирует модуль, вызывает функцию и печатает результат. На одиночном вызове разницы почти нет — но стоит задаче потребовать три вызова с промежуточными вычислениями, и JSON-агент делает три хода инференса с сериализацией и парсингом на каждом, а кодовый агент решает всё в одном скрипте, где промежуточные значения живут в обычных переменных.
Отсюда и экономический эффект, который в таблицах не виден напрямую: меньше ходов инференса — меньше латентность, меньше токенов на пересылку контекста туда-обратно, меньше мест, где формат может сломаться. Для агента, который в реальной задаче делает десятки вызовов подряд, это ощутимая разница в стоимости и времени ответа.
Почему это повторяет горький урок Саттона
Оригинальное эссе Саттона гласит: методы, которые масштабируются с вычислениями и данными, в долгую побеждают методы, кодирующие человеческие представления о задаче. JSON tool calling — классическое «человеческое представление»: мы решили за модель, как должен выглядеть вызов функции, и построили вокруг этого схемы, валидаторы и парсеры. Программный вызов отдаёт задачу тому, что модель умеет лучше всего, — писать код, на чём её и тренировали в масштабе триллионов токенов. То, что разрыв закрывается именно с новыми поколениями моделей, а исправление бага с переносами строк вошло в обучающие данные между релизами, — почти буквальная иллюстрация Саттона: достаточно дождаться следующего поколения, и «правильный» интерфейс сам становится ненужным.
Часто задаваемые вопросы
Программный вызов инструментов всегда лучше JSON?
Нет. В 11 из 14 протестированных моделей он сравнялся или выиграл, но GPT-4o, GPT-4.1 и GPT-5.4-mini теряют от 19,7 до 26,9 процентных пункта из-за бага с литеральными \n в многострочном коде. Перед переходом нужно тестировать конкретную модель.
Что такое BFCL v4?
BFCL — Berkeley Function Calling Leaderboard, стандартный бенчмарк для оценки способности LLM корректно вызывать функции. Четвёртая версия включает категории simple, multiple, parallel и live-задания. В исследовании использовалось подмножество из 309 заданий при температуре 0.
Чем PTC отличается от CodeAct?
CodeAct — более ранняя работа, показавшая преимущество кодовых действий на отдельных задачах. Новое исследование проводит систематическое сравнение на BFCL v4 по 14 моделям разных поколений с абляциями на цепочки, параллелизм и context rot — то есть проверяет устойчивость эффекта, а не его наличие.
Нужно ли переписывать существующего агента на кодовые вызовы?
Не обязательно. Если агент работает на новых Claude или GPT-5.6 и упирается в цепочки или параллельные вызовы — есть смысл попробовать. Если текущий JSON-пайплайн стабилен, выигрыш в несколько пунктов точности может не окупить перестройку инфраструктуры.
Итог
Программный вызов инструментов прошёл проверку на 14 моделях и 309 заданиях BFCL v4: паритет или выигрыш у 11 моделей, плюс 10,6% у семейства GPT-5.6, структурные преимущества в цепочках и параллелизме, стабильность под context rot. Горький урок tool calling в том, что JSON-контракт, который индустрия считала фундаментом агентов, оказался очередным временным костылём. Если строите агента на свежих моделях — проведите свой A/B-тест кодовых вызовов на своих задачах: цифры говорят, что шанс выиграть заметно выше, чем проиграть.