Перейти к содержимому
16 нояб. 2024 г.·7 мин чтения

Оценка tool use: автоматические проверки без ручного разбора

Оценка tool use без ручного просмотра: как собрать автопроверки аргументов, порядка вызовов и финального ответа для LLM.

Оценка tool use: автоматические проверки без ручного разбора

Почему ручной просмотр ломается на объеме

На первых 20-30 диалогах ручная проверка кажется разумной. На тысяче она уже создает ложное чувство контроля. Команда смотрит выборку, находит пару заметных промахов и решает, что общая картина понятна. Обычно это не так.

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

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

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

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

Что считать ошибкой в tool use

Ошибка в tool use - это не любой странный шаг модели. Ошибкой стоит считать только то, что нарушает контракт сценария: модель выбрала не тот инструмент, передала неверные аргументы, сломала обязательный порядок шагов или дала ответ, который не следует из полученных данных.

Сначала разделите сбой модели и сбой инструмента. Если модель вызвала get_order_status с корректным order_id, а сервис вернул 500 или таймаут, проблема на стороне инструмента. Если модель отправила пустой order_id, перепутала инструмент или ответила так, будто запрос прошел успешно, хотя данных нет, проблема на стороне модели. Эти статусы лучше считать отдельно. Иначе одна метрика смешает слабый промпт с нестабильным API.

Для каждого инструмента заранее зафиксируйте контракт: какие аргументы обязательны, какие типы и диапазоны допустимы, какие значения разрешены, можно ли передавать лишние поля и где допустима нормализация, например строка "42" вместо числа 42. После этого спорных случаев станет меньше. Если сценарий требует currency="RUB" или limit <= 100, проверка уже не зависит от вкуса ревьюера.

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

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

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

Какие данные сохранять после каждого прогона

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

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

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

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

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

Обычно хватает такого минимума:

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

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

Проверки аргументов

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

Начните с базовой валидации аргументов: типы, обязательные поля и допустимые диапазоны. Если инструмент ждет число от 1 до 30, строка "пять" и значение 300 должны считаться разными ошибками. Это полезно для отладки: одна проблема указывает на неверный парсинг, другая - на плохой контроль границ.

Отдельно ловите мусор в payload. Лишние поля часто появляются, когда модель додумывает структуру запроса по аналогии с другим инструментом. Пустые строки, null вместо значения и массивы с одним пустым элементом тоже стоит помечать, даже если API молча их принимает.

Нормализация нужна до сравнения с эталоном, иначе вместо сигнала вы получите шум. Даты лучше приводить к одному виду, например YYYY-MM-DD. Суммы - к одной валюте, а идентификаторы - очищать от пробелов, скобок и случайных префиксов, если это допустимо правилами API.

Проверки аргументов удобно разделить на несколько простых слоев. Первый слой - синтаксис: тип, обязательность, диапазон, enum. Второй - структура: лишние поля, пустые значения, дубли. Третий - нормализация: даты, валюты, идентификаторы. Четвертый - источник значения: взято ли оно из диалога, из tool output или модель его придумала.

Последний пункт особенно полезен. Если пользователь не называл дату встречи, а модель отправила 2025-05-12, это не мелкая неточность, а подстановка. То же самое с суммой заказа, номером счета или городом доставки.

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

Проверки порядка вызовов

Упростите корпоративный запуск
Получайте инвойсы в рублях и подключайте API без лишней бухгалтерии.

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

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

Что стоит ловить сразу

Есть несколько сбоев, которые почти всегда стоит считать ошибкой. Первый - запрещенный ранний вызов. Например, payment.charge нельзя вызывать, пока модель не получила ответ от order.get_status или risk.check. Второй - повтор без причины. Если модель дважды подряд вызывает один и тот же tool с теми же аргументами, это обычно цикл, а не осознанная попытка. Третий - пропуск шага. Если сценарий возврата требует order.get_status, а модель сразу идет в refund.create, тест должен падать. Четвертый - гонка без ожидания, когда модель делает следующий шаг, который зависит от результата, хотя ответа инструмента еще нет.

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

Небольшой пример

Представьте помощника поддержки магазина. Нормальная цепочка такая: сначала order.get_status, потом refund.check_eligibility, и только после этого refund.create. Если модель после первого шага сразу запускает refund.create, она пропустила обязательную проверку. Если она три раза подряд дергает order.get_status с одним и тем же order_id, значит, она зациклилась.

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

Для первых тестов не пытайтесь покрыть все ветки. Возьмите 3-4 самых дорогих сценария, где ошибка ведет к списанию денег, неверному статусу или пропуску обязательной проверки. Такие правила быстро находят реальные сбои.

Проверки финального ответа

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

