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

Контрактные тесты для tool calling: как ловить ошибки агентов до релиза

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

Дашборд проверки OpenAI function calling с результатами валидации JSON-схем и статусом GitHub Actions
Дашборд проверки OpenAI function calling с результатами валидации JSON-схем и статусом GitHub Actions
Visit of Ursula von der Leyen, President of the European Commission, to India (P-065755-00-36).jpg | by Europäische Kommission — Audiovisueller Dienst, CE — Service audiovisuel, EC — Audiovisual Service, Dati Bendo | wikimedia_commons | CC BY 4.0

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

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

выбор инструмента;

соответствие аргументов схеме и смыслу запроса;
3. поведение цепочки вызовов от первого шага до результата.

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

Что именно считается ошибкой

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

Уровень проверки Что сравнивать Пример ошибки Решение
Выбор функции Имя инструмента и число вызовов Агент вызывает `cancel_order` вместо `get_order_status` Исправить описания функций или маршрутизацию
Аргументы Типы, обязательные поля, перечисления В `order_id` передаётся имя клиента Уточнить схему и добавить негативные тесты
Семантика Соответствие аргументов исходному запросу Выбран заказ другого пользователя Добавить проверку прав и сопоставление сущностей
Последовательность Порядок и зависимости шагов Операция выполняется до проверки доступа Ввести промежуточный guard или запрет на вызов
Ошибки Реакция на отказ, таймаут и пустой ответ Агент повторяет опасную операцию бесконечно Настроить лимит повторов и понятное завершение

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

Начните со схемы инструмента

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

Удобная минимальная запись для тестового набора может выглядеть так:

json
{
«id»: «order_status_missing_id»,
«input»: «Покажи статус моего заказа»,
«expected»: {
«action»: «ask_clarification»,
«reason»: «order_id is required»
},
«tags»: [«missing_parameter», «safe_failure»]
}

Для успешного сценария добавьте проверку:

json
{
«id»: «order_status_valid_id»,
«input»: «Покажи статус заказа A-1042»,
«expected»: {
«tool»: «get_order_status»,
«arguments»: {
«order_id»: «A-1042»
}
}
}

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

Четыре набора сценариев

Минимальный набор должен проверять разные причины отказа, а не повторять один и тот же запрос с небольшими изменениями.

Прямой вызов

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

Пример: «Проверь статус заказа A-1042». Ожидается один вызов `get_order_status` с точным значением `order_id`.

Конфликтующие инструменты

В списке должны быть функции с близкими задачами. Например, `search_orders` ищет заказы по фильтрам, а `get_order_status` получает состояние конкретного заказа. Тест покажет, понимает ли агент разницу между поиском и чтением уже известной сущности.

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

Негативные и пограничные входы

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

Модель не должна «исправлять» критичный идентификатор по догадке. Если в запросе указан заказ `A-1042`, нельзя молча подставлять `A-1043` из похожего результата поиска.

Цепочка действий

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

В тесте зафиксируйте:

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

Метрики, которые пригодны для CI

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

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

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

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

Успешность цепочки измеряет весь сценарий целиком. Если первые три вызова прошли, а четвёртый нарушил порядок подтверждения, цепочка должна считаться проваленной.

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

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

Как подключить тесты к CI

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

Последовательность внедрения:

Соберите 20–30 сценариев для наиболее важных инструментов.

Разделите их на `happy_path`, `missing_data`, `invalid_data`, `ambiguous` и `sequence`.
3. Зафиксируйте версию модели, схему функций и параметры запуска.
4. Сохраните структурированный ответ модели и нормализованный результат сравнения.
5. Добавьте пороги для каждой группы, а не один общий процент.
6. Сделайте сборку неуспешной при нарушении критичного правила.
7. Прикладывайте к отчёту идентификатор теста, вход, ожидаемый вызов и фактические аргументы.

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

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

Как разбирать проваленный тест

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

Если выбрана неправильная функция, сравните описания конкурирующих инструментов. Частая причина — пересечение терминов или слишком широкое описание вроде «работает с заказами». Уточните входные условия и явно перечислите случаи, когда функцию вызывать нельзя.

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

Если нарушен порядок, перенесите критичное правило из описания в код оркестратора. Проверку прав, подтверждение операции и ограничения на повтор нельзя оставлять только на усмотрение модели.

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

Что измерять после релиза

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

Еженедельный отчёт может включать:

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

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

Практический план на один день

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

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

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

Документация OpenAI по function calling содержит описание структурированных вызовов и работы со схемами: https://platform.openai.com/docs/guides/function-calling. Руководство Anthropic по tool use показывает формат инструментов и ответы с вызовами: https://docs.anthropic.com/en/docs/build-with-claude/tool-use. Для общего контекста оценки вызова функций можно обратиться к исследованию Gorilla от Patil и соавторов, опубликованному в 2023 году: https://arxiv.org/abs/2307.04725.

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