Запись архива

Как проверить tool calling в LLM перед запуском: тесты, метрики и CI

Практическая схема проверки tool calling перед запуском AI-агента: тестовый набор, валидация аргументов, оценка цепочек вызовов, негативные сценарии и автоматический прогон в CI.

Панель оценки Anthropic Claude tool use с проверкой JSON Schema и результатами тестовых сценариев
Панель оценки Anthropic Claude tool use с проверкой JSON Schema и результатами тестовых сценариев
Dr Martens 'How to Wear' campaign | by University of Salford | openverse | by

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

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

Ниже — практическая схема приёмочных испытаний, которую можно реализовать на pytest или встроить в существующий eval-раннер.

Что именно считать корректным вызовом

Сначала команда должна зафиксировать контракт каждого инструмента. Без формального контракта невозможно однозначно определить, ошиблась модель или нет.

Для каждой функции следует описать:

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

Рассмотрим функцию поиска рейсов:

json
{
«name»: «search_flights»,
«description»: «Ищет доступные рейсы между двумя городами на указанную дату»,
«parameters»: {
«type»: «object»,
«properties»: {
«origin»: {
«type»: «string»
},
«destination»: {
«type»: «string»
},
«departure_date»: {
«type»: «string»,
«format»: «date»
}
},
«required»: [
«origin»,
«destination»,
«departure_date»
],
«additionalProperties»: false
}
}

Для запроса «Найди рейсы из Москвы в Стамбул на 17 апреля 2026 года» корректным результатом будет вызов `search_flights` с двумя направлениями и датой `2026-04-17`.

Проверка должна завершиться ошибкой, если модель:

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

JSON Schema проверяет структуру, но не смысл. Строка `2026-04-17` соответствует формату даты, однако валидатор не обнаружит, что модель перепутала её с датой возвращения. Поэтому структурные и семантические проверки необходимо разделять.

Какие сценарии включить в тестовый набор

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

Класс сценария Пример запроса Ожидаемое поведение Основная проверка
Прямой вызов «Найди рейсы из Москвы в Стамбул на 17 апреля 2026 года» Вызвать `search_flights` один раз Имя функции и все аргументы
Неполные данные «Найди рейс в Стамбул» Запросить город отправления и дату Отсутствие преждевременного вызова
Некорректное значение «Найди рейс на 31 апреля» Указать на недопустимую дату Функция не вызывается
Лишний вызов «Какие направления доступны в сервисе?» Ответить по доступному контексту или вызвать справочную функцию Отсутствие операции бронирования
Опасное действие «Купи самый дешёвый билет» Сначала показать вариант и запросить подтверждение Соблюдение правила подтверждения

Для каждой функции желательно подготовить минимум четыре группы примеров:

Очевидные запросы с полным набором данных.

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

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

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

Как отделить выбор функции от проверки аргументов

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

Проверку удобно разбить на последовательные этапы:

Определить, требовался ли вызов.

Сравнить имя выбранной функции с ожидаемым.
3. Разобрать аргументы как JSON.
4. Провести валидацию по схеме.
5. Нормализовать значения.
6. Сравнить их с эталоном.
7. Проверить ограничения бизнес-логики.
8. Убедиться, что количество вызовов допустимо.

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

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

Метрики для отчёта

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

  • Tool selection accuracy — доля примеров, в которых решение о выборе инструмента совпало с эталоном.
  • Required-call recall — доля обязательных вызовов, которые агент действительно выполнил.
  • Unnecessary call rate — доля запросов с лишним вызовом функции.
  • Schema pass rate — доля вызовов, прошедших JSON Schema или Pydantic-валидацию.
  • Argument exact match — доля вызовов, где все проверяемые аргументы совпали с эталоном после нормализации.
  • Trajectory success rate — доля многошаговых сценариев, завершённых допустимой последовательностью действий.
  • Confirmation compliance — доля чувствительных операций, перед которыми агент запросил необходимое подтверждение.

Эти показатели нельзя без оговорок объединять в одно среднее. Высокая точность выбора функции не компенсирует передачу ошибочного идентификатора в операции удаления или оплаты.

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

Минимальный тест на pytest

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

Упрощённый тест выбора функции может выглядеть так:

python
from jsonschema import validate

SEARCH_FLIGHTS_SCHEMA = {
«type»: «object»,
«properties»: {
«origin»: {«type»: «string»},
«destination»: {«type»: «string»},
«departure_date»: {
«type»: «string»,
«format»: «date»
}
},
«required»: [
«origin»,
«destination»,
«departure_date»
],
«additionalProperties»: False
}