Сначала сверьте финальный ответ с тем, что реально вернул инструмент. Если tool output говорит "доставка 12 мая", а модель пишет "завтра", это уже повод для ошибки. То же касается сумм, статусов, имен, лимитов и кодов ошибок. Лучше сравнивать не весь текст целиком, а отдельные поля: дату, сумму, ID заявки, остаток на складе, признак успеха.

Отдельный класс ошибок - выдуманные действия. Модель любит писать "я отправил запрос", "я обновил заказ" или "письмо уже ушло", хотя в логе нет ни одного успешного вызова с таким результатом. Такие фразы удобно ловить простым правилом: если в ответе есть маркер завершенного действия, в trace должен быть соответствующий tool call со статусом success.

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

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

Последний пункт часто недооценивают. Пользователь спрашивает: "Когда доставят заказ и можно ли сменить адрес?" Модель проверяет только дату и бодро отвечает про доставку, игнорируя адрес. Формально tool call мог пройти без ошибок, но задача пользователя не закрыта. Такие случаи лучше отмечать отдельно: инструменты сработали, а смысловой результат не достигнут.

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

Начните не с кода, а со сценариев. Возьмите 20-50 реальных задач из продакшена или пилота и для каждой зафиксируйте, что модель получает на входе, какие инструменты ей доступны и какая трасса считается правильной. Трасса нужна не идеальная, а проверяемая: какой инструмент должен вызваться, в каком порядке, с какими обязательными аргументами и в какой момент модель должна остановиться.

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

Дальше соберите эталонные ответы инструментов. Лучше не дергать живые сервисы во время оценки, иначе тесты начнут падать из-за сети, новых данных или случайных задержек. Обычно хватает моков или замороженных JSON-ответов. Если у вас есть поиск, CRM и калькулятор скидки, сохраните типичные ответы для каждого шага и подавайте их одинаково на каждом прогоне.

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

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

Запускайте такой набор на каждой версии промпта, модели и маршрута. Если вы гоняете модели через единый OpenAI-совместимый слой, например через RU LLM, сравнивать результаты проще: меняется модель или провайдер, а тестовый контур остается тем же. В этом и есть практический смысл такой прослойки. Один и тот же набор кейсов можно прогонять через api.rullm.com без переписывания SDK и тестовой обвязки, а потом сравнивать не ощущения, а трассы и ошибки по категориям.

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

Простой сценарий для первого набора тестов

Поменяйте только адрес API
Оставьте SDK, код и промпты без изменений и подключите RU LLM.

Для первого набора тестов лучше взять короткий бытовой сценарий, где легко увидеть правильную цепочку действий. Подходит запрос "Перенесите доставку на завтра после 18:00".

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

Набор инструментов здесь простой: find_order находит заказ по данным клиента, get_delivery_slots возвращает свободные интервалы для найденного заказа, а reschedule_delivery меняет дату и время доставки.

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

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

Если ответ звучит как "Доставку перенесли на завтра", этого мало. Пользователь должен увидеть конкретику: "Перенесла доставку на 12 марта, 18:00-20:00. Статус: подтверждено". Такой ответ легко проверить автоматически регулярным правилом или простым парсером.

Если команда гоняет один и тот же сценарий через несколько моделей, удобно держать тест в одном формате и запускать его через общий OpenAI-совместимый слой, например через RU LLM. Тогда меняется модель, а сами проверки остаются одинаковыми. Это заметно экономит время и быстро показывает, какая модель чаще ломает порядок вызовов или подставляет неверный интервал.

Где команды чаще всего ошибаются

Первая частая ошибка - сводить всю оценку к одному числу. Score 78/100 выглядит аккуратно в дашборде, но почти не помогает, когда нужно понять, что именно сломалось. Модель могла выбрать не тот инструмент, передать пустой аргумент или дать уверенный финальный ответ после неуспешного вызова. У этих сбоев разная цена, а один общий балл это скрывает.

Лучше хранить простые флаги по каждому шагу:

  • tool_selected_correctly
  • args_valid
  • call_order_valid
  • final_answer_supported
  • tool_execution_success

Так команда сразу видит, где копать.

Вторая ошибка - смешивать бизнес-правила и форматные проверки в один слой. Проверка JSON-схемы и правило "не показывать клиенту отказ банка как одобрение" не равны по смыслу. Если держать их вместе, сигнал начинает шуметь: тест упал, но непонятно, сломан парсер или нарушена логика продукта.

Еще одна частая проблема - не сохранять сырые tool outputs. Многие оставляют только финальный ответ модели и пару метрик, а потом не могут восстановить ход выполнения. Это особенно болезненно, когда внешний сервис вернул неожиданный статус, лишнее поле или текст вместо числа. Без сырого ответа вы видите симптом, но не причину.

Если команда гоняет tool use через единый OpenAI-совместимый эндпоинт, проблема становится еще заметнее. Можно быстро поменять модель или провайдера, а поведение на вызовах инструментов слегка изменится. Без сырых ответов и аудит-трейла разбор растягивается на часы.

