Перейти к содержимому
5 мин чтения

Почему список инструментов для модели нельзя делать общим?

Список инструментов для модели нужно подбирать по качеству tool calling, контексту и риску действий, а не передавать весь API-каталог.

Почему список инструментов для модели нельзя делать общим?

Модели не нужен полный каталог ваших API-операций в каждом запросе. Ей нужен небольшой набор действий, которые действительно допустимы в текущем состоянии диалога. Когда команда передает одинаковый список из двухсот функций и большой reasoning-модели, и дешевой компактной модели, она проверяет не возможности модели, а терпимость системы к плохому дизайну.

Список инструментов влияет сразу на три вещи: на вероятность верного выбора, на место в контексте и на безопасность исполнения. Большая модель иногда переживает перегруженный каталог. Небольшая модель чаще выбирает функцию по случайному совпадению слов в описании, пропускает обязательный параметр или вызывает действие, которое вообще не требовалось. Это не повод отказаться от малых моделей. Это повод перестать считать набор функций неизменной частью системного промпта.

Один глобальный каталог ломает выбор действия

Глобальный каталог API удобен только разработчику, который хочет один раз сгенерировать JSON Schema и больше к нему не возвращаться. Для модели он смешивает действия разных доменов, ролей и стадий процесса.

Представьте помощника поддержки интернет-магазина. В общем реестре лежат get_order, cancel_order, refund_payment, create_return, change_delivery_address, apply_discount, block_account, reset_password и еще десятки функций. Пользователь пишет: «Курьер не приехал, верните деньги за заказ 8241». Если модель видит операции возврата, отмены, претензии к доставке и ручной скидки одновременно, ей нужно не просто извлечь номер заказа. Ей нужно правильно восстановить бизнес-процесс.

Функция cancel_order может казаться подходящей по слову «верните». Но заказ уже передан в доставку, отменять его поздно. refund_payment может быть недоступна до проверки статуса и причины. Верная последовательность обычно начинается с чтения заказа, затем проверяет факт доставки, затем создает обращение или предлагает возврат в рамках правил. Если все эти переходы отданы модели как набор равноправных кнопок, ошибка становится ожидаемым результатом.

Проблема усиливается, когда инструменты похожи по названию:

  • search_customer ищет учетную запись по контактам;
  • get_customer читает запись по внутреннему идентификатору;
  • get_customer_orders возвращает заказы;
  • get_order читает один заказ;
  • update_customer меняет профиль.

Человек видит разницу после короткого просмотра документации. Модель видит несколько семантически близких описаний, которые конкурируют за один вызов. Чем слабее модель и чем короче пользовательский запрос, тем выше цена этой конкуренции.

Нельзя лечить это фразой в промпте вроде «внимательно выбирай правильную функцию». Модель уже пытается это сделать. Приложение должно уменьшить число неправильных вариантов до того, как запрос попадет в модель.

Размер контекста не равен способности различать функции

Большое контекстное окно позволяет поместить в запрос больше схем, документации и истории. Оно не дает модели отдельный механизм поиска по каталогу и не делает похожие операции менее похожими.

Официальная документация Gemini прямо советует держать активный набор примерно в пределах 10-20 релевантных функций и отбирать их динамически, если общий каталог велик. Это не универсальный лимит и не магическое число. Это полезное признание того, что качество выбора ухудшается, когда модель получает слишком много конкурирующих вариантов. В тех же документах описан параметр allowed_function_names, которым можно ограничить набор доступных вызовов.

Есть еще одна практическая причина не забивать контекст схемами. Описание инструмента состоит не только из имени. Оно включает текст назначения, JSON Schema, свойства, перечисления, обязательные поля, примеры и иногда правила ошибок. Сотня тщательно описанных функций легко вытесняет из запроса историю пользователя, результаты прошлых вызовов и документы, по которым модель должна принять решение.

Это особенно заметно в длинных агентных цепочках. После каждого вызова в историю добавляются имя операции, аргументы, результат, идентификатор вызова и служебные данные провайдера. Документация Gemini по комбинации инструментов напоминает, что возвращаемые части вызовов становятся частью истории и учитываются в запросах. Увеличить окно контекста можно. Сделать историю бесплатной и безошибочной нельзя.

Разделяйте две задачи:

  1. Контекстное окно определяет, сколько информации вы можете передать и сохранить между шагами.
  2. Качество tool calling определяет, выберет ли модель нужную операцию, построит ли допустимые аргументы и поймет ли результат.

