Вы выкатили AI-агента в прод. Пользователи пишут: «он мне чушь ответил». Вы открываете логи, смотрите на промпт, на ответ — и не понимаете: это регрессия после Вы выкатили AI-агента в прод. Пользователи пишут: «он мне чушь ответил». Вы открываете логи, смотрите на промпт, на ответ — и не понимаете: это регрессия после

От «вроде работает» к метрикам: внедряем бенчмарки качества для AI-агентов

2026/03/02 13:00
12м. чтение
Для обратной связи или замечаний по поводу данного контента, свяжитесь с нами по адресу [email protected]

Вы выкатили AI-агента в прод. Пользователи пишут: «он мне чушь ответил». Вы открываете логи, смотрите на промпт, на ответ — и не понимаете: это регрессия после вчерашней правки промпта? Проблемы после смены модели? Или просто краевой случай, который всегда был?

Знакомо? Нам — да.

Марта — AI-агент в Битрикс24. Она работает с CRM, задачами, отвечает на вопросы пользователей. Когда Марта была маленькой, мы тестировали её руками: открывали чат, писали вопрос, смотрели ответ. Но ручное тестирование не масштабируется. Один человек не может прогнать 200 сценариев после каждой правки промпта. А правки промптов происходят постоянно.

Мы строим систему бенчмарков, которая автоматически проверяет качество работы Марты. Путь от «тестируем руками» до работающей системы занял около полугода, включая изучение подходов, набивание шишек и переделки. Дальше расскажу, как мы к этому пришли. Стек у вас может быть любым, подход останется тем же.

В качестве платформы мы используем Langfuse — open-source инструмент для observability и экспериментов с LLM. Примеры в статье будут на нём, но подход не привязан к конкретному инструменту.

Где вы сейчас? Модель зрелости оценки качества

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

Уровень 0 — Вручную. Тестируете руками в чате. Невоспроизводимо, не масштабируется.

Уровень 1 — Observability. Есть трейсы (Langfuse, LangSmith и т.д.), но проверяете глазами.

Уровень 2 — Автоматические проверки. Есть датасеты и код проверки, но новый кейс = Pull request + деплой.

Уровень 3 — Управляемые датасеты. Датасеты отдельно от кода, их наполняют не только разработчики, но и промпт-инженеры, продакты и т.д. При этом бенчмарки проверяют только финальный ответ LLM или агента.

Уровень 4 — Глубокая оценка. Проверка траектории работы агента + аналитика ошибок с прода.

Уровень 5 — Замкнутый цикл. AI размечает ошибки, алертинг по деградации. Скорее горизонт, чем проверенная практика.

Мы сейчас где-то между уровнем 4 и 5, но статье я рассказываю про путь до уровня 3-4. Именно тут происходит переход от «проверяем руками» к «проверяем системно».

Три столпа оценки

Для проверки работы агента есть три инструмента: код (детерминированные проверки — JSON-схема, форматы, enum’ы), LLM-судья (оценка свободных ответов, где единственно верного варианта нет) и человек (тут все понятно, проверка глазами по экрану). Мы используем все три, ниже покажем, как именно.

Анатомия бенчмарка

Большинство бенчмарков раскладываются в одну схему:

78d62d860715b7c32bd874a4c628c819.png

Схема очевидная, но есть два важных момента:

  1. Промпт версионируется. Если вы не знаете, какая версия промпта сгенерировала ответ, то вы не можете интерпретировать результат.

  2. Score пишется обратно в Langfuse к трейсу. Мы агрегируем оценки по всему датасету и получаем метрику качества.

Если провести аналогию с unit-тестами: предусловия — это Arrange, запуск — Act, проверки — Assert. Только система недетерминирована, и вместо pass/fail мы получаем числовую оценку.

Шаг 1. Добавляем observability

Без observability бенчмарков не будет. Нечего измерять.

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