Есть и более приземленная ошибка: команда поменяла схему инструмента и забыла обновить тесты. Например, поле amount стало decimal_amount, а старые проверки все еще ждут прежнее имя. В итоге часть тестов падает шумно, а часть, наоборот, проходит случайно, потому что валидатор слишком мягкий.

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

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

Разберите регрессии быстрее
Аудит-трейлы показывают, где сломался шаг.

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

  • Для каждого tool задайте строгую схему аргументов: типы, обязательные поля, допустимые значения и явные запреты.
  • Для каждого сценария опишите ожидаемую последовательность шагов. Достаточно простого плана: сначала поиск, потом проверка, потом действие.
  • Убедитесь, что логи сохраняют полный trace: сырые сообщения, аргументы вызовов, ответы инструментов, идентификаторы запросов, время и финальный ответ модели.
  • Отчет должен показывать не просто статус fail, а точную причину. Формулировка вроде "tool payment_refund вызван до проверки статуса заказа" полезнее любого общего балла.

Есть простой способ проверить качество пайплайна до большого запуска. Возьмите 10-15 кейсов и специально сломайте часть из них: поменяйте тип аргумента, уберите обязательное поле, переставьте два вызова местами. Если система не может ясно объяснить каждый провал, на тысяче диалогов она тоже не справится.

Если вы прогоняете модели через шлюз вроде RU LLM, отдельно проверьте, что trace не теряет поля на уровне прокси и провайдера. Иначе сравнение моделей и разбор ошибок будут неполными.

Что делать дальше

Не пытайтесь покрыть весь каталог инструментов сразу. Начните с 20-30 частых сценариев: поиск клиента, проверка статуса заказа, создание заявки, отмена операции, запрос выписки. Такой набор быстро покажет, где модель путает аргументы, делает лишний вызов или пишет уверенный, но неверный ответ.

Сразу разделите оценку tool use на три группы метрик. Один общий балл почти бесполезен: он скрывает источник ошибки и замедляет разбор.

  • Аргументы: обязательные поля, типы, диапазоны, формат дат, валют и ID
  • Порядок вызовов: тот ли инструмент выбрала модель, нужен ли второй шаг, не появился ли лишний вызов
  • Финальный ответ: опирается ли текст на результат инструментов, не добавляет ли модель выдуманные факты, правильно ли передает статус и ограничения

Такой разрез помогает спорить не о впечатлениях, а о конкретных сбоях. Если модель получила 92% по аргументам, но 61% по финальному ответу, проблема обычно не в схеме tool use, а в том, как она пересказывает результат пользователю.

Сравнивайте модели, промпты и версии оркестрации только на одном и том же наборе тестов. Иначе цифры не совпадут даже при похожем качестве, а обсуждение быстро уйдет в догадки. На старте хватит обычной таблицы по трем группам метрик и списка проваленных кейсов.

Если вы уже ведете LLM-трафик через RU LLM, не стоит без причины собирать для оценки отдельный стек. Проще гонять те же тесты через единый OpenAI-совместимый контур, а затем разбирать аудит-трейлы по каждому запросу. Это особенно удобно для команд, которым важны маршрутизация моделей, локальный биллинг и хранение логов внутри РФ.

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

Часто задаваемые вопросы

Почему ручной просмотр плохо работает на большом объеме диалогов?

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

Что именно считать ошибкой в tool use?

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

Как отличить сбой модели от сбоя инструмента?

Смотрите на первый неверный шаг. Если модель вызвала нужный tool с корректными аргументами, а сервис ответил 500 или timeout, сломался инструмент. Если модель отправила пустой order_id, выбрала не тот tool или уверенно ответила без данных, ошиблась модель.

Какие данные нужно сохранять после каждого прогона?

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

Как проверять аргументы вызовов инструментов?

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

Как ловить ошибки в порядке вызовов?

Опишите допустимые переходы между шагами для частых сценариев. Тест должен ловить ранний вызов, пропуск обязательного шага, повтор одного и того же tool без новой причины и попытку продолжить цепочку без ответа инструмента. Этого хватает для первых 5–10 сценариев.

Как проверить, что финальный ответ опирается на данные инструментов?

Сверяйте ответ не с общим впечатлением, а с полями из tool output. Если инструмент вернул одну дату, а модель написала другую, это ошибка. То же касается выдуманных действий: фраза вроде «я создал заявку» проходит только тогда, когда в trace есть успешный вызов с таким результатом.

С чего начать сборку пайплайна оценивания?

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

Какой сценарий лучше взять для первого набора тестов?

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

Какие ошибки команды делают чаще всего при оценке tool use?

Часто команды сводят все к одному score и теряют причину сбоя. Еще они не хранят сырые tool outputs, смешивают форматные проверки с бизнес-логикой и забывают обновить тесты после смены схемы инструмента. Из-за этого отчет выглядит чисто, а контракт уже не совпадает с продом.