У модели с огромным окном может быть посредственный выбор функций. У небольшой модели с коротким окном может быть отличная точность, если вы дали ей пять четко разведенных операций. Сравнивать модели только по числу токенов в контексте для агентной задачи бессмысленно.

Активный набор должен следовать за состоянием задачи

Передавайте функции по этапам процесса, а не по организационной структуре вашего API. На первом шаге модели часто нужен поиск или чтение. На втором, после проверки фактов, ей может понадобиться создание заявки. Операция, которая реально меняет деньги или права, должна появляться только после прохождения условий, которые проверяет код приложения.

Для того же возврата заказа маршрут может выглядеть так:

  1. Пользователь сообщает проблему и номер заказа.
  2. Приложение дает модели только get_order и find_orders_by_contact, если номера нет.
  3. После чтения заказа приложение само определяет доступные переходы по статусу, стране, сроку и роли пользователя.
  4. Модель получает create_delivery_claim или prepare_refund, если эти операции допустимы.
  5. Исполнитель проверяет параметры и либо выполняет действие, либо требует подтверждение.

Это не жесткий сценарий, который убивает полезность LLM. Модель все еще извлекает данные из свободного текста, выбирает между допустимыми ветками, задает уточнения и объясняет результат. Но она больше не решает, существует ли в вашей системе право на опасный переход.

Такой подход полезен и для внутренних систем. В финансовом помощнике набор на стадии анализа может включать чтение проводок, поиск договора и расчет лимита. На стадии подготовки платежа он может получить создание черновика. Функцию отправки платежа лучше не добавлять, пока сервер не проверит сумму, полномочия и обязательные поля. Если пользователь просит «сразу оплатить», модель не должна обходить правила только потому, что видит соответствующее имя функции.

AI Router позволяет сохранить привычный OpenAI-совместимый формат запросов при смене модели, поэтому маршрутизатор активного набора стоит держать в вашем приложении, а не привязывать его к одному провайдеру. Тогда вы можете проверять одну и ту же задачу на нескольких моделях без переписывания клиентского кода.

Похожие функции нужно развести до того, как их увидит модель

Самая частая ошибка в схемах инструментов состоит не в отсутствии поля required. Команда пытается представить внутренние CRUD-методы как интерфейс для рассуждающей системы.

Например, вместо трех операций create_ticket, update_ticket, assign_ticket нередко публикуют ticket_mutation с параметром action. Это может уменьшить число функций, но часто делает схему хуже: модель должна угадать строку действия, совместимость полей и разрешенный переход. Объединяйте операции только тогда, когда у них одинаковая цель, одинаковые права и почти одинаковый жизненный цикл.

Обратная крайность тоже вредна. Не стоит публиковать set_shipping_city, set_shipping_street, set_shipping_house, set_shipping_apartment, если система может безопасно обновить адрес одним валидируемым объектом. Пользователь думает об адресе как об одном объекте. Модель тоже должна видеть одну операцию update_delivery_address с ясной схемой.

Хорошая функция отвечает на один прикладной вопрос. Ее имя описывает действие, а описание объясняет границу применения. Сравните:

{
  "name": "refund_payment",
  "description": "Return money for a paid order only after the order record confirms that a refund is allowed. Do not use to cancel an unpaid order or create a delivery claim.",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string",
        "description": "Internal order identifier returned by get_order"
      },
      "reason": {
        "type": "string",
        "enum": ["delivery_failure", "duplicate_charge", "approved_return"]
      }
    },
    "required": ["order_id", "reason"],
    "additionalProperties": false
  }
}

Здесь важна не английская формулировка. Важно, что описание запрещает два похожих, но неверных применения, а поле reason не заставляет модель изобретать внутренний код причины. Закрытые наборы значений оформляйте через enum. Документация Gemini рекомендует это же правило для параметров с ограниченным списком допустимых значений.

Не прячьте бизнес-правила только в описании инструмента. Текст помогает модели выбрать действие, но не заменяет серверную проверку. Поле additionalProperties: false не позволит передать лишний аргумент в поддерживающих реализациях, но оно не проверит, что пользователь вправе вернуть деньги именно по этому заказу.

Малые модели требуют более узкого контракта

Сравнивайте модели на одном маршруте
Меняйте модели через один OpenAI-совместимый эндпоинт, сохраняя SDK, код и промпты.

Небольшая модель может быть хорошим диспетчером для простых, частых и хорошо ограниченных задач. Она часто дешевле и быстрее крупной, но это преимущество исчезает, если вы грузите ее огромной библиотекой функций и заставляете восстанавливать скрытую логику вашего бизнеса.