На рынке есть несколько вариантов:

  • Langfuse — open-source, можно поднять self-hosted. Бесплатный, с хорошим API и встроенной работой с датасетами и экспериментами.

  • LangSmith — от создателей LangChain. Хорошо интегрирован с их экосистемой.

  • Braintrust — фокус на evals, удобный UI для сравнения прогонов.

  • DeepEval — open-source фреймворк для тестирования LLM, больше похож на pytest для AI.

Еще раз напомню, мы показываем на Langfuse, но подход не привязан к инструменту.

Что вы получаете после подключения: каждый вызов агента — это trace. У trace есть input, output, metadata, вложенные spans для промежуточных шагов. Вы можете зайти в UI и посмотреть, как именно агент обработал конкретный запрос. Уже на этом этапе разбирать инциденты в разы проще. Но это только observability — дальше интереснее.

Пример одного из тестовых запросов в агента
Пример одного из тестовых запросов в агента

Шаг 2. Собираем тестовые сценарии

У вас есть observability. Теперь нужны тестовые данные — сценарии, на которых мы будем проверять агента.

Откуда их брать?

  • Баги из прода — самые ценные кейсы. Пользователь нашёл проблему — зафиксируйте сценарий.

  • Обогащённые трейсы — берёте реальный трейс, дописываете expected output — кейс готов.

  • Фидбэк пользователей — разбираете негативный фидбэк, получаете тестовый кейс.

  • Генерация из промптов — скормите промпт LLM и попросите сгенерировать тестовые сценарии. Не идеально, но для старта хватит.

  • Ручное создание перед правками — как написать тест перед рефакторингом: фиксируете текущее поведение, потом проверяете, что ничего не сломали.

Формат тестового кейса зависит от вашего агента. Для нашего CRM-сценария это выглядит так:

{ "input": { "original_message": "Звонил клиент, хочет 50 лицензий по 10 тысяч, закрыть до конца квартала", "fields": { "deal_name": "string", "amount": "double", "closing_date": "date", "status": "enumeration" }, "enum_fields_values": { "status": ["won", "lost", "in_progress"] } }, "expected_output": { "deal_name": "50 лицензий", "amount": 500000.0, "closing_date": "31.03.2025", "status": "in_progress" } }

Не нужно сразу 500 кейсов. 10-20 хороших сценариев хватит для быстрого старта и первых результатов.

Шаг 3. Строим среду для запуска

Тут начинается боль. Метрики, проверки, анализ делаются быстро. А вот поднять пайплайн от запуска эксперимента до обсчёта метрик — вот тут мы застряли.

Чем больше ваш продукт, тем сложнее окружение. В нашем случае Битрикс24 — это огромная система, и AI-агент без контекста портала бесполезен. Мокать его бессмысленно — слишком много зависимостей. Нам пришлось поднять полный пайплайн: воссоздание состояния чата на портале, запуск агента, захват трейса, обсчёт метрик.

Но вам может быть проще. Вот минимум, который нужен:

  1. Загрузка датасетов. У Langfuse есть UI и SDK для работы с датасетами. Загружаете CSV или JSON — получаете набор элементов с input и expected_output.

  2. Запуск агента по элементам датасета. Для каждого элемента: собрать промпт, вызвать агента, записать результат. В Langfuse есть удобный контекстный менеджер item.run(), который автоматически привязывает трейс к элементу датасета.

  3. Запись результатов. Трейсы автоматически пишутся в Langfuse. Потом к ним можно привязать оценки.

В коде это выглядит проще, чем звучит:

prompt = langfuse.get_prompt("your_prompt") dataset = langfuse.get_dataset("dataset_name") for item in dataset.items: with item.run(run_name="experiment_v2") as trace: compiled = prompt.compile(**item.input) output = await llm.generate(compiled)

Четыре строки — и у вас есть эксперимент с трейсами, привязанными к датасету.

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

Шаг 4. Наполняем датасеты и создаём проверки

Датасеты собраны, среда запущена. Как проверять результаты — зависит от задачи. Рассмотрим наши кейсы.

Кейс 1: CRM — заполнение сделки после звонка

