
Когда AI-агент перестаёт вызывать нужный инструмент или передаёт в него мусорные параметры, проблема редко заметна на одном примере. Она проявляется в прогоне пайплайна, когда модель, которая ещё вчера корректно парсила дату и передавала её в календарь, вдруг начинает возвращать `null` или строку с лишними символами.
Tool calling — самый хрупкий компонент агентных систем. Смена модели, обновление API, изменение схемы инструмента или даже разница в температуре могут сломать то, что работало неделю. Без регрессионных тестов деградация вызова инструментов остаётся незамеченной до первого инцидента в проде.
В этой статье — практический чеклист для настройки регрессионных проверок tool calling, который можно встроить в CI/CD за вечер.
Что ломается в tool calling и почему это не видно в чате
Tool calling (или function calling) — это способ LLM вернуть структурированный JSON с именем инструмента и аргументами вместо обычного текста. Модель решает, какой инструмент вызвать, и заполняет параметры по описанию схемы.
Проблема в том, что даже одна и та же модель с той же температурой может давать разные результаты на граничных случаях. А при смене модели — например, с Claude 3 Haiku на Claude 3.5 Sonnet — поведение меняется кардинально.
Типичные поломки:
- модель перестаёт вызывать инструмент в сценариях, где раньше вызывала;
- передаёт параметры не того типа (строка вместо числа);
- возвращает лишние поля, которых нет в схеме;
- вызывает не тот инструмент из нескольких возможных;
- дублирует вызовы или не вызывает ничего.
В чате эти ошибки выглядят как «странное поведение агента». Без тестов вы не поймёте, что проблема именно в tool calling, а не в логике промпта.
Какие метрики измерять
Прежде чем писать тесты, нужно определить, что считать «правильным вызовом». Для регрессионных проверок достаточно трёх метрик.
Точность выбора инструмента (tool selection accuracy) — доля случаев, когда модель вызвала правильный инструмент из доступных. Если у агента три инструмента, а модель вызывает четвёртый (которого нет) или не вызывает ничего — это ошибка.
Точность аргументов (argument fidelity) — доля вызовов, где все переданные параметры соответствуют схеме по типу, обязательности и допустимым значениям.
Полнота вызова (completeness) — процент обязательных полей, которые модель заполнила, от общего числа обязательных полей в схеме.
Для каждого тестового сценария вы фиксируете ожидаемый инструмент и ожидаемые аргументы. После прогона модель возвращает фактический вызов — и вы сравниваете.
Чеклист: 6 шагов к регрессионному тесту
Соберите датасет граничных случаев
Не нужно тысячу примеров. Достаточно 20–30 сценариев, которые покрывают:
- штатный вызов с полными данными;
- пропуск необязательных полей;
- пустые строки и null;
- числа с плавающей точкой вместо целых;
- даты в разных форматах;
- enum-поля со значением вне списка;
- контекст, где нужно вызвать один инструмент из нескольких похожих.
Каждый сценарий — это пара: входной промпт (контекст пользователя) и ожидаемый вызов (инструмент + аргументы).
Напишите валидатор вызова
Валидатор принимает фактический JSON от модели и сравнивает с ожидаемым. Не жёсткое сравнение строк — нормально, если порядок полей другой или есть лишний пробел. Проверяйте по ключам и типам значений.
Пример на Python:
python
import json
def validate_tool_call(expected: dict, actual: dict) -> dict:
errors = []
if expected.get(«name») != actual.get(«name»):
errors.append(f»Expected tool {expected.get(‘name’)}, got {actual.get(‘name’)}»)
for key, expected_val in expected.get(«arguments», {}).items():
actual_val = actual.get(«arguments», {}).get(key)
if key == «date» and isinstance(actual_val, str):
# допускаем разные форматы даты
continue
if type(actual_val) != type(expected_val):
errors.append(f»Argument {key}: expected {type(expected_val).name}, got {type(actual_val).name}»)
return {«passed»: len(errors) == 0, «errors»: errors}
Добавьте прогон с разной температурой
Одна и та же модель при temperature=0 и temperature=0.5 может вести себя по-разному. Для регрессии используйте temperature=0 — это даёт детерминированный результат на большинстве провайдеров. Отдельно прогоняйте с temperature=0.3, чтобы поймать нестабильность на граничных случаях.
Интегрируйте в CI/CD
Регрессионный тест должен запускаться при каждом изменении промпта, схемы инструмента или версии модели. В GitHub Actions это выглядит так:
yaml
name: Tool Calling Regression
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
— uses: actions/checkout@v4
— name: Run tool calling tests
run: python tests/test_tool_calling.py
В тесте вы подставляете API-ключ из секретов, прогоняете датасет и фейлите пайплайн, если accuracy упала ниже порога (например, 95%).
Зафиксируйте baseline
Перед тем как начать тестировать регрессии, прогоните датасет на текущей рабочей модели и сохраните результат как baseline. Дальнейшие прогоны сравниваются с ним — не поштучно, а по метрикам. Если accuracy на новом вызове упала на 5% относительно baseline — это повод остановить деплой.
Добавьте проверку на невызов инструмента
Отдельный тест — ситуации, когда модель должна вызвать инструмент, но возвращает текстовый ответ. Это самая опасная деградация, потому что она не заметна в логах ошибок: агент просто отвечает текстом вместо действия.
Включите в датасет 3–4 сценария, где вызов инструмента обязателен. Если модель хотя бы в одном случае не вызвала инструмент — тест падает.
Что ещё учесть
Модели ведут себя по-разному на одинаковых схемах. Claude 3.5 Sonnet, например, стабильнее вызывает инструменты с enum-полями, чем GPT-4o mini на тех же схемах. При смене модели перепрогоняйте весь датасет и обновляйте baseline.
Схема инструмента — часть теста. Если вы добавили новое обязательное поле в инструмент, старые тесты должны упасть. Это нормально: вы явно фиксируете, что поведение изменилось.
Не доверяйте одному прогону. Tool calling — вероятностный процесс даже при temperature=0. Запускайте тест дважды. Если результат различается — модель нестабильна на вашем датасете, и это повод сменить модель или доработать схемы.
Open-source фреймворки. Для быстрого старта можно использовать DeepEval или LangSmith — у обоих есть встроенные метрики для function calling. Но для регрессии в CI проще написать свой валидатор на 50 строк, чем тянуть зависимость.
Когда этого недостаточно
Регрессионный чеклист ловит только явные ошибки: не тот инструмент, не тот тип, пропущенный вызов. Он не проверяет семантическую корректность — передал ли агент правильную дату, если в контексте было «послезавтра». Для этого нужны eval-наборы с человеческой разметкой, которые прогоняются реже — раз в неделю или перед релизом.
Также чеклист не покрывает цепочки вызовов, где один инструмент зависит от результата другого. Для многошаговых агентов нужны интеграционные тесты, которые проверяют последовательность, а не отдельный вызов.
Что делать с результатами
Если тест упал — не отключайте его. Зафиксируйте, на каком сценарии произошла ошибка, и сравните с baseline. Чаще всего проблема в схеме: слишком длинное описание параметра, неоднозначное имя инструмента или конфликт имён. Реже — в модели, которая плохо обработала конкретный паттерн.
Ошибка в tool calling — это не баг модели, а несоответствие между тем, как вы описали инструмент, и тем, как модель его поняла. Чем раньше вы это поймаете, тем меньше шансов, что агент в проде вызовет не тот API.