Для малой модели полезны четыре ограничения. Они не заменяют тесты, но резко снижают число случайных решений.

  • Давайте только функции текущей стадии процесса.
  • Используйте короткие, различающиеся имена без внутренних сокращений.
  • Делайте обязательные параметры явными и используйте перечисления.
  • Требуйте уточнение, если для вызова не хватает идентификатора, даты, суммы или согласия пользователя.

Не заставляйте компактную модель планировать длинную цепочку из восьми вызовов в одном ответе. Разбейте исполнение на короткие ходы: модель выбирает действие, приложение выполняет его, возвращает результат, затем снова формирует активный набор. Такой цикл добавляет сетевые обращения, зато локализует ошибку. Если модель неверно классифицировала запрос, она не успеет за один ответ создать клиента, изменить договор и отправить уведомление.

Режим выбора инструмента тоже имеет значение. В OpenAI API есть режимы, где приложение запрещает инструменты, оставляет модели автоматический выбор, требует вызов или принудительно задает конкретный инструмент. Принудительный вызов полезен не как костыль для всех запросов, а после детерминированного решения вашего кода. Если сервер уже установил, что нужен только get_order, не заставляйте модель снова выбирать между десятью операциями.

Строгая схема помогает с формой аргументов, но не с намерением. Модель может идеально сформировать JSON для неправильной функции. Поэтому в оценке всегда разделяйте ошибку выбора и ошибку параметров. Иначе команда увидит высокую долю валидного JSON и ошибочно решит, что агент готов к действиям.

Каталог из сотен операций нужно искать кодом, а не вниманием модели

Не привязывайте tools к вендору
Подключайте разные модели к вашему отбору инструментов без переписывания клиентского кода.

Когда у вас сотни функций, не передавайте их все даже сильной модели. Сделайте слой отбора. Он не обязан быть сложной векторной базой и не обязан доверять LLM право расширять собственный доступ.

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

Пример контракта между маршрутизатором и вызовом модели:

{
  "conversation_state": "order_delivered_claim",
  "user_role": "support_agent",
  "allowed_tools": [
    "get_order",
    "create_delivery_claim",
    "prepare_refund"
  ],
  "blocked_tools": [
    "refund_payment",
    "cancel_order",
    "change_delivery_address"
  ]
}

Поле blocked_tools не нужно отправлять модели. Оно полезно в журнале решения и в тестах маршрутизатора. Через месяц вы сможете ответить на неприятный вопрос: функция возврата отсутствовала потому, что модель о ней не вспомнила, или потому, что приложение правильно ее запретило?

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

Если ваша платформа использует MCP, правило не меняется. MCP решает совместимость между клиентом и сервером инструментов, а не задачу допуска. Сервер может объявить сотни операций, но клиент должен выбрать, какие из них доступны в текущем разговоре. Документация Gemini для Remote MCP прямо предусматривает allowed_tools для ограничения инструментов сервера.

Оценивать нужно маршрут, а не только финальный ответ

Тест «модель вернула правильный текст» почти ничего не говорит о пригодности агента. Пользователь может получить вежливое сообщение, даже если модель выбрала неверную операцию, приложение отклонило ее, а затем модель удачно извинилась.

Соберите набор задач, где для каждого случая известны разрешенные функции, запрещенные функции, требуемые аргументы и допустимая последовательность. Добавьте намеренно неудобные примеры: неполный номер заказа, два заказа в сообщении, просьбу пользователя нарушить правило, конфликтующие даты, устаревший статус из истории и похожие названия продуктов.

Для каждого прогона сохраняйте запись такого вида:

{
  "case_id": "refund_after_failed_delivery_017",
  "active_tools": ["get_order", "create_delivery_claim", "prepare_refund"],
  "expected_first_tool": "get_order",
  "model_tool": "prepare_refund",
  "arguments_valid": true,
  "policy_allowed": false,
  "executor_result": "blocked_missing_status_check"
}

Эта запись показывает разницу между тремя событиями, которые часто смешивают в один показатель: модель создала корректный JSON, модель выбрала разумную функцию, система разрешила действие. В примере JSON валиден, но вызов преждевременен. Если считать только валидность схемы, вы пропустите опасный дефект.

Смотрите как минимум на четыре результата:

  • точность выбора первой функции;
  • точность аргументов после серверной нормализации;
  • долю лишних вызовов;
  • долю запрещенных или преждевременных действий.