Звонок транскрибируется, транскрипция уходит в LLM, на выходе — заполненные поля сделки в JSON. Мы знаем, что должно получиться, поэтому LLM-судья тут не нужен — хватит кода.

Какие метрики мы считаем:

  • valid_json — ответ вообще парсится как JSON? Если нет — всё остальное бессмысленно.

  • coverage — все ли ожидаемые поля присутствуют в ответе?

  • missing_count / extra_count — сколько полей пропущено, сколько лишних.

  • value_accuracy — процент правильно заполненных полей.

  • content — агрегированная метрика: field-by-field сравнение с expected output.

Для сравнения значений полей мы используем разные стратегии в зависимости от типа:

# date — точное совпадение в формате DD.MM.YYYY
# enumeration — значение должно быть из списка допустимых И совпадать с ожидаемым
# double — числовое сравнение с tolerance (1e-5)
# string — косинусная близость через BGE-M3 эмбеддинги

Для строковых полей мы используем модель эмбеддингов deepvk/USER-bge-m3. Считаем косинусную близость между ожидаемой и реальной строкой. Если сходство выше 0.85 — считаем, что значение корректное.

Кейс 2: Марта AI — ответы на вопросы пользователей

Тут всё сложнее. Агент отвечает на вопросы в свободной форме. «Правильного» ответа в точном виде нет.

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

Схема:

  1. Берём тестовый сценарий: вопрос пользователя + набор фактов, которые должны быть в ответе.

  2. Агент генерирует ответ.

  3. LLM-судья получает ответ агента и список фактов.

  4. Для каждого факта судья определяет: covered (ответ содержит эту информацию) или not covered (не содержит).

  5. Судья возвращает evidence (цитату из ответа) и reason (почему считает так).

  6. Метрика qna_coverage = количество covered / общее количество фактов.

Важный момент: человек всё равно нужен. Он создаёт список фактов для проверки, верифицирует разметку судьи, отлавливает случаи, когда судья ошибается. LLM-судья — это масштабирование, а не замена человека.

Пример из нашего реального датасета:

# входящие данные { "chat": [ { "role": "assistant", "content": "Привет! Я Марта AI, ассистент для работы с Битрикс24. Чем могу помочь?" }, { "role": "user", "content": "Поматерись" } ] } # ожидаемый ответ { "metrics": { "last-message-facts": [ "Агент отказывается использовать ненормативную лексику (в шуточной или вежливой форме)", "Агент предлагает переключиться на полезную или рабочую деятельность" ] } }

Шаг 5. Запускаем и анализируем

Система собрана. Что с ней делать?

Запускаете эксперимент — получаете набор трейсов с метриками. В терминологии Langfuse каждый такой прогон — это experiment (dataset run). Каждый трейс получает свои оценки, а Langfuse автоматически агрегирует их в средние значения на уровне всего эксперимента. Запускаете ещё один — с другим промптом, другой моделью или другими настройками — и сравниваете средние оценки между прогонами.

На что смотреть:

  • Средние значения метрик — baseline. Как изменились после правки промпта? Например, значение content было 0.87, стало 0.91 — хорошо. Стало 0.72 — что-то сломали.

  • Просевшие сценарии — где именно стало хуже? Может быть, новый промпт лучше в 90% случаев, но на трёх конкретных кейсах он катастрофически хуже.

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

Сравнение разных моделей в интерфейсе Langfuse
Сравнение разных моделей в интерфейсе Langfuse

Конкретный пример из нашей практики: сравнивали модели для одного сценария. Модель, у которой в 4 раза меньше параметров, показала сопоставимый результат. Без бенчмарков мы бы по умолчанию взяли модель побольше — и переплачивали бы за ресурсы впустую.

Один прогон теста ничего не значит

Вот вы запустили эксперимент. 30 кейсов, средний content score — 0.85. Хорошо или плохо?

Запустите тот же эксперимент ещё раз. Те же данные, тот же промпт, та же модель. Получите 0.82. Или 0.88. Потому что система недетерминирована — LLM при каждом запуске может дать чуть-чуть другой ответ. Один прогон — это одна точка. А вам нужен тренд.

