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

Как оценить стабильность tool calling в LLM: практический чеклист с примерами на Python

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

Фрагмент Python-скрипта с тестом стабильности tool calling и JSON-схемой валидации аргументов
Фрагмент Python-скрипта с тестом стабильности tool calling и JSON-схемой валидации аргументов
Fırtına Haber.png | by Fırtına Haber | wikimedia_commons | CC BY-SA 4.0

LLM-агенты работают через tool calling — модель получает описание доступных функций и возвращает JSON с именем инструмента и аргументами. На практике этот механизм даёт сбои даже у ведущих моделей: пропуск обязательного вызова, неверный тип аргумента, вызов несуществующего инструмента или дублирование одного и того же действия.

Разработчики часто замечают такие проблемы только в продакшене, когда агент уже начал списывать деньги с карты пользователя или отправлять дублирующиеся письма. Этот материал — практический чеклист для проверки стабильности tool calling до развёртывания. Все примеры — на Python, с готовыми тестами, которые можно добавить в CI.

Почему tool calling ломается: три типовых сценария

Tool calling — не генерация текста, а структурированный вывод. Модель обучают предсказывать токены, и JSON — лишь один из возможных форматов. Отсюда три категории ошибок, которые встречаются в бенчмарках (например, в работе Berkeley Function Calling Leaderboard и исследованиях вроде «ToolLLM» arxiv:2307.08702):

Пропуск вызова — модель решает, что инструмент не нужен, хотя по сценарию он обязателен. Например, агент поддержки должен вызывать `get_user_info` перед ответом, но пропускает шаг.
2. Неверные аргументы — модель передаёт строку вместо числа, пустой объект вместо обязательного поля или значение вне допустимого диапазона.
3. Нарушение порядка — модель вызывает инструменты в неправильной последовательности или дублирует вызовы.

Систематическое тестирование на этих трёх осях закрывает большинство проблем. Дальше — конкретные тесты с кодом.

Тест 1: проверка на пропуск обязательного вызова

Самый простой и важный тест: модель должна вызвать инструмент, когда это явно требуется промптом или контекстом.

Сценарий: пользователь просит перевести деньги. Агент обязан вызвать `transfer_money(amount: float, recipient: str)` перед тем, как ответить.

Пример теста на Python

python
import json
from openai import OpenAI

client = OpenAI()

tools = [{
«type»: «function»,
«function»: {
«name»: «transfer_money»,
«description»: «Перевести деньги получателю»,
«parameters»: {
«type»: «object»,
«properties»: {
«amount»: {«type»: «number»},
«recipient»: {«type»: «string»}
},
«required»: [«amount», «recipient»]
}
}
}]

messages = [
{«role»: «user», «content»: «Переведи 500 рублей Ивану»}
]

response = client.chat.completions.create(
model=»gpt-4o»,
messages=messages,
tools=tools,
tool_choice=»required» # принудительно требует вызова
)

has_call = bool(response.choices[0].message.tool_calls)
assert has_call, «Модель не вызвала обязательный инструмент»

Важный нюанс: параметр `tool_choice` не всегда доступен (например, в некоторых open-source моделях). В этом случае проверяйте наличие вызова после обычного запроса. Если модель в 10% случаев не вызывает инструмент — это проблема, которую нужно документировать и решать через few-shot примеры или дообучение.

Тест 2: валидация типов и структуры аргументов

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

Пример теста с JSON Schema валидацией

python
from jsonschema import validate, ValidationError

tool_schema = {
«type»: «object»,
«properties»: {
«amount»: {«type»: «number», «minimum»: 0.01},
«recipient»: {«type»: «string», «minLength»: 1}
},
«required»: [«amount», «recipient»]
}

def validate_tool_call(tool_call):
try:
args = json.loads(tool_call.function.arguments)
validate(instance=args, schema=tool_schema)
return True
except (json.JSONDecodeError, ValidationError) as e:
print(f»Ошибка валидации: {e}»)
return False

Проверка на серии из 100 запросов
failures = 0
for _ in range(100):
response = client.chat.completions.create(
model=»gpt-4o»,
messages=messages,
tools=tools,
tool_choice=»required»
)
for tool_call in response.choices[0].message.tool_calls:
if not validate_tool_call(tool_call):
failures += 1

