Вызовы инструментов LLM — не просто вопрос о том, назвала ли модель нужную функцию. Важно проверить весь переход: выбрала ли она подходящий инструмент, передала ли допустимые аргументы и остановилась ли, когда действие не требуется. Иначе корректный на вид ответ может запустить неверный запрос к API.
Надёжный тест не должен зависеть от того, насколько убедительно модель объясняет своё решение. Проверяйте структурированный результат вызова и отдельно — поведение приложения после него. Ниже — практический способ собрать такой набор проверок для API с функциями, поиска или внутренних операций.
Что считать корректным вызовом инструмента LLM
Разделите результат на несколько проверяемых решений:
Выбор: нужная функция выбрана или модель правильно ответила без неё.
Аргументы: значения соответствуют запросу и схеме — например, дата не подменена, а идентификатор не выдуман.
3. Количество действий: модель не вызывает функцию повторно без причины и не запускает несколько несовместимых операций.
4. Обработка результата: после ответа инструмента модель использует полученные данные, а не выдаёт их за собственные предположения.
5. Границы: приложение проверяет разрешения и корректность параметров до выполнения действия.
Эти пункты нельзя свести к одной метрике. Например, правильный выбор функции при неверном аргументе всё равно может привести к ошибке. А валидный JSON ещё не означает, что действие уместно.
В терминологии API OpenAI это обычно называется function calling или tool calling; Anthropic описывает tool use, а Gemini API — function calling. Конкретные форматы отличаются, поэтому тесты должны опираться на ответ выбранного провайдера и на контракт вашего приложения, а не на универсальное представление о «вызове».
Составьте набор сценариев до запуска модели
Начните не с большого корпуса, а с небольшого набора реальных задач. Для каждого сценария запишите вход, ожидаемое решение, допустимые аргументы и запрещённые побочные эффекты.
Например, у приложения есть инструменты `search_orders` и `cancel_order`:
| Запрос | Ожидаемое поведение | Что проверять |
|---|---|---|
| «Найди заказ 4182» | Вызвать `search_orders` | Передан именно номер 4182 |
| «Отмени заказ 4182» | Вызвать `cancel_order` | Передан верный ID; отмена разрешена приложением |
| «Какие способы доставки доступны?» | Ответить без этих инструментов | Никаких вызовов поиска или отмены |
| «Отмени мой последний заказ», когда заказов несколько | Запросить уточнение либо выполнить поиск по правилам продукта | Не выбирать случайный заказ |
Сценарии должны включать нормальные формулировки, неполные запросы, неоднозначность, лишние данные и случаи, когда инструмент не нужен. Добавьте и отрицательные примеры: запросить действие без обязательного параметра, передать неверный формат ID, попросить отменить чужой заказ.
Не записывайте в ожидаемый результат дословное объяснение модели. Фиксируйте поведение: имя инструмента, аргументы, отсутствие вызова или необходимость уточнения. Это позволит менять формулировки, не превращая тест в сравнение строк.
Проверьте схему и смысл аргументов отдельно
Схема помогает отсеять неверный тип или пропущенное обязательное поле. В документации OpenAI параметр `strict` для function calling включает режим строгого соответствия заданной JSON Schema с оговорёнными ограничениями формата. У Anthropic и Google также есть описания структурированных параметров инструментов. Возможности и точные правила различаются между API, поэтому сверяйтесь с документацией используемой модели.
Но схема не проверяет смысл. Значение `order_id: 4182` может быть допустимым числом и при этом относиться не к тому заказу. Нужны два слоя проверок:
- Структурный: JSON разбирается, обязательные поля присутствуют, типы и допустимые значения соблюдены.
- Семантический: параметры соответствуют запросу, контексту и бизнес-правилам.
Для практического теста сравнивайте значения с ожидаемым результатом, а не с порядком ключей в JSON. Для идентификаторов проверяйте точное совпадение; для дат — нормализованный формат; для свободного текста — заранее определённые ограничения или ручную проверку выборки.
Особенно опасны параметры, которые меняют состояние: отправка письма, удаление файла, отмена заказа, перевод средств. Их нельзя считать безопасными только потому, что модель сформировала валидные аргументы. Серверная часть должна независимо проверять права пользователя, объект операции и необходимость подтверждения.
Измеряйте ошибки по типам, а не одной долей успеха
Для каждого тестового сценария отметьте как минимум четыре результата:
- правильный ли выбран инструмент;
- корректны ли его аргументы;
- пропущен ли вызов там, где он не нужен;
- не произошло ли запрещённого действия.
Посчитайте точность выбора инструмента среди сценариев, где он необходим, и долю лишних вызовов среди сценариев, где действие не требуется. Ошибки аргументов учитывайте отдельно: объединённый «процент успешных вызовов» скрывает причину сбоя.
Для чувствительных операций полезно вести отдельную метрику опасных ошибок — например, неверный объект или вызов без проверки полномочий. Средняя точность не должна компенсировать хотя бы один такой провал: политика приложения может требовать нулевого числа опасных действий в тестовом наборе.
Добавьте повторные запуски. Ответ модели может зависеть от версии, настроек генерации, текста описания функции и состава доступных инструментов. Проведите один и тот же тест несколько раз с зафиксированными параметрами; сравнивайте распределение результатов, а не только лучший запуск. Сохраняйте версию модели, параметры запроса, определения инструментов и дату теста — иначе регрессии будет трудно воспроизвести.
Запускайте модель без реальных побочных эффектов
На этапе оценки не подключайте тесты к рабочим системам. Подмените инструменты заглушками, которые записывают имя функции и аргументы, а затем возвращают заранее заданный ответ. Так можно проверить решение модели, не отправляя письма и не изменяя реальные записи.
Упрощённая логика проверки может выглядеть так:
python
def run_case(client, case, tools):
response = client.responses.create(
model=case.model,
input=case.input,
tools=tools,
)
calls = extract_tool_calls(response)
assert len(calls) == case.expected_call_count
if case.expected_tool is None:
assert calls == []
return
call = calls[0]
assert call.name == case.expected_tool
args = parse_arguments(call.arguments)
validate_schema(args, case.schema)
validate_semantics(args, case.expected_args)
`extract_tool_calls`, `parse_arguments` и `validate_schema` здесь зависят от SDK и формата ответа конкретного провайдера. Это не готовый универсальный адаптер: реализацию нужно сверить с актуальной документацией API.
Проверяйте также полный цикл. Заглушка должна вернуть результат, после чего модель получает его и формирует ответ пользователю. В этом шаге обнаруживаются другие ошибки: модель игнорирует результат инструмента, неверно пересказывает его или повторяет действие бесконечно.
Проверьте отказ, уточнение и ошибки инструмента
Хороший тест охватывает не только успешный вызов. Подготовьте ответы инструмента с пустым результатом, тайм-аутом, ошибкой доступа и некорректными данными. Ожидаемое поведение задаёт продукт: сообщить, что данных нет; запросить уточнение; безопасно остановиться; предложить повторить попытку.
Не просите модель самостоятельно решать, можно ли игнорировать отказ в доступе. Это правило должно обеспечиваться приложением. Аналогично, после ошибки нельзя автоматически повторять операцию с побочным эффектом, если повтор способен создать дубликат.
Для неоднозначного запроса задайте проверяемый критерий: модель должна запросить недостающий параметр или вызвать безопасный инструмент для получения контекста. Не оценивайте такой случай по «вежливости» ответа — проверяйте, что она не придумала значение и не совершила действие.
Сравнивайте версии модели и описания функций
Когда меняете модель, системные инструкции или описание инструмента, повторно запускайте один и тот же набор тестов. Отдельно сравнивайте варианты описания: краткое название функции, ясное назначение, обязательные параметры и условия, при которых её нельзя вызывать.
Меняйте по одному фактору за раз. Если одновременно заменить модель, схему и инструкции, улучшение или регрессию нельзя будет связать с конкретным изменением. Храните тестовый набор и результаты рядом с кодом, чтобы запускать его при обновлении интеграции. Для Python-проектов это можно встроить в обычный набор тестов с фикстурами и проверками; сам pytest не оценивает качество модели, но помогает воспроизводимо выполнять проверки.
Набор всё равно не доказывает, что модель безопасна во всех формулировках. Он показывает поведение на выбранных сценариях. После изменений в API, схеме или бизнес-логике добавляйте примеры из новых сбоев, а критические действия дополнительно защищайте ограничениями на стороне сервера.
Что проверить перед интеграцией
Перед подключением инструмента к реальным данным убедитесь, что тестовый набор включает успешный вызов, отказ от ненужного действия, неоднозначный запрос и ошибку инструмента. Проверьте аргументы не только по схеме, но и по смыслу; прогоните повторные запуски с зафиксированными настройками; оставьте реальные побочные эффекты за заглушками.
Затем отдельно проверьте серверную авторизацию и возможность безопасного повтора. Результаты тестов относятся к конкретной версии модели, описаниям функций и настройкам API — после любого из этих изменений оценки нужно обновить.




