Введение: Наш самый полезный баг
Привет, я Рамиль, QA-инженер в компании Raft. В своей работе я фокусируюсь на автоматизации тестирования, в том числе для LLM-решений, где часто использую связку Pytest и специализированных фреймворков. Эта статья — история из нашей недавней практики.
Когда перед нами встала задача построить автоматизированную систему оценки (evaluation) для LLM-классификатора, который должен был сортировать запросы клиентов, выбор инструментов казался очевидным. Мы взяли DeepEval — это open-source фреймворк, который заслуженно называют "Pytest для LLM". Он позволяет писать кастомные метрики, запускать тесты и оценивать качество ответов языковых моделей, используя для этого другие модели в качестве "судьи" (Judge). В качестве такого судьи мы начали с GPT-4o, поскольку DeepEval изначально "заточен" под экосистему OpenAI, и это был самый прямой и логичный путь.
Однако в проекте были требования информационной безопасности (ИБ): мы должны были убедиться, что наша система сможет работать с моделями, развернутыми в определенном контуре. Так в нашем тестировании появился второй обязательный участник — YandexGPT.
Именно это, на первый взгляд, обычное требование ИБ и привело нас к неожиданному открытию. Сравнивая результаты двух моделей-судей на нашей, как мы думали, простой метрике, мы обнаружили, что они ведут себя кардинально по-разному.
Эта статья — история не о недостатках DeepEval, а о тех уроках, которые мы извлекли, создавая собственные метрики. Это рассказ о том, как "характер" разных LLM-судей может выявить скрытые ошибки в вашем коде и как требования ИБ могут стать катализатором для более глубокого понимания ваших инструментов.
Почему мы начали с метрики Faithfulness
?
Прежде чем погрузиться в детали ошибки, стоит пояснить, почему наш выбор пал именно на эту метрику. Наш LLM-классификатор должен был анализировать запрос клиента и присваивать ему одну из заранее определенных категорий (например, "Покупка", "Возврат", "Технический вопрос").
В этом контексте Faithfulness (дословно, "верность фактам") казалась нам логичной отправной точкой. Мы хотели убедиться, что классификатор основывает свое решение только на информации из запроса клиента, а не додумывает что-то свое. Идея была простой: если в ответе модели (названии категории) содержатся "утверждения", они должны быть подкреплены "контекстом" (исходным запросом).
Как мы увидим дальше, именно эта, казалось бы, правильная логика и завела нас в ловушку, когда мы столкнулись с особенностями разных моделей-судей.
Проблема на практике: как мы ошиблись при локализации метрики
Чтобы понять корень проблемы, давайте посмотрим на код метрики FaithfulnessMetric
. Это не наша разработка с нуля, а, по сути, локализованная версия стандартной метрики Faithfulness
из DeepEval. Так как DeepEval по умолчанию использует промпты на английском, для корректной работы с YandexGPT и нашими русскоязычными данными мы просто перевели его внутренние инструкции на русский. И именно здесь нас ждал сюрприз.
Наблюдение: При адаптации мы слепо скопировали логику, в которой был заложен анти-паттерн: если модель-судья не находила "утверждений" в тексте, метрика по умолчанию выставляла высший балл 1.0
. Это и создало "тихую ошибку": вместо того чтобы сигнализировать о проблеме, система рапортовала об успехе.

Когда в actual_output
прилетала простая строка-категория, например "Покупка"
, модели вели себя по-разному.
Два архетипа: "Прагматичный Помощник" и "Педантичный Профессор"
Чтобы понять разницу, представим две модели в виде двух архетипов.
1. YandexGPT — Прагматичный Помощник
Эта модель стремится понять намерение пользователя. Она видит, что мы хотим что-то проверить, и даже если слово "Покупка" не является "утверждением", она пытается что-то с этим сделать. Она действует по "духу закона".
2. GPT-4o — Педантичный Профессор
Эта модель следует инструкциям с максимальной точностью. Для нее слово "Покупка"
— это имя существительное, а не "фактологическое утверждение". Поэтому она строго по инструкции возвращает пустой список []
, что и активировало нашу ошибку в коде.
Результаты говорят сами за себя
Анализ отчетов на реальных данных (34 примера) наглядно демонстрирует, как наша ошибка в коде по-разному проявилась на двух моделях.
Метрика |
YandexGPT |
GPT-4o |
Комментарий |
Средняя |
61.76% |
91.18% |
GPT-4o показывает высокий балл, потому что наша ошибка в коде это позволяет. |
Типичное |
Утверждение: '' |
В ответе не найдено утверждений для проверки. |
YandexGPT пытается работать, GPT-4o честно говорит, что не может. |
Итог |
Низкая оценка от YandexGPT заставила нас искать ошибку. |
Высокая оценка от GPT-4o маскировала нашу проблему, создавая иллюзию качества. |
Наблюдение: "Педантичность" GPT-4o в итоге сослужила нам хорошую службу — она явно показала, что наш промпт был некорректным. А "прагматизм" YandexGPT, хоть и пытался "помочь", но давал менее однозначный сигнал.
От Faithfulness
к AnswerRelevancy
: поиск правильной метрики
Стало очевидно, что Faithfulness
— неподходящий инструмент для нашей задачи. Проблема была в самой постановке вопроса: мы пытались проверить "фактологическую верность" там, где нужно было оценивать релевантность. Наш классификатор не генерирует развернутый ответ, а выдает короткую категорию. Поэтому ключевой вопрос звучит иначе: "Является ли присвоенная категория адекватной реакцией на запрос клиента?"
Именно для ответа на этот вопрос мы использовали вторую метрику — AnswerRelevancyMetric
. Она не ищет абстрактные "утверждения", а напрямую просит модель-судью оценить смысловое соответствие между запросом и ответом.
Наблюдение: Этот промпт гораздо надежнее, потому что он не содержит абстрактных, академических терминов ("утверждение"), а ставит прямую и ясную бизнес-задачу: "Является ли категория Х адекватной реакцией на фразу Y?".

