Почему модель выбирает не тот инструмент: имена и схемы
Разбираем, почему модель выбирает не тот инструмент: как имена функций, порядок описаний и громоздкие схемы ломают вызов инструментов и как это исправить.

В чем проблема на самом деле
Модель редко "понимает", какой инструмент вы имели в виду, так, как это понимает команда. Она ищет самое близкое совпадение между запросом пользователя и тем, как вы оформили функцию: ее именем, первыми строками описания и схемой аргументов. Поэтому ошибка часто рождается не в самой модели, а в контракте вызова.
Когда разработчик спрашивает, почему модель выбирает не тот инструмент, ответ обычно прячется в тексте tools. Для модели это и есть карта местности. Если карта нарисована небрежно, она уверенно идет не туда.
Сильнее всего на выбор влияют три вещи: имя функции, первые строки описания и объем схемы. Имя работает как ярлык. Если у вас есть get_user_info и get_user_payment_info, модель легко цепляется за слово user и берет первую функцию даже там, где нужен платежный статус. Она не читает контракт как юрист. Она сопоставляет смысл по близости слов.
С описанием то же самое. Если одна функция начинается с фразы "получить статус заказа", а другая с длинного текста про роли, ограничения и внутренние правила, модель чаще выбирает первую. Просто потому, что полезный сигнал в начале сильнее шума ниже.
Схема аргументов тоже сильно влияет на выбор. Лишние поля, редкие параметры, длинные enum-списки и условия вроде "заполняется только если..." размывают назначение функции. В итоге простая задача выглядит как сложный и сомнительный вызов. Тогда модель часто уходит в соседний инструмент с более короткой и понятной схемой.
Из-за этого ошибка кажется случайной. Сегодня модель вызвала одну функцию, завтра другую, хотя вопрос пользователя почти не изменился. Но случайности тут мало. Если посмотреть на текст контракта, обычно видно, за какой именно крючок зацепилась модель.
Хорошая новость простая: это можно исправить без магии. Часто хватает переименовать функцию, сократить первые строки описания и убрать поля, которые не нужны почти никогда. После этого вызов инструментов в LLM становится заметно стабильнее даже без смены модели.
На что модель смотрит перед вызовом
Перед вызовом инструмента модель не воспринимает набор функций как аккуратную документацию. Она ищет самое близкое совпадение между текстом запроса и тем, как вы назвали функцию, описали ее и оформили аргументы.
Обычно порядок такой: сначала имя функции, потом короткое описание, затем названия и типы аргументов, а уже после этого примеры и детали внутри описания.
Имя функции работает первым фильтром. Если у вас есть get_user_info и get_user_orders, а пользователь пишет "покажи заказы", слово orders почти всегда подтолкнет модель к нужному выбору. Но если функции названы расплывчато, вроде process_data и handle_request, модель начинает угадывать.
Короткое описание идет сразу после имени. Оно должно быстро снять двусмысленность. Фраза "получает активные заказы пользователя" помогает намного лучше, чем длинный абзац с общими правилами. Модель легче цепляется за конкретные слова, чем за запреты в духе "не использовать в некоторых случаях".
Дальше она сверяет аргументы с запросом. Если человек пишет "найди клиента по ИНН", функция с аргументом inn выглядит убедительнее, чем функция с нейтральным query. Для модели это не мелочь, а прямой сигнал о назначении инструмента.
Примеры внутри описания тоже меняют приоритет. Один короткий пример вроде "используй, когда пользователь просит проверить баланс по номеру счета" часто полезнее, чем десять строк правил. В OpenAI-совместимых интеграциях это видно особенно хорошо: модель опирается на текст tools, а не на замысел в голове команды.
Если функция выбрана неверно, сначала проверьте не промпт, а упаковку инструмента. В большинстве случаев ошибка живет именно там.
Почему имя функции часто решает исход
Когда модель выбирает инструмент, она сначала цепляется за самый заметный сигнал. Чаще всего это имя функции. Если имя ясное, выбор точнее. Если имя мутное, модель начинает блуждать между похожими вариантами.
Поэтому ответ на вопрос "почему модель выбирает не тот инструмент" часто звучит скучно, но честно: инструмент назвали слишком широко. Функция billing_action_v2 выглядит как контейнер для всего подряд. Модель легко отправит туда возврат, смену тарифа, проверку счета и даже то, что не относится к биллингу напрямую. А имя refund_create почти не оставляет лишних трактовок. Оно описывает одно действие.
Лучше всего работают названия, где есть глагол и объект. Такое имя похоже на короткую команду, а не на внутренний ярлык команды разработки.
Чаще всего мешают общие слова вроде action, handler, manager, process, внутренние сокращения и версии, которые ничего не говорят модели. Если вы назвали функцию refund_or_cancel, вы уже смешали два действия в одной точке входа. Дальше путаница почти неизбежна.
Аббревиатуры сбивают особенно часто. Человек в команде знает, что ar_txn_adj означает корректировку дебиторской транзакции. Модель такой договоренности не знает. Она видит шумный маркер и ищет сходство наугад. Потом в логах это выглядит как странная ошибка выбора, хотя проблема была в названии.
Есть простой тест. Покажите имя функции человеку, который не знает ваш проект. Если он за несколько секунд не скажет, что именно она делает, модель тоже будет путаться.
В продакшене это заметно сильнее, чем кажется. Когда через один API проходит десяток или несколько десятков инструментов, модель видит их почти как меню. И в таком меню побеждают самые ясные названия.
Как порядок описаний меняет выбор
Даже хорошие имена не спасают, если набор инструментов организован неудачно. Модель читает список не как человек на ревью. Она не строит полную карту возможностей, а цепляется за первые подходящие совпадения. Поэтому верхние функции нередко перехватывают запросы просто потому, что встретились раньше.
Представьте такой набор:
find_model- подобрать модель под задачуfind_low_latency_model- подобрать модель с низкой задержкойfind_model_by_152fz- подобрать модель с хранением данных в РФ
Если первой стоит самая общая функция, она начнет забирать запросы вроде "нужна быстрая модель" или "нужна модель для персональных данных". Модель видит знакомые слова "подобрать модель" и срабатывает слишком рано. Узкие сценарии ниже в списке просто не получают нормального шанса.
Старые версии рядом с новыми портят картину еще сильнее. Когда рядом лежат search_customer, search_customer_v2 и search_customer_new, для модели это три почти одинаковых пути с мелкими отличиями. Шум растет, а выбор начинает плавать от запроса к запросу.
На практике помогает простой порядок: сначала ставьте частые и четко очерченные сценарии, потом более общие функции. Архивные и переходные версии лучше убирать из публичного набора совсем, а не держать рядом "на всякий случай".
Если вы строите вызов инструментов в LLM для продакшена, проверяйте порядок так же строго, как имя функции и JSON-схему. Иногда перестановка нескольких описаний дает больше пользы, чем неделя правок в промпте.
Что ломают большие схемы
Когда у функции двадцать параметров, модель легко цепляется за знакомые слова и теряет саму задачу. Она не рассуждает как разработчик. Она сопоставляет запрос пользователя с именем, описанием и формой аргументов. Чем тяжелее схема, тем проще уйти не туда.
Первая проблема в том, что большая схема прячет смысл функции. Если описание обещает одно действие, а ниже идут десятки полей для фильтров, режимов, флагов и вложенных настроек, модель видит не "получить курс валют", а сплошной шум. Тогда она часто выбирает более короткую и понятную функцию, даже если та подходит хуже.
Вторая проблема в том, что пользователь почти никогда не формулирует полный набор аргументов. Он пишет: "покажи неоплаченные счета за март" или "создай черновик письма клиенту". Если схема требует сразу указать двенадцать полей, модель начинает угадывать. Она либо заполняет аргументы слабо и неточно, либо уходит в соседний инструмент, где вход проще.
Особенно мешают вложенные объекты. Когда внутри options лежат filters, внутри них rules, а внутри них еще массив условий, границы функции расплываются. Для модели это уже не один понятный вызов, а маленький конструктор.
На практике громоздкая функция часто скрывает две разные задачи. Одна читает данные. Другая что-то меняет в системе. Если держать их вместе, модель путает чтение и действие.
Обычно помогает простое разделение. Вынесите поиск и изменение в разные функции. Редкие параметры перенесите в отдельный инструмент. Обязательные поля сократите до минимума. А самые важные аргументы поднимите на верхний уровень схемы.
Хороший ориентир такой: если человек не может за десять секунд объяснить, что делает функция и какие три-четыре поля нужны почти всегда, схема уже слишком большая. Ошибки схемы JSON редко выглядят как "сломанный формат". Чаще это перегрузка, из-за которой модель теряет намерение пользователя.
Как привести набор инструментов в порядок
Беспорядок в наборе инструментов редко выглядит как большая проблема. Но именно он часто объясняет, почему модель выбирает не тот инструмент. Если две функции делают почти одно и то же, а их описания расплывчаты, модель цепляется за случайные сигналы: первое слово в имени, лишний аргумент, место в списке.
Начните с простого списка всех инструментов. Напротив каждого напишите не общую формулировку вроде "работа с клиентом", а конкретный первый шаг в реальном запросе. После этого дубли обычно становятся видны сразу. Одна функция ищет клиента, другая почти так же ищет клиента, но через другой источник. Одна создает заявку, другая делает почти то же самое, только с лишними полями.
Дальше нужна обычная чистка. У каждой функции должно быть одно действие в имени. Описание лучше свести к одной прямой фразе. Аргументы, без которых первый вызов не ломается, стоит убрать из обязательных. Похожие функции нужно развести по смыслу, а не по тонким внутренним отличиям.
Проверять это лучше на живых запросах, а не на придуманных примерах. Возьмите 20-30 реальных фраз пользователей, прогоните их через старый и новый набор инструментов и сравните результат. Смотрите не только на точность, но и на колебания: стала ли модель увереннее, исчезли ли странные вызовы, сократилось ли число пустых аргументов.
В командах это часто дает заметный эффект без смены модели. Например, после переименования get_customer_info_full в find_customer_by_phone и сокращения схемы до двух обязательных полей ошибки выбора падают уже на первом прогоне.
Если вы работаете через RU LLM, такие сравнения удобно делать на одном OpenAI-совместимом эндпоинте и смотреть аудит-трейлы запросов. Это помогает быстро увидеть, где модель перестала метаться между похожими функциями.
Простой пример с двумя похожими функциями
Пользователь пишет: "Верните деньги за подписку". Человек обычно сразу понимает, что речь о возврате. Модель видит это иначе: она сравнивает доступные функции по имени, порядку и форме аргументов.
Представьте две функции: cancel_subscription и create_refund. Если cancel_subscription стоит первой и описана шире, модель часто уходит туда. Особенно если описание звучит размыто: "работает с подпиской", "обрабатывает запросы по подписке", "останавливает обслуживание". Для модели это выглядит как безопасный общий выбор.
Проблему усиливают аргументы. Допустим, у cancel_subscription всего два поля: user_id и reason. А у create_refund их семь: payment_id, amount, currency, refund_type, approval_code, comment, source. Тогда модель нередко берет отмену подписки просто потому, что этот вызов проще собрать из короткой фразы пользователя.
Это и есть частый ответ на вопрос, почему модель выбирает не тот инструмент. Она не ошибается "по-человечески". Она берет то, что лучше совпало по словам и проще прошло по схеме.
Исправление обычно простое. У cancel_subscription стоит явно написать: "Только отменяет будущие списания. Не возвращает деньги за уже оплаченный период". У create_refund - "Возвращает деньги за уже проведенный платеж или подписку. Используйте этот вызов, когда пользователь просит вернуть оплату". После этого полезно убрать лишние обязательные поля из create_refund на первом шаге. Если данных не хватает, модель лучше пусть задаст уточняющий вопрос.
После такой правки выбор быстро выравнивается. Запрос "Верните деньги за подписку" начинает вести в create_refund, а не в cancel_subscription.
Где команды ошибаются чаще всего
Чаще всего команда винит модель, хотя проблема сидит в самих инструментах. Если интерфейс шумный, двусмысленный или перегруженный, модель начинает угадывать. Отсюда и ощущение, что она ведет себя странно.
Первая типичная ошибка - копировать почти одно и то же описание в несколько функций. Для человека разница может быть очевидна по контексту проекта, а для модели нет. Если у двух функций описание вроде "работа с данными клиента", она начнет цепляться за случайные сигналы.
Вторая ошибка - прятать смысл функции за внутренними именами. Когда в название добавляют версию, команду, микросервис и служебный код вроде crm_customer_fetch_v2_beta_team7, полезная часть теряется. Модель видит длинную строку токенов и реагирует на самый понятный кусок, даже если он не главный.
Третья ошибка - складывать в схему все поля "на будущее". Так появляется огромный JSON с десятками необязательных параметров, флагов и редких веток. В реальной работе это мешает: модель либо не решается вызвать инструмент, либо заполняет лишнее, либо пропускает нужное поле среди мусора.
Еще одна частая попытка - чинить все промптом. Команда пишет длинную инструкцию: какую функцию брать, когда ее брать и что не делать. Иногда это помогает на тестах. Но если имя функции мутное, описание повторяет соседнее, а схема перегружена, промпт лишь маскирует проблему.
Простой пример: есть get_customer_info_v4_ops и update_customer_info_v4_ops. У обеих описание "работа с карточкой клиента". Пользователь спрашивает: "Какой у меня тариф?" Модель вполне может выбрать обновление, потому что слово customer_info совпало, а различие в глаголе и описании оказалось слишком слабым.
Даже привычный OpenAI-совместимый стек не исправит плохой контракт инструмента. Сначала нужно убрать лишнее из имени, сузить описание и оставить в схеме только то, что нужно для одного понятного действия.
Быстрый чек перед релизом
Перед выкладкой полезно один раз пройтись по набору инструментов холодным взглядом. Большая часть ошибок видна еще до тестов.
Проверьте имя каждой функции. Хорошее имя обещает один понятный результат. get_invoice_status почти не оставляет места для догадок, а invoice_handler звучит так широко, что модель может притянуть его к любому запросу про счет.
Сожмите описание до одного-двух предложений. В тексте должно быть ясно, когда функцию вызывать и когда не вызывать. Если описание похоже на маленькую документацию с оговорками, модель начнет цепляться за случайные слова.
Посмотрите на аргументы глазами модели. Они должны отвечать на вопрос "что нужно прямо сейчас", а не "что когда-нибудь может пригодиться". Если схема просит десять полей, половина из которых необязательна, модель чаще угадывает, чем делает осознанный выбор.
Найдите пары функций, которые спорят за один и тот же запрос. Если одна функция "создает заявку", а другая "регистрирует обращение", для модели это почти одно и то же. Такие вещи лучше развести по смыслу или объединить.
И обязательно прогоните хотя бы десяток частых фраз пользователей. Нужны не идеальные тестовые формулировки, а живые, короткие и небрежные. Именно на них видно, где модель путает похожие инструменты и где схема просит лишнее.
Что сделать дальше в своей системе
Когда уже видно, почему модель выбирает не тот инструмент, не нужно сразу переписывать весь набор функций. Гораздо полезнее разобрать последние 50-100 ошибочных вызовов и собрать их в группы. Обычно картина быстро проясняется: модель путает две похожие функции, уходит в слишком общий инструмент или спотыкается о перегруженную схему.
Смотрите не только на одну общую метрику, а на долю неверных выборов по сценариям. Отдельно посчитайте поиск данных, создание сущности, обновление записи, работу с документами. Одна цифра по всей системе часто скрывает реальную проблему.
Минимальный цикл проверки простой. Возьмите один частый сценарий и один тип ошибки. Поменяйте только имя функции, или только описание, или только схему. Затем прогоните тот же набор запросов еще раз и сравните выбранный инструмент, аргументы и долю ошибок. Такой подход кажется медленным, но он экономит время. Если команда меняет все сразу, потом никто не понимает, что именно помогло.
Логи должны показывать не только сам факт вызова. Сохраняйте исходный запрос, выбранную функцию, аргументы, ошибки валидации и то, что система сделала после сбоя. Тогда становится видно, где именно модель оступилась: не поняла намерение, зацепилась за слово в описании или ушла в более простую функцию, потому что у второй схема слишком тяжелая.
Если вы гоняете несколько моделей через один OpenAI-совместимый эндпоинт, полезно проверять один и тот же набор тестов в одинаковых условиях. В RU LLM это можно делать без смены SDK, кода и промптов: достаточно переключить base_url на api.rullm.com и сравнить поведение моделей на одном наборе инструментов. Такой тест быстро показывает, где проблема в контракте функции, а где различается поведение самой модели.
Дальше все довольно приземленно: выберите три самых дорогих сценария по числу ошибок, зафиксируйте тестовый набор и пересматривайте изменения раз в неделю. Через несколько итераций обычно становится ясно, какие функции нужно переименовать, какие описания сократить, а какие инструменты вообще лучше убрать из публичного списка.
Часто задаваемые вопросы
Почему модель выбирает общий инструмент вместо точного?
Обычно модель цепляется за знакомые слова в имени и первых строках описания. Если общий инструмент стоит выше в списке и выглядит проще по схеме, она уходит туда раньше, чем успевает разобрать более узкий вариант.
Что сначала проверять: промпт или описание tools?
Сначала правьте сами инструменты. В большинстве случаев проблема сидит в имени функции, начале описания или перегруженной схеме, а не в системном промпте.
Как лучше называть функции?
Давайте функции имя с одним действием и понятным объектом, например find_customer_by_phone или create_refund. Общие слова вроде handler, process и внутренние сокращения лучше убрать.
Первые строки описания правда так сильно влияют?
Да, и часто сильнее, чем длинный текст ниже. Первые строки должны быстро отвечать на вопрос, когда вызывать функцию и для чего она нужна.
Почему длинная JSON-схема ломает выбор?
Большая схема размывает смысл функции. Когда аргументов слишком много, модель либо угадывает поля, либо выбирает соседний инструмент, у которого вход проще.
Нужно ли оставлять рядом v1, v2 и new версии функций?
Нет, если ими пользуется только история проекта, а не продакшен. Старые версии рядом с новыми создают шум, и модель начинает метаться между почти одинаковыми путями.
Что делать, если две функции выглядят почти одинаково?
Разведите их по смыслу так, чтобы каждая отвечала за один сценарий. Если разницу трудно объяснить в одном предложении, лучше объединить функции или переписать названия и описания.
Когда одну функцию лучше разбить на две?
Разделяйте функцию, когда она и читает данные, и что-то меняет в системе, или когда редкие параметры тянут за собой половину схемы. Модели проще выбрать один понятный вызов, чем разбираться в конструкторе на двадцать полей.
Как быстро проверить, что правки помогли?
Возьмите 20–30 живых запросов и прогоните их до и после правок. Смотрите не только на точность выбора, но и на пустые аргументы, лишние вызовы и колебания между похожими функциями.
Смена модели решит проблему сама по себе?
Иногда помогает, но плохой контракт она не чинит. Если имя мутное, описания похожи, а схема тяжёлая, новая модель тоже будет ошибаться, просто по-другому.