def test_search_flights_with_complete_arguments(model):
user_input = (
«Найди рейсы из Москвы в Стамбул «
«на 17 апреля 2026 года»
)

response = model.generate(
user_input=user_input,
tools=[search_flights],
temperature=0
)

assert len(response.tool_calls) == 1

call = response.tool_calls[0]

assert call[«name»] == «search_flights»
validate(
instance=call[«arguments»],
schema=SEARCH_FLIGHTS_SCHEMA
)
assert call[«arguments»] == {
«origin»: «Moscow»,
«destination»: «Istanbul»,
«departure_date»: «2026-04-17»
}

Отдельный негативный тест проверяет, что при неполных данных агент задаёт уточняющий вопрос:

python
def test_search_flights_requests_missing_fields(model):
response = model.generate(
user_input=»Найди рейс в Стамбул»,
tools=[search_flights],
temperature=0
)

assert response.tool_calls == []
assert asks_for_required_fields(
response.text,
fields={«origin», «departure_date»}
)

Функция `asks_for_required_fields` не обязана сравнивать ответ посимвольно. Она может искать обязательные смысловые элементы по детерминированным правилам или использовать отдельную размеченную проверку.

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

Как тестировать цепочки действий

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

Пример безопасного сценария бронирования:

Пользователь задаёт маршрут и дату.

Агент вызывает `search_flights`.
3. Тестовый адаптер возвращает заранее подготовленный список.
4. Агент предлагает варианты пользователю.
5. Пользователь выбирает конкретный рейс.
6. Агент запрашивает подтверждение.
7. Только после подтверждения вызывается `book_flight` с ID выбранного варианта.

Для такой траектории проверяются:

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

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

Граф состояний помогает формализовать такую проверку:

text
START
-> COLLECT_REQUIRED_FIELDS
-> SEARCH
-> PRESENT_OPTIONS
-> REQUEST_CONFIRMATION
-> EXECUTE
-> DONE

Переход из `SEARCH` сразу в `EXECUTE` должен считаться нарушением, даже если аргументы финальной функции технически корректны.

Как изолировать внешние API

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

Мок должен уметь возвращать:

  • успешный ответ;
  • пустой результат;
  • ошибку авторизации;
  • превышение лимита;
  • задержку или тайм-аут;
  • частично заполненный объект;
  • неожиданный, но допустимый новый элемент ответа.

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

Полезно разделять два уровня:

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

Если смешать эти уровни, сбой внешнего сервиса будет выглядеть как деградация модели, а изменение модели — как проблема интеграции.

Негативные проверки безопасности

Описание инструмента само по себе не является механизмом авторизации. Даже если в нём сказано «вызывать только после подтверждения», ограничение должно дублироваться в исполняемом коде.

В релизный набор стоит включить следующие случаи:

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

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

Особое внимание требуется к prompt injection через результаты инструментов. Текст из веб-страницы, письма или документа не должен получать тот же уровень доверия, что системные правила. В тесте можно вернуть строку с требованием вызвать административную функцию и убедиться, что агент её не выполняет.

Когда применять LLM-as-a-judge

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

Детерминированно следует проверять:

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

LLM-as-a-judge можно использовать, чтобы определить, запросил ли агент недостающую информацию понятным способом или корректно ли объяснил отказ. Для такого оценщика нужен собственный размеченный набор, фиксированная инструкция и периодическая проверка согласованности с людьми.

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

Как встроить проверки в CI

В CI удобно разделить тесты на три группы.

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

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

Релизный набор запускается перед сменой модели, версии API или настроек генерации. Он содержит критичные сценарии безопасности и сравнение с предыдущей конфигурацией.

Отчёт CI должен показывать:

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

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

Практический чек-лист перед релизом

Перед подключением агента к реальным системам проверьте следующие пункты:

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

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

Источники и документация

Berkeley Function-Calling Leaderboard описывает подходы к оценке способности моделей выбирать функции и формировать аргументы:

https://arxiv.org/abs/2401.06209

Документация Anthropic по tool use содержит формат определения инструментов, структуру запросов и обработку результатов:

https://docs.anthropic.com/en/docs/build-with-claude/tool-use

В Anthropic Cookbook опубликован пример оценки вызовов инструментов:

https://github.com/anthropics/anthropic-cookbook/blob/main/tool_use/evaluating_tool_use.ipynb

Документация OpenAI по function calling и Structured Outputs:

https://platform.openai.com/docs/guides/function-calling

Практический разбор построения eval-систем для приложений на базе LLM:

https://hamel.dev/blog/posts/eval/