Пример из реальной жизни
Чтобы было понятнее, как это работает на практике, вот два примера из наших тестов.
Пример 1: Релевантный ответ
- Входные данные (test_case.input
): "Хочу подключить еще один город к своему тарифу"
- Ответ системы (test_case.actual_output
): "Подключение услуг"
- Вердикт модели-судьи (YandexGPT):{
"score": 1.0,
"reason": "Ответ релевантен, так как подключение города является одной из услуг, предоставляемых в рамках тарифа."
}
Пример 2: Нерелевантный ответ
- Входные данные (test_case.input
): "какой остаток по счету"
- Ответ системы (test_case.actual_output
): "Покупка"
- Вердикт модели-судьи (YandexGPT):
{
"score": 0.0,
"reason": "Ответ нерелевантен. Запрос касается информации о балансе, а система выдала категорию, связанную с совершением покупки."
}
Эти примеры наглядно показывают, что метрика AnswerRelevancy
точно решает нашу задачу — оценивает соответствие ответа бизнес-логике, а не абстрактные лингвистические "утверждения".
Бонус: Наша матрица для диагностики ошибок классификации
Чтобы системно анализировать ошибки, мы используем следующую матрицу, которая помогает нам превращать результаты тестов в конкретные задачи для команды.
Точность |
Релевантн-ть |
Консистент-ть |
Диагноз: Что это значит? |
Рекомендация: На что обратить внимание? |
1 |
>= 0.7 |
>= 0.7 |
✅ Идеальный |
Ничего делать не нужно. |
1 |
< 0.7 |
>= 0.7 |
⚠️ "Семантический разрыв". |
Фокус на промпт! Уточнить описание класса. |
0 |
>= 0.7 |
>= 0.7 |
❌ "Близкий промах". |
Самая важная ошибка! Улучшить разграничение классов. |
0 |
< 0.7 |
< 0.7 |
☠️ Полный провал. |
Кандидат №1 на разбор. Что "сломало" модель? |
... |
... |
... |
... |
... |
Выводы: 5 правил для тех, кто оценивает LLM с помощью кастомных метрик
Наш опыт позволяет сформулировать несколько практических правил для всех, кто использует такие фреймворки, как DeepEval, для создания собственных систем оценки.
1. Признайте, что у моделей есть "характер". Промпт — это не универсальный код. Всегда тестируйте свои кастомные метрики как минимум на двух разных моделях (например, от OpenAI и Yandex), чтобы выявить скрытые зависимости от "стиля" интерпретации.
2. Не создавайте "тихих ошибок" в своих метриках. Ваша система оценки не должна молча ставить 1.0
, если что-то пошло не так. Если модель-судья не смогла выполнить инструкцию, это должно быть явной ошибкой (Error
или Score = 0.0
), а не тихим успехом.
3. Пишите промпты для "педанта", а не для "помощника". Всегда стремитесь к максимальной однозначности в своих инструкциях.
* Плохо (зависит от интерпретации): "Извлеки утверждения из ответа".
* Хорошо (однозначно): "Посчитай, сколько предложений в 'Ответе'. Для каждого предложения проверь, содержится ли оно в 'Контексте'".
4. "Характер" модели — это не баг, а фича. Нет "плохой" или "хорошей" модели в этом сравнении. Педантичность GPT-4o идеальна для отладки промптов. Гибкость YandexGPT может быть полезна в других задачах. Выбирайте инструмент под задачу.
5. Доверяй, но проверяй (и логгируй). Всегда логгируйте сырые ответы от моделей-судей. Если бы мы не увидели, что GPT-4o возвращает пустой список []
, мы бы никогда не нашли ошибку в нашем коде и продолжали бы доверять завышенной оценке.
А какие инструменты и подходы используете вы для определения качества классификации на основе LLM? Делитесь своим опытом в комментариях!
ARazum
Классная статья. Таких не хватает сейчас.
Есть 2 вопроса:
1) Пробовали ли вы встроить процесс регулярной валидации в CI/CD, чтобы минимизировать риск ошибок на проде, или вернее, что бы ускорить их обнаружение, после обновлений или если появятся новые промпт-инъекции?
2) Не пробовали ли работать на закрытых моделях, что бы проверка располагалась строго в контуре на открытых моделях?
AllahverdievRamil Автор
Спасибо за комментарий!
Сейчас регулярную валидацию пока реализовали через батч‑запуски, без интеграции в CI/CD – фокус пока был на самой методологии оценки и корректности метрик
Проверять строго на закрытых моделях пока не пробовали – сейчас основной акцент был на возможности сравнительного анализа и гибкости настройки