Function calling: почему маленькие локальные модели врут про инструменты

Обложка

Есть момент, который наступает у каждого, кто строит агента в домашней лабе. В какой-то день тебе надоедает писать «сводки» и хочется, чтобы агент начал что-то делать руками: дёрнуть API, выполнить команду, проверить маршрут, перезапустить сервис. Ты лезешь в документацию, находишь слово «tools», радуешься и прописываешь модели список инструментов. А потом смотришь, как она их «вызывает», и ловишь себя на мысли: что-то тут не так.

Я прошёл через это целиком — и вылез с одной мыслью: function calling — это не про «ум», а про формат. И если модель этот формат не поддерживает, она тебе не «туповато вызывает инструменты», она просто молча отдаёт работу кому-то другому. Или падает. Или несёт мусор. Давайте разберём, где именно тут спрятан обман и как не наступить на те же грабли.

Что такое tools на самом деле

Начну с того, что function calling звучит как «модель умеет пользоваться функциями». Это красивая ложь. На деле всё куда скучнее и конкретнее: tools — это просто формат ответа.

Схема такая. Ты говоришь модели: вот список функций, которые можешь вызывать, вот их сигнатуры и описания. Модель не «запускает» ничего — она в ответ выдаёт не обычный текст, а структурированный блок «я хочу вызвать такую-то функцию с такими-то аргументами». Дальше уже твой код ловит этот блок, реально исполняет функцию, кладёт результат обратно в диалог, и модель продолжает с этого места. Все «агентские» чудеса — это цикл из трёх шагов: модель говорит «вызови», ты вызываешь, модель говорит «вот результат, сделай следующий шаг».

Ключевое слово здесь — «формат». Это не способность рассуждать, не навык, который модель «набирает» с размером. Это договорённость о том, что модель умеет выдавать ответ в специальной структурированной форме, а не только в свободном тексте. Либо модель этот формат выучила при обучении, либо нет. Третьего не дано — и тут начинается самое интересное.

Грабля: локальный агент, которого не было

Моя история с этой граблей началась красиво. У меня в лабе стоит свой GPU — встроенная карточка, проброшенная в контейнер через ROCm, на ней крутится Ollama с локальной моделью gemma3:12b. План был благородный: субагенты, которые разбирают сеть и ходят по командам, должны жить на своём железе. Приватно, бесплатно, красиво. Я настроил субагентов «с инструментами» и — они работали. Отчёты приходили, команды вроде выполнялись, всё выглядело штатно.

А потом я полез разбираться с конфигами и увидел в логах нечто, от чего пришлось перечитать дважды. Все сессии, где субагент «работал с инструментами», шли не на локальную gemma, а на облачную модель. provider был другой. Локальная модель стояла в конфиге, честно грела GPU, но инструменты через неё не проходили.

Причина оказалась до неприличия проста. Я сделал прямой запрос к Ollama с просьбой вызвать функцию — и получил в ответ короткое и честное: «does not support tools». У gemma3 в связке с ollama версии 0.32.5 в списке возможностей значится только генерация текста и картинки (completion и vision). Про tools там пусто. А мой фреймворк, получив «не умею», не упал и не ругнулся — он тихо переключился на запасную модель. Через fallback-цепочку, которую я сам же и построил для отказоустойчивости.

Получился идеальный обман: снаружи всё выглядело как «локальный агент работает», а внутри всю работу тащило облако, пока я гордился своим GPU и нулём рублей за токен. Локальный субагент с инструментами был фейком.

Дальше было не веселее

Окей, подумал я, возьму модель поменьше — gemma3:4b. Она быстрая, 23 с лишним токена в секунду, и на простых exec-задачах (пинг, curl) вела себя вполне прилично. Настроил на неё субагента — и получил не тихий fallback, а честный FailoverError. Модель просто не поднималась в этом режиме. Без «извините», без объяснений — бабах.

Тогда я решил зайти с другой стороны и попробовать классику жанра — 8B-модели, которые по бумагам tools поддерживают: hermes3:8b и llama3.1:8b. С ними не было ни тихого fallback, ни FailoverError. Было хуже — мусор. В агентском режиме, где надо держать инструкцию, вызвать пару инструментов и собрать из результата связный отчёт, эти модели рассыпались: инструкции терялись на полпути, вместо структурированного вызова шёл свободный текст, а вместо отчёта — каша. Я честно почистил их с диска без сожалений.