Отдельно учитывайте оправданные уточняющие вопросы. Модель, которая при отсутствии идентификатора спрашивает номер заказа, лучше модели, которая уверенно придумывает идентификатор и вызывает функцию. Для рискованных доменов, включая банк, медицину и госуслуги, это различие важнее средней длины ответа.

Параллельность и автономность требуют явной границы

Отделите допуск от модели
Совмещайте собственный маршрутизатор допустимых действий с единым доступом к моделям.

Параллельные вызовы уменьшают задержку, когда операции независимы. Можно одновременно получить профиль клиента и список его заказов, если обе функции только читают данные и не зависят друг от друга. Документация OpenAI описывает настройку parallel_tool_calls, а документация Gemini поддерживает параллельные и последовательные вызовы. Поддержка API не означает, что любой набор операций можно безопасно запускать параллельно.

Не параллельте чтение и изменение одного объекта, если чтение определяет законность изменения. Не запускайте два вызова, которые тратят общий лимит. Не разрешайте модели одновременно создать возврат и применить скидку «для компенсации», пока код не проверил, совместимы ли эти действия.

Полезно разделить инструменты на три класса:

  • чтение, которое не меняет состояние;
  • подготовка, которая создает черновик или расчет;
  • исполнение, которое меняет деньги, права, данные или внешнее обязательство.

Модель может относительно свободно работать с чтением. Подготовка требует проверки контекста и идемпотентного идентификатора. Исполнение требует серверного допуска, журналирования и в ряде процессов подтверждения человека. Это не бюрократия вокруг модели. Это обычная инженерная граница между предложением действия и самим действием.

Не выбирайте модель по одному красивому примеру tool calling. Возьмите свои операции, сократите активный набор до реально допустимых функций, добавьте тесты на похожие действия и посмотрите на трассы вызовов. После этого станет видно, где вам нужна более сильная модель, а где достаточно маленькой. Чаще всего первым исправлением окажется не смена модели, а удаление из ее поля зрения девяноста процентов каталога.

Часто задаваемые вопросы

Сколько инструментов можно передавать модели одновременно?

Нет. У модели нет фиксированного безопасного числа функций, после которого она обязательно начнет ошибаться. Но рост набора повышает путаницу между похожими именами и схемами, а также съедает контекст, поэтому активный список должен зависеть от задачи и результатов ваших тестов.

Решает ли большое контекстное окно проблему сотен инструментов?

Длинный контекст позволяет передать больше описаний, но не делает выбор между похожими операциями точнее. Контекстное окно отвечает на вместимость запроса, а качество tool calling отвечает за выбор функции, аргументы и последовательность вызовов.

Нужно ли скрывать от модели часть функций?

Это нормально, если операция требует явного контроля со стороны приложения. Передавайте модели узкий набор функций, когда действие меняет деньги, права доступа, персональные данные или состояние заказа.

Может ли строгая JSON-схема гарантировать правильный вызов функции?

Модель может вернуть JSON, который проходит синтаксическую проверку, но означает неправильное действие. Например, она способна выбрать отмену заказа вместо возврата средств, если описания операций пересекаются.

Какие метрики важны для оценки tool calling?

Сначала оцените точность выбора функции, точность аргументов, лишние вызовы и долю безопасных уточняющих вопросов. Затем добавьте задержку, стоимость и долю ошибок исполнителя, потому что удачный вызов модели еще не означает удачный результат системы.

Как писать описания функций для LLM?

Используйте понятное имя действия, описание границ применения, схему параметров и явные значения enum там, где набор значений закрыт. Не пытайтесь компенсировать плохое проектирование длинным описанием на полстраницы.

Когда модели можно разрешить параллельные вызовы инструментов?

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

Что делать, если маленькая модель путает похожие функции?

Сначала сократите список до операций, нужных в конкретном состоянии диалога, и объедините очевидные дубликаты на стороне приложения. Если качество не выросло, выберите модель, которая лучше проходит ваш набор сценариев, а не ту, у которой больше контекстное окно.

Нужен ли отдельный подход для MCP-инструментов?

MCP упрощает подключение серверов инструментов, но не отменяет необходимость ограничивать доступный набор. Сервер может публиковать сотни операций, а приложению все равно стоит отбирать допустимые для текущей задачи.

Как безопасно выполнять вызовы, которые предложила модель?

Проверяйте схему, права вызывающего пользователя, допустимость перехода состояния, лимиты и идемпотентность до исполнения. Для необратимых действий добавляйте отдельное подтверждение человека или детерминированное правило приложения.