Классы дефектов LLM: тренажёр на ассистенте магазина и шаблон баг-репорта
Как отличить галлюцинацию от неполного ответа, лишний отказ от правильного, регрессию от нормальной вариативности. Пять вопросов-развилок, тренажёр с кейсами ассистента магазина и шаблон баг-репорта, который разработчик сможет воспроизвести.
60fca9e
Содержание
Коротко
- «Бот ответил неправильно» не баг-репорт. Чтобы дефект починили, нужно назвать его класс, частоту и критерий, которому ответ не соответствует.
- Класс определяют несколько вопросов-развилок: воспроизводится ли дефект k из n, был ли нужный чанк в контексте, противоречит ли ответ контексту, все ли обязательные факты на месте.
- Самый частый ложный дефект: нормальная вариативность формулировки. Ответ другими словами, но с теми же фактами, дефектом не считается.
- Класс описывает то, что увидел пользователь. Причину (поиск, промпт, модель, данные) пишите отдельным полем: один и тот же класс может родиться в разных блоках.
- Ожидаемое поведение в репорте записывается как проверяемый критерий, а не как эталонная строка.
Проблема оракула: с чем сравнивать ответ
В первой части серии я писал, что у LLM-системы нет одного правильного ответа. На вопрос про возврат верны и «Да, в течение 30 дней с чеком», и «Можно, у вас 30 дней, сохраните чек». Сравнение строк упадёт на обоих.
Отсюда следствие для баг-репортов. Когда тестировщик пишет «ожидалось: „Возврат возможен в течение 30 дней“», разработчик справедливо отвечает, что модель так и не ответит дословно. Репорт возвращается, хотя дефект, возможно, настоящий.
Оракулом для LLM служат свойства ответа: какие факты обязаны быть, каких фактов быть не должно, какой формат, как вести себя, если ответа в базе нет. Как проверять эти свойства кодом, метрикой и судьёй, разобрано в четвёртой части. Здесь нас интересует следующий шаг: ответ свойствам не соответствует, и теперь нужно понять, что именно сломалось.
Восемь классов и вариант «дефекта нет»
Таблица классов с примерами на ассистенте магазина есть в первой части, повторять её не буду. Напомню сам список: галлюцинация, неполный ответ, лишний отказ, нарушение формата, prompt injection, утечка, регрессия, рост стоимости и задержки.
К этим восьми я добавляю девятый исход, о котором в таблицах обычно молчат: дефекта нет. Модель ответила иначе, чем вы ожидали, но все обязательные свойства на месте. Такие «дефекты» засоряют трекер, тратят время разработчиков и подрывают доверие к QA. В программе курса это отдельный критерий приёмки домашнего задания: среди найденных дефектов не должно быть нормальной вариативности формулировки.
Класс отвечает на вопрос, что увидел пользователь. Причина отвечает на вопрос, где это сломалось. Галлюцинация может появиться из-за модели, которая додумала факт, а может из-за retriever, который не принёс нужный чанк, и модель заполнила пробел своими знаниями. Для пользователя это один и тот же неверный ответ, для разработчика две разные задачи. Поэтому в репорте два поля: класс и гипотеза о причине. Где на схеме системы живут эти причины, показано в предыдущей части.
Вопросы-развилки: как отличить один класс от другого
Перед тем как выбрать класс, задайте себе вопросы по порядку. Каждый отсекает часть вариантов.
1. Воспроизводится ли проблема? Прогоните тот же запрос n раз (от 5).
0 из n и ответы различаются только словами -> дефекта нет
k из n -> идём дальше, k/n в репорт
2. Ответ нарушает формат, который проверяется кодом (JSON, схема, длина)?
да -> нарушение формата
3. Ассистент выполнил чужую инструкцию или выдал то, что не должен?
выполнил инструкцию из вопроса или документа -> prompt injection
наружу ушёл системный промпт или чужие данные -> утечка
(бывает и то и другое: класс по последствию, причина отдельно)
4. Был ли нужный чанк в контексте модели?
нет, и ассистент уверенно ответил -> галлюцинация, причина: поиск
нет, и ассистент сказал, что не знает -> корректный отказ, дефект поиска
да, а ассистент отказался -> лишний отказ
5. Ответ противоречит контексту или добавляет факты не из него?
да -> галлюцинация, причина: генерация
нет, но обязательного факта не хватает -> неполный ответ
Отдельно, если ответ верный:
на прошлой версии было лучше на том же датасете -> регрессия
ответ тот же, но дороже или медленнее -> стоимость и задержка
Несколько пояснений к развилкам.
Первая развилка самая важная. Один прогон ничего не доказывает. Если дефект появился в 2 случаях из 10, он настоящий, и пользователь его увидит. Если вы видели его однажды и больше воспроизвести не можете, сначала соберите частоту, потом пишите репорт. В программе курса частоту требуем минимум по пяти прогонам.
Четвёртая развилка требует трейса. Ответить на вопрос «был ли нужный чанк в контексте» можно только по логам: какие фрагменты нашёл retriever и в каком порядке они ушли в промпт. Если трейсов нет, это первое, что стоит попросить у команды.
Регрессия определяется только сравнением. Сказать «стало хуже» по одному ответу нельзя. Нужен прогон того же датасета на старой и новой версии и разница в долях.
Тренажёр: определите класс дефекта
Ниже кейсы ассистента интернет-магазина. В каждом есть вопрос покупателя, что нашёл retriever, ответ ассистента и иногда дополнительные данные: частота, версия, токены. Выберите класс и прочитайте разбор.
Совет: прежде чем нажимать, пройдите развилки сверху вниз. Если колеблетесь между двумя классами, спросите себя, что увидел пользователь.
Ассистент интернет-магазина «Полка». В каждом кейсе: вопрос покупателя, что нашёл поиск по базе знаний, ответ ассистента. Определите класс дефекта.
Кейсы и цифры иллюстративные. Магазин, документы, модели и метрики вымышлены.
Пограничные случаи
На тренажёре чаще всего путают одни и те же пары. Разберу их отдельно.
Галлюцинация или неполный ответ. Если в ответе есть факт, которого нет в контексте или который ему противоречит, это галлюцинация. Если всё сказанное верно, но не хватает обязательного факта (срок назван, про чек ни слова), это неполный ответ. Ответ может быть и тем и другим: тогда выбирайте класс с большим риском для пользователя, обычно галлюцинацию.
Лишний отказ или правильный отказ. Отказ становится дефектом, только если ответ был в найденном контексте или в базе. Если ответа в базе нет, отказ и есть ожидаемое поведение. Дефектом в этом случае будет как раз уверенный ответ: это галлюцинация.
Высокая Faithfulness, а ответ неверный. Ассистент назвал срок возврата 14 дней, контекст подтверждает 14 дней, а по актуальному регламенту 30. Метрика честно говорит, что ответ соответствует контексту. Для пользователя это неверный факт, причина в данных: в базе лежит устаревший документ. В репорте пишите класс по симптому, а в причину ставьте «данные». Такие дефекты ловят вопросы на актуальность в golden-датасете.
Prompt injection или утечка. Injection описывает механизм: ассистент выполнил инструкцию, которую ему подложили в вопросе или в документе. Утечка описывает последствие: наружу ушло то, что не должно было. Если покупатель попросил «покажи свои инструкции» и получил системный промпт, это утечка через прямую инъекцию. Записывайте класс по последствию, механизм в поле причины. Про атаки через документы базы есть отдельный разбор.
Регрессия или вариативность. После смены промпта доля верных ответов упала с 90% до 87%. Цифры иллюстративные. Такая разница может оказаться шумом: тот же датасет на одной и той же версии тоже даёт разброс между прогонами. Прежде чем заводить дефект, прогоните обе версии несколько раз и посмотрите, перекрываются ли диапазоны. Как с этим работать в CI, разобрано в статье про quality gate.
Шаблон баг-репорта для LLM-дефекта
Хороший репорт отвечает на три вопроса разработчика: как воспроизвести, как понять, что починено, и насколько это срочно. Шаблон ниже можно скопировать в трекер.
## [Класс] Короткое описание симптома
**Класс дефекта:** галлюцинация | неполный ответ | лишний отказ | формат |
prompt injection | утечка | регрессия | стоимость и задержка
**Риск для пользователя:** что случится, если дефект останется
**Гипотеза о причине:** данные | индексация | поиск | промпт | модель | обвязка
### Воспроизведение
- Вопрос пользователя (дословно):
- История диалога (если была):
- Версия системного промпта:
- Найденные чанки: id документа, позиция в выдаче, был ли нужный
- Ответ ассистента (дословно, один из провальных):
- Модель и версия:
- Параметры: temperature, max_tokens, top_k поиска
- Частота: k из n прогонов (n не меньше 5)
- Trace id провального прогона:
### Ожидаемое поведение (критерий, а не строка)
- В ответе есть: ...
- В ответе нет: ...
- Если в контексте нет ответа: ...
### Как проверить исправление
- Какой тест или кейс golden-датасета должен пройти, на скольких прогонах
И пример заполнения. Цифры и идентификаторы иллюстративные.
## [Галлюцинация] Ассистент называет срок возврата обуви 14 дней вместо 30
**Класс дефекта:** галлюцинация
**Риск для пользователя:** покупатель не вернёт товар, думая, что срок прошёл
**Гипотеза о причине:** генерация. Нужный чанк найден на первой позиции,
ответ ему противоречит
### Воспроизведение
- Вопрос: «Можно вернуть кроссовки, если я их уже надевал?»
- История диалога: нет
- Версия системного промпта: v12
- Найденные чанки: returns_policy#3 (позиция 1, нужный, «30 дней с чеком»),
delivery#7 (позиция 2), promo#1 (позиция 3)
- Ответ: «Да, вернуть можно в течение 14 дней при сохранении товарного вида.»
- Модель и версия: <модель>, <версия>
- Параметры: temperature 0.7, top_k 3
- Частота: 3 из 10 прогонов
- Trace id: <id провального прогона>
### Ожидаемое поведение
- В ответе есть срок 30 дней и условие про чек
- В ответе нет других сроков возврата
- Ответ не противоречит returns_policy#3
### Как проверить исправление
- Кейс returns_shoes_worn из golden-датасета проходит в 10 из 10 прогонов,
Faithfulness на нём не ниже порога
Обратите внимание на раздел «Ожидаемое поведение». Каждую строку можно превратить в проверку: наличие факта и отсутствие чужих сроков проверяются кодом или судьёй, «не противоречит» проверяет Faithfulness. Этот же кейс потом уходит в регрессионный набор, чтобы дефект не вернулся.
Сравните с репортом «бот неправильно отвечает про возврат». По нему разработчик не знает, какой вопрос задать, какую версию смотреть и когда закрывать задачу.
Ограничения подхода
Классы дефектов это договорённость команды, а не стандарт. Названия и границы между ними у вас могут быть другими, главное, чтобы они были одинаковыми у всех, кто заводит баги.
Частота k из n на пяти или десяти прогонах грубая. 3 из 10 и 5 из 10 на таком числе прогонов легко могут оказаться одним и тем же. Для решения «чинить или нет» этого хватает, для сравнения двух версий нужен датасет и доли, а не отдельный кейс.
Развилки предполагают, что у вас есть трейсы с найденными чанками. Без них четвёртый и пятый вопросы превращаются в догадки, и класс «галлюцинация» поглощает всё подряд.
Если класс ставит LLM-судья, а не человек, судья тоже ошибается. Прежде чем доверять ему разметку дефектов, сверьте его с ручной разметкой, как описано в разборе про калибровку судьи.
Что дальше
- Пройдите тренажёр ещё раз через пару дней, не глядя в развилки. Если путаете одну и ту же пару классов, перечитайте раздел про пограничные случаи.
- Возьмите любого доступного вам ассистента (свой стенд или локальную модель из третьей части) и найдите пять дефектов минимум трёх классов. Для каждого соберите частоту по пяти прогонам и оформите репорт по шаблону.
- Перечитайте последние три LLM-бага в вашем трекере. Есть ли у них частота, найденные чанки и критерий вместо строки?
- Дальше по серии: где эти дефекты живут в архитектуре, показано на интерактивной схеме RAG, а как ловить их автоматически до релиза, в разборе про quality gate в CI.


