AI-инжиниринг
Error Handling для LLM: hallucinations, timeouts, malformed output, fallbacks
Цели урока
- Классифицировать типы отказов LLM: timeout, rate limit, malformed output, hallucination
- Реализовать retry с exponential backoff, jitter и model fallback
- Построить слой валидации output через Zod и JSON repair
- Применять self-consistency и grounding check для обнаружения галлюцинаций
- Спроектировать graceful degradation с иерархией fallback-ов
LLM API падает по-другому чем REST API. 429 - не ошибка, норма: подожди и повтори. 500 - часто временно: retry помогает. Но главная ошибка LLM невидима: модель вернула 200, JSON валидный - и при этом полностью солгала. Американский юрист в 2023 году подал иск с 6 несуществующими прецедентами, сгенерированными GPT. Судья заметил. Юрист получил штраф 5000 долларов. Модель не вернула ошибку. Она вычислила следующий токен.
- Air Canada: чат-бот пообещал скидку на билет, которой не существовало - суд обязал компанию выполнить обещание бота
- Chevrolet: бот-продавец согласился продать автомобиль за 1 доллар после prompt injection от покупателя
- Samsung: сотрудники загрузили конфиденциальный исходный код в ChatGPT - утечка через training data
- Stack Overflow: временно запретил GPT-ответы - 80% проверенных ответов содержали фактические ошибки
Почему «галлюцинация» стала инженерной проблемой
Термин **галлюцинация (hallucination)** пришёл из исследований natural language generation, где он описывал гладкий текст, который не соответствует источнику или попросту неверен. Когда LLM дошли до продакшена, это перестало быть академическим курьёзом: модель может вернуть 200 с валидным JSON и при этом выдумать факты, цитаты или API. Инженеры ответили, заимствуя паттерны надёжности из распределённых систем - **retries с backoff, fallbacks и circuit breakers** - и применяя их к вызовам LLM вместе с валидацией вывода, чтобы ловить уверенные, но ошибочные ответы. Error handling для LLM - это столько же про правдоподобный, но ложный вывод, сколько про таймауты и 429.
Предварительные знания
Режимы отказа LLM: timeout, rate limit, malformed output, hallucination
Обычные API возвращают ошибку или корректный ответ. Два исхода. LLM добавляет третий - **формально корректный, фактически ложный ответ**. Это не баг реализации. Это фундаментальное свойство вероятностной генерации.
429 при работе с OpenAI - не катастрофа. Это сигнал: притормози, подожди, повтори. 500 от API чаще всего исчезает через секунду. Но `finish_reason: 'length'` - это тихая бомба. Ответ выглядит нормально. JSON начался. Просто не закончился.
| Тип отказа | Пример | Обнаружимость |
|---|---|---|
| Timeout | Модель генерирует длинный ответ, соединение обрывается через 30с | Легко - HTTP timeout |
| Rate limit (429) | Превышен лимит запросов в минуту или токенов в минуту | Легко - HTTP status code |
| Malformed output | Запрошен JSON, получен текст с markdown-обёрткой ```json...``` | Средне - нужен парсинг |
| Hallucination | Модель уверенно называет несуществующую функцию API | Сложно - нужна верификация |
| Partial response | finish_reason: 'length' - ответ обрезан по max_tokens | Легко - проверка finish_reason |
**finish_reason: 'length'** - одна из самых коварных ошибок. Ответ выглядит нормально, но обрезан на середине. JSON без закрывающей скобки, список без последних пунктов, код без return. Всегда проверяйте finish_reason перед парсингом.
Главный инсайт: error handling для LLM - это не только `catch`. Это **валидация каждого 200 OK**. Модель вернула успех - начинается настоящая работа. Является ли ответ полным? Соответствует ли формату? Не выдуман ли он?
Если API вернул 200 - всё хорошо
200 OK означает только то, что HTTP-запрос прошёл. Семантические ошибки - галлюцинации, truncated output, невалидный JSON внутри строки - в HTTP-статусе не отражаются
LLM генерирует вероятностный текст. Модель не 'знает', что солгала - она вычислила наиболее вероятный следующий токен. Валидный HTTP при этом гарантирован. Валидный смысл - нет никогда.
Почему error handling для LLM сложнее, чем для обычных REST API?
Retry-стратегии: exponential backoff, jitter, model fallback
429 от OpenAI - это не отказ. Это вежливая просьба подождать. Проблема в том, что 1000 параллельных запросов получили этот 429 одновременно. И если все они ретраят через ровно 2 секунды - сервер снова получает удар в 1000 запросов. Thundering herd. Сам себе создал DDoS.
Решение: **exponential backoff с jitter**. Задержка растёт экспоненциально (1с, 2с, 4с, 8с...), плюс случайный разброс. 1000 клиентов расползаются по времени. Нагрузка сглаживается. Именно так устроен retry в AWS SDK, в Google Cloud клиентах, в OpenAI SDK начиная с версии 4.
**Jitter** решает проблему thundering herd: если 100 клиентов получили 429 одновременно и все ретраят через ровно 2 секунды - они снова ударят сервер одновременно. Случайный разброс (jitter) распределяет ретраи во времени.
**Model fallback** - цепочка gpt-4o → gpt-4o-mini → claude-haiku - стандарт для production AI-бекенда. GPT-4o упал? Переключаемся автоматически. Claude дорого для простой задачи? gpt-4o-mini в 16 раз дешевле. Абстракция LLMProvider делает это переключение незаметным для бизнес-логики.
Для malformed output - отдельная стратегия: **retry с усилением промпта**. Первая попытка - обычный промпт. Ретрай - добавляется жёсткое требование: «Предыдущий ответ был невалидным JSON. Верни ТОЛЬКО JSON, без markdown-обёрток, без пояснений». Часто работает с первого ретрая.
Зачем добавлять jitter к exponential backoff?
Валидация output: Zod parsing, JSON repair, structured output
LLM генерирует текст. Бизнес-логика ожидает типизированные данные. Между ними - пропасть. Модель попросили вернуть JSON, она вернула что-то похожее на JSON, обёрнутое в ```json, с trailing comma перед `}`, и комментарием «Вот ваш результат:» в начале.
Слой валидации - это конвейер из трёх шагов. Сначала извлечь JSON из любой обёртки. Затем починить то, что можно починить. Затем прогнать через Zod-схему. Каждый шаг - отдельная линия защиты.
Для случаев, когда JSON «почти правильный» - пропущена запятая, лишняя запятая перед `}`, незакрытая кавычка - существует библиотека **jsonrepair**. Она покрывает 90% реальных случаев невалидного LLM output:
**Structured Output (strict: true)** решает проблему формата, но НЕ решает проблему содержания. Модель вернёт валидный JSON с sentiment: 'positive' - но sentiment может быть неправильным. Формат - это HTTP 200. Корректность - совсем другой вопрос.
Что делает Zod-валидация при обработке ответа LLM?
Детекция галлюцинаций: confidence scoring, self-consistency, grounding
В 2023 году американский юрист подал иск, в котором GPT сгенерировал 6 судебных прецедентов. Дела звучали убедительно, цитаты были оформлены правильно, номера дел - реалистичны. Ни одного из них не существовало. Модель не знала, что галлюцинирует. Она вычисляла вероятный следующий токен.
Галлюцинация - самый коварный тип ошибки. В production это не баг, а **системный риск**. Нет ни HTTP-статуса, ни exception, ни stack trace. Есть уверенный ответ с несуществующими фактами.
- **Self-consistency** - задать один вопрос N раз с temperature > 0. Если ответы расходятся - модель не уверена
- **Grounding check** - проверить, основан ли ответ на предоставленном контексте (RAG) или взят «из головы»
- **Confidence scoring** - попросить модель оценить уверенность (0-1) и отклонить ответы ниже порога
- **Cross-model verification** - запросить ответ у двух моделей и сравнить
- **Fact extraction + lookup** - извлечь утверждения из ответа и проверить по базе данных
**Self-consistency с 3 запросами** стоит 3x. Для медицины, финансов, юридических документов - это оправдано: цена ошибки выше цены трёх вызовов API. Для менее критичных задач хватит grounding check - 1 дополнительный вызов за разумные деньги.
Как работает self-consistency check для обнаружения галлюцинаций?
Graceful degradation: fallback responses, cached answers, human handoff
Все ретраи провалились. Модель галлюцинирует. Timeout истёк. Что показывает система пользователю? Ошибку 500? Пустой экран? Или - честный, понятный ответ: «Не смогли обработать сейчас, передали специалисту».
**Graceful degradation** - это иерархия: от идеального к минимально приемлемому. GPT-4o с полным контекстом - идеал. Статичный заранее написанный ответ - минимум. Между ними - несколько ступеней, каждая из которых лучше ошибки.
**Пример для customer support бота:**
**Метрика source** - золото для мониторинга. 95% ответов из primary - всё в порядке. 20% из cache или fallback - что-то сломалось, нужно расследование. Это лучший ранний индикатор деградации качества AI-системы - задолго до жалоб пользователей.
Что является ПОСЛЕДНИМ fallback-ом в цепочке graceful degradation?
Если API вернул 200 - всё хорошо
200 OK означает только то, что HTTP-транспорт сработал. Галлюцинации, truncated output, семантически неверный JSON - всё это приходит с кодом 200
LLM - вероятностный генератор текста. Он не знает 'правды', он вычисляет наиболее вероятный токен. Формально успешный HTTP-ответ и семантически корректный контент - две независимые вещи. Поэтому каждый 200 нужно валидировать так же тщательно, как любую ошибку.
Итоги
- LLM может вернуть 200 OK с галлюцинацией - проверка HTTP-статуса недостаточна
- finish_reason: 'length' - обрезанный ответ, опасный для JSON-парсинга
- Exponential backoff + jitter + model fallback (gpt-4o → gpt-4o-mini → claude-haiku) - защита от всех retryable-ошибок
- Zod + jsonrepair - надёжный парсинг невалидного LLM output
- Self-consistency (N запросов) и grounding check - обнаружение галлюцинаций
- Graceful degradation: primary → secondary → cache → fallback → human handoff
Что дальше
Error handling защищает от технических сбоев. Но LLM может генерировать токсичный, небезопасный или манипулятивный контент - и это уже задача guardrails.
- Guardrails: безопасность LLM — Input/output фильтрация, NeMo Guardrails, defense-in-depth
- Observability AI pipeline — Мониторинг ошибок, quality drift, cost tracking в production
- Стоимость и оптимизация — Self-consistency стоит 3x - когда оправдано, а когда избыточно
Связанные уроки
- aie-07-structured-output — Битый JSON - ключевой отказ для обработки
- aie-33-guardrails — Обработка ошибок подпирает работу guardrails
- aie-30-rate-limiting-ai — Retry с backoff на 429 и таймаутах
- net-66-resilience — Применяем retry, таймауты и circuit breaker
- aie-29-cost-management — Retry без лимитов умножают стоимость
- sd-03-scalability