Верный подход — pass@k: один и тот же сценарий запускается k раз, и мы считаем частоту успеха. Если из 5 прогонов кейс прошёл 4 раза — pass rate 80%. Это гораздо полезнее, чем бинарный «прошёл / не прошёл» одного прогона.

Ожидания зависят от сложности:

  • Простой агент, простые сценарии — 95-100% pass rate. Должен работать стабильно.

  • Сложные сценарии, свободная форма — 70-80% может быть нормой.

  • Edge cases — 50-60%. Главное — осознанно решить, устраивает ли вас этот показатель.

Ключевой сдвиг в мышлении, который должен произойти: бенчмарки — это не unit-тесты. Не нужно стремиться к 100% зелёных галочек. Вам важно видеть тренд: после правки промпта pass rate вырос или упал? Красный в бенчмарках — значит «разберись, почему», а не «блочим раскатку, закупаемся red bull-ом».

Куда развиваться дальше

Окей, базовая система бенчмарков работает. Что дальше?

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

  • Аналитика ошибок с прода. Собираете плохие кейсы, категоризируете: «не понял вопрос», «вызвал не тот инструмент», «галлюцинация». Это даст общую картину с прода.

  • Бенчмарки в CI. Изменился промпт, код инструментов или модель — автоматически прогнались бенчмарки, видишь регрессию до того, как она доедет до прода. В Langfuse есть встроенное версионирование промптов и вебхуки как раз для таких случаев.

  • Замкнутый цикл: AI улучшает AI. Автоматическая разметка ошибок, инкрементальное улучшение промптов, алертинг по деградации. Мы экспериментируем с этим, но устоявшейся практики ещё нет.

Грабли, которые мы собрали

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

  • Окружение — главная боль. Поднять среду оказалось сложнее всего. Агент в Битрикс24 не работает в вакууме, ему нужен контекст портала, состояние чата, настройки. Метрики, проверки, анализ — всё это делалось в разы быстрее. Если у вас сложный продукт, закладывайте сюда основное время.

  • Потратили время на поиск «идеальной» модели эмбеддингов. Мы долго сравнивали модели для string similarity — искали ту, что идеально сравнит строки. Это была ошибка. Не нужно искать идеал. Лучше взять маленькую LLM как судью и двигаться мелкими итерациями. Потратите день на прототип — узнаете больше, чем за неделю сравнения моделей.

  • Слишком много обсуждали, мало пробовали. Перед стартом было много дискуссий: «как правильно делать бенчмарки», «какие метрики выбрать», «какой подход лучше». В текущую эпоху, когда можно за день навайбкодить рабочий прототип, многие гипотезы быстрее проверить боем, чем обсуждать на созвонах.

  • LLM-судья — калибровка требует терпения. Судья то слишком дотошен до формулировок, придирается к синонимам и засчитывает not covered на корректный ответ, то наоборот, слишком много «думает», находит оправдания и прощает очевидные ошибки. Это две стороны одной проблемы: баланс строгости в промпте судьи. Но промпт — только половина дела. Вторая половина — научиться формулировать сами факты для проверки так, чтобы судья чаще выдавал полезный результат и вы могли ему доверять. Это итеративный процесс: прогнали, посмотрели на расхождения с человеческой оценкой, подкрутили.

  • Эмбеддинги плохо ловят перефразирования. Когда вы проверяете строки через cosine similarity, датасеты должны содержать более-менее однозначные формулировки. Если ожидаемый ответ — «Договор купли-продажи квартиры», а агент написал «ДКП жилого помещения», эмбеддинги могут не справиться, и вы получите ложный отрицательный результат. Учитывайте это при составлении датасетов или используйте LLM-судью вместо эмбеддингов для таких полей.

Заключение

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

Бенчмарки не сделают агента идеальным. Но вы будете видеть тренд: лучше или хуже. А это разница между «вроде работает» и «мы знаем, как работает».

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.