Итог по локальному железу получился такой: 12b — не умеет в tools на этой версии рантайма, 4b — падает, 8b — «умеет», но результат непригоден. Красиво, да?

Что реально умеет в tools

Чтобы не оставить вас с ощущением, что «локальное не работает вообще», — вот честная картинка, кто у меня реально тянет инструменты.

Облачные DeepSeek Flash и Pro. Основные рабочие лошадки. Flash — быстрая и дешёвая, для рутины; Pro — для сложных разборов. Обе честно держат формат вызова инструментов, и именно на них сейчас крутится вся агентская работа. Цена — деньги и то, что текст уходит на чужие серверы.

Локальный qwen3:30b у местного провайдера. Вот это для меня было открытием. Модель крутится у нашего провайдера по квоте, то есть фактически бесплатно в пределах лимита, и при этом заявляет связку tools + thinking. То есть умеет и вызывать инструменты, и размышлять перед ответом. Формально это не «мой GPU», но и не дорогое облако — а инструменты работают.

qwen3-coder и deepseek-r1:14b. Ещё две модели у того же провайдера с поддержкой tools. deepseek-r1:14b — из семейства «рассуждающих», у неё tools + thinking, то есть подходит для задач, где надо подумать, прежде чем дёргать инструмент. qwen3-coder — для кода.

Заметьте закономерность: всё, что реально умеет в инструменты, — это либо облако, либо модели заметно крупнее 8B. Маленькие локальные 4B–8B на моём железе в эту категорию не попали вообще.

Как проверить до того, как строить

Главный урок, который я вынес: проверяй поддержку tools до того, как навешиваешь на модель агента. Не после недели работы, не по логам через месяц, а до. Это занимает минуты и экономит дни.

Шаг первый — смотри capabilities. У каждой модели есть заявленный список возможностей. В Ollama это прямо пишется: completion, vision, tools — смотрите, что там есть. Если в списке нет tools, дальше можно не читать: чуда не случится. У меня именно тут была дыра — я просто не смотрел, а фреймворк не стал меня спасать.

Шаг второй — пробный вызов. Не полагайтесь на заявленное. Сделайте один честный запрос: «вот тебе функция, вызови её с такими-то аргументами». Ответ «does not support tools» — это самый ценный ответ из возможных, потому что он приходит сразу, а не через месяц молчаливого fallback. Ответ в свободном тексте вместо структурированного блока — тоже сигнал: модель «вроде бы» поняла, но формат не держит, и на длинной дистанции развалится.

Шаг третий — проверьте, куда реально уехал запрос. Даже если всё работает, загляните в логи провайдера/сессии. Если ваш «локальный агент» на поверку обслуживается облаком через скрытый fallback — лучше узнать об этом от логов, чем от счёта за API.

Вывод: локальное плюс агент — не всегда экономия

Вокруг self-hosting витает романтика: «своё железо, ноль рублей, всё под контролем». В контексте агентов эта формула работает с большой оговоркой. Локальная модель и агент с инструментами — это не синонимы экономии, а два разных требования, которые могут просто не пересечься на вашем железе.

Маленькая модель замечательно подойдёт для приватной болталки, быстрых простых ответов, локальной обработки, куда не хочется пускать облако. Но как только вы хотите, чтобы она вызывала инструменты и собирала из результатов связный отчёт, — вы требуете от неё конкретного формата ответа и способности держать контекст. И тут 4B–8B на моём железе дружно сдались: одна падает, вторая несёт мусор, а «умеющая» — на поверку отдаёт работу в облако.

Поэтому моя честная формула такая. Tools проверяй заранее: capabilities, пробный вызов, логи. Если локальная модель не тянет — не стесняйся выносить агентскую работу туда, где она реально работает, а локальному GPU оставляй то, что он делает хорошо. Экономия — это не «всё на своём железе любой ценой», а понимание, за что ты платишь и за что нет. И да, если ваш агент «работает с инструментами» на маленькой локальной модели — первым делом проверьте, не тащит ли его на самом деле кто-то другой.

#llm #openclaw