print(f»Невалидных вызовов: {failures}/100″)

Дополнительно стоит проверять граничные значения: отрицательные числа, пустые строки, слишком длинные строки. Некоторые модели склонны передавать `»amount»: 0` или `»recipient»: «»`, что может привести к ошибкам на стороне бэкенда.

Тест 3: проверка на дублирование вызовов и порядок

Агенты, которые вызывают несколько инструментов последовательно (multi-step), могут страдать от дублирования: модель вызывает один и тот же инструмент дважды с одинаковыми аргументами или нарушает ожидаемый порядок (например, вызывает `send_email` до `create_draft`).

Пример теста на порядок вызовов

python
EXPECTED_ORDER = [«create_draft», «send_email»]

def check_call_order(tool_calls):
actual_order = [tc.function.name for tc in tool_calls]
# Проверяем, что последовательность подпоследовательность ожидаемой
idx = 0
for call in actual_order:
if idx < len(EXPECTED_ORDER) and call == EXPECTED_ORDER[idx]:
idx += 1
return idx == len(EXPECTED_ORDER)

Проверка на дубликаты
def check_duplicates(tool_calls):
seen = set()
for tc in tool_calls:
key = (tc.function.name, tc.function.arguments)
if key in seen:
return False # дубликат
seen.add(key)
return True

В реальных сценариях дублирование особенно опасно для идемпотентных операций: повторный вызов `charge_payment` может привести к двойному списанию.

Тест 4: стресс-тест на большое количество инструментов

Модели хуже справляются с выбором, когда в описании больше 10–15 инструментов. Полезно провести стресс-тест: добавить 20–30 фиктивных инструментов и проверить, что модель всё ещё вызывает нужный.

Методика

Создайте список из 30 инструментов с разными названиями и описаниями.
2. В одном из запросов явно укажите, какой инструмент нужно использовать.
3. Запустите 50 запросов и замерьте долю правильных вызовов.

Исследование «ToolLLM» arxiv:2307.08702 показывает, что точность падает на 15–20% при увеличении набора инструментов с 5 до 20. Это важно учитывать при проектировании агентов: не складывайте все инструменты в один промпт, группируйте их по сценариям.

Интеграция тестов в CI

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

yaml
name: Tool Calling Tests
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
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

Важно: тесты будут тратить токены. Чтобы не разориться, ограничьте количество запросов (например, 10–20 на прогон) и используйте кэширование ответов для повторяющихся сценариев.

Ограничения подхода и что делать, если тесты падают

Ни один набор тестов не гарантирует стабильность на 100%. Tool calling зависит от версии модели, температуры (при температуре > 0 модель может «креативить» с аргументами) и даже от формулировки описания инструмента.

Что делать при падающих тестах

Добавьте few-shot примеры — покажите модели 2–3 корректных вызова в системном сообщении.
2. Уточните описание параметров — вместо `»amount»: {«type»: «number»}` напишите `»amount»: {«type»: «number», «description»: «Сумма перевода в рублях, минимум 0.01»}`.
3. Используйте строгую валидацию на бэкенде — не доверяйте аргументам от LLM, проверяйте их перед выполнением.
4. Снизьте температуру до 0 для критических вызовов.

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

Что проверить перед запуском агента в продакшен

Перед развёртыванием прогоните минимальный набор:

  • Пропуск вызова — модель вызывает инструмент в 100% обязательных сценариев.
  • Валидность аргументов — JSON проходит схему, типы корректны, граничные значения обработаны.
  • Отсутствие дубликатов — модель не вызывает один инструмент дважды с одинаковыми аргументами.
  • Порядок вызовов — multi-step сценарии выполняются в правильной последовательности.
  • Стресс-тест — модель не теряется при 20+ инструментах.

Эти пять проверок не заменяют полноценного QA, но отсекают самые дорогие ошибки. Если вы используете open-source модели — добавляйте тесты на специфичные для них баги (например, Llama 3 иногда возвращает невалидный JSON в tool calling, что требует повторной генерации).

Источники