Runbook для LLM-инцидентов: таймауты, refusals и сбои
Runbook для LLM-инцидентов помогает команде быстро гасить таймауты, всплеск refusals, сбои structured output и недоступность провайдера.

Почему без runbook команда теряет время
Один и тот же симптом легко уводит команду не туда. Пользователь видит таймаут или пустой ответ, а причин может быть несколько: модель стала отвечать медленнее, провайдер режет запросы, ваш сервис уперся в очередь, валидатор схемы гоняет лишние повторы. Снаружи это выглядит одинаково. Внутри это четыре разных инцидента.
Без понятного шаблона дежурная смена почти всегда теряет первые минуты на споры. Один инженер смотрит на latency и винит провайдера. Другой вспоминает недавнюю смену промпта и считает, что выросли refusals. Третий уже тянется к откату релиза. Пока команда спорит, очередь растет, а SLA проседает.
Это особенно заметно в системах с несколькими слоями. Если запрос идет через API-шлюз вроде RU LLM, сам таймаут еще ничего не доказывает. Проблема может быть в модели, у внешнего провайдера или в вашей логике повторов и парсинга ответа.
Хороший runbook задает один и тот же порядок действий для любой смены: сначала зафиксировать, что именно видит пользователь, потом проверить, что изменилось за последний час, отделить сбой модели от сбоя провайдера и собственного сервиса, а после этого включить временный обходной путь, который сразу уменьшает ущерб.
Во время инцидента не нужно сразу искать виноватого. Цель проще: быстро вернуть сервис в норму, а точную причину добрать после стабилизации. Поэтому runbook нужен не для красивой документации. Он экономит те самые 10-20 минут, которые обычно стоят дороже всего.
Когда порядок проверок уже прописан, смена не гадает. Она последовательно отсеивает версии, быстрее включает резервный маршрут и реже ломает систему резкими ручными действиями.
Что зафиксировать до первого инцидента
До первой аварии команде нужна не общая теория, а короткая карта системы. В ней должны быть модели, провайдеры и запасные маршруты: какая модель идет по умолчанию, на что переключаться при росте ошибок, какой маршрут дороже, а какой безопаснее по данным. Если вы работаете через единый шлюз вроде RU LLM, рядом удобно держать связку "модель - провайдер - резерв" и base_url для каждого контура.
Отдельно запишите норму. Без нее любой спор быстро упрется в ощущения. Зафиксируйте обычную задержку по p50 и p95, долю 4xx и 5xx, частоту таймаутов, долю refusals и процент невалидного structured output. Лучше разбить это по типам задач: чат, классификация, извлечение данных, генерация JSON.
Много времени съедает дрейф промптов. Храните версии системных и пользовательских промптов, JSON Schema, парсеров и валидаторов в одном месте. Если команда меняла temperature, max_tokens или retry policy, это тоже должно попасть в журнал изменений. Иначе через 20 минут никто не поймет, ошибка пришла от модели, от свежего релиза или от нового правила валидации.
Еще до инцидента назначьте владельца смены. Не "дежурную команду" вообще, а конкретного человека. Рядом укажите канал связи, кого будить при эскалации и кто принимает решение о переключении провайдера.
Полезно заранее подготовить и шаблон журнала инцидента. Ему хватает пяти полей:
- время начала и время изменения симптома
- что видит пользователь или сервис
- первая гипотеза
- что уже проверили и что исключили
- какое решение приняли и что поменяли
Такой runbook экономит не часы, а первые 10-15 минут. Обычно именно они решают, быстро ли команда найдет причину или будет метаться между промптом, валидатором и провайдером.
Как идти по runbook во время инцидента
Сначала проверьте, что сбой реален, а не шум в одной очереди или на одном тесте. Возьмите 5-10 свежих запросов за последние минуты и посмотрите ответ целиком: статус, latency, текст refusal, ошибки парсинга, размер промпта, выбранную модель. Один старый пример почти всегда сбивает с пути.
Потом разрежьте метрики по трем осям: модель, провайдер, тип задачи. Проблема редко бывает общей. Таймауты могут расти только на длинных генерациях, refusals - только на шаблоне с новыми инструкциями, а structured output - только на одной модели после смены версии.
Дальше нужен простой порядок. Подтвердите симптом на нескольких новых запросах, быстро оцените масштаб, сравните доли ошибок по моделям и провайдерам, а если SLA уже в красной зоне, сразу включайте деградацию. После этого запишите первую гипотезу и раздайте проверки по людям.
Масштаб лучше оценивать грубо, но быстро. Если падает только one-shot классификация, не трогайте суммаризацию и чат. Если сбой идет по одному провайдеру, не объявляйте общий инцидент раньше времени. Такая дисциплина избавляет от лишних переключений.
Когда сервис уже проседает, не ждите полного диагноза. Сокращайте max_tokens, отключайте необязательный structured output, переводите часть трафика на запасную модель или другого провайдера. Если вы работаете через RU LLM, это часто можно сделать на уровне маршрута, не меняя SDK, код и промпты.
У каждой гипотезы должен быть владелец и срок проверки. Один человек смотрит сеть и таймауты, второй проверяет недавние изменения промпта, третий сравнивает поведение на соседнем провайдере. Это полезнее длинных описаний в wiki: пока все понимают, кто что делает в ближайшие 15 минут, инцидент движется вперед.
Если растут таймауты
Когда растут таймауты LLM, среднее время ответа почти бесполезно. Смотрите p50, p95 и p99 отдельно. Часто p50 еще выглядит нормально, а p95 и p99 уже показывают очередь, перегруженный провайдер или слишком тяжелые запросы.
Сразу запишите момент, когда задержка вышла за норму. Потом это сэкономит время при разборе. Вы сможете связать всплеск с релизом, ростом трафика, новой моделью или изменением промпта, а не вспоминать все по памяти.
Дальше разделите проблемные запросы по размеру. Сравните длину контекста, prompt_tokens и max_tokens у медленных и обычных вызовов. Частая причина банальна: в прод попал длинный системный промпт, к нему добавили большие документы, а max_tokens оставили с запасом. Модель отвечает дольше, очередь растет, и таймауты идут цепочкой.
Рабочий порядок здесь такой:
- смотрите p50, p95 и p99 по каждой модели и каждому провайдеру
- сравнивайте prompt_tokens, длину контекста и max_tokens у успешных и зависающих запросов
- находите участок, где параллельность давит сильнее всего, и временно снижайте ее
- переводите часть трафика на резервный маршрут или другую модель
С параллельностью лучше действовать грубо и быстро. Если одна очередь растет быстрее остальных, не пытайтесь сразу чинить все. Снизьте concurrency там, где запросы копятся, и посмотрите, падает ли p95 в течение нескольких минут.
Если вы используете RU LLM, часть трафика можно увести на другой маршрут через тот же OpenAI-совместимый эндпоинт. Это особенно помогает, когда один провайдер отвечает медленно, а другой держит нормальную задержку на похожем качестве.
Не ждите полного отказа. Если p99 уже ушел далеко вверх, а очередь не уменьшается, режим "понаблюдаем еще" почти всегда только увеличивает потери и число повторных запросов.
Если растет доля refusals
Резкий рост refusals редко бывает случайным. Обычно модель начала читать обычный запрос как рискованный после смены промпта, шаблона роли, модели или провайдера.
Сначала сравните свежие отказы с последней стабильной версией промпта. Возьмите один и тот же набор безопасных запросов и прогоните его через текущий и прошлый вариант. Если старая версия отвечает нормально, а новая уходит в отказ, причину стоит искать в инструкциях, а не в трафике.
Сразу разделите отказы на две группы. Первая - нормальные отказы на действительно рискованных запросах. Вторая - лишние отказы там, где задача безопасна. Если модель раньше спокойно извлекала реквизиты из письма, а теперь пишет, что не может работать с персональными данными даже после маскирования, это уже не норма.
Проверьте, что именно менялось вокруг запроса: системный промпт, шаблон роли, модель, версия модели, провайдер, параметры вроде temperature и response format, а также фильтры до и после вызова модели.
После этого соберите 5-10 пар удачных и неудачных ответов. Для каждой пары сохраните входной текст, модель, версию промпта, провайдера и полный текст refusal. Без таких примеров команда обычно спорит о причинах вместо проверки.
Если продукт нельзя долго держать в деградации, подготовьте запасной путь заранее. Это может быть более короткий системный промпт без лишних запретов или соседняя модель для той же задачи.
Если вы работаете через RU LLM, проблемный поток можно быстро перевести на другого провайдера или модель через тот же OpenAI-совместимый endpoint. Первопричину это не убирает, но часто снимает давление с дежурной смены и дает время спокойно проверить шаблоны и регрессионные примеры.
Если ломается structured output
Когда JSON или другой структурированный ответ внезапно перестает проходить проверку, не начинайте с догадок про модель. Сначала сверяйте схему: какая версия ожидалась, какие поля обязательны, какие типы и enum проверяются прямо сейчас. Очень часто один сервис уже ждет новый формат, а другой все еще живет на старом.
Дальше ловите не только 500 в приложении, а первые сырые невалидные ответы. Нужен сам текст ответа модели, результат парсинга и сообщение валидатора хотя бы по одному запросу. Иначе вы видите только симптом в приложении, но не понимаете, где именно сломалась цепочка. Если у вас есть audit trail на каждый запрос, как в RU LLM, связать эти куски заметно проще.
Проверка обычно идет по короткой схеме:
- сравнить ожидаемую schema_version и фактический формат ответа
- проверить, какие обязательные поля исчезли первыми
- отделить ошибку модели от ошибки парсера, санитайзера или валидатора
- найти 3-5 первых невалидных ответов, а не смотреть только среднюю статистику за час
На практике разница простая. Если модель возвращает почти корректный JSON, а парсер падает на лишней запятой, это один класс проблемы. Если JSON синтаксически верный, но поле items стало строкой вместо массива, проблема уже в генерации. Если валидатор ругается на новое обязательное поле, которое команда добавила утром, модель вообще может быть ни при чем.
После этого включайте повтор с короткой инструкцией на исправление. Не переписывайте весь промпт. Часто хватает фразы: "Верни только валидный JSON по схеме ниже. Не добавляй текст вне JSON. Исправь типы полей". Один повтор нередко закрывает всплеск без лишней нагрузки.
Ослаблять строгую проверку можно только там, где ошибка не ломает бизнес-логику. Например, можно временно принять пустой comment или неизвестный необязательный тег. Но нельзя молча проглотить неверную сумму, статус заявки или идентификатор клиента. Здесь правило простое: либо корректный ответ, либо явный отказ с понятной причиной.
Если провайдер недоступен
Когда модель перестает отвечать, не называйте это сразу падением провайдера. Сначала разберите ошибки по типу. Код 401 обычно указывает на токен, подпись запроса или сбой в авторизации. 429 чаще связан с лимитами и всплеском нагрузки. 5xx обычно означает проблему на стороне провайдера. Сетевые ошибки вроде connect timeout, DNS failure или TLS error часто возникают между вашим сервисом и API.
В runbook стоит держать короткую развилку:
- при 401 проверить ключ, заголовки, недавнюю ротацию секретов и base_url
- при 429 снизить burst, включить очередь и посмотреть, не уперлись ли вы в квоту
- при 5xx перевести часть трафика на резервный маршрут и проверить, держится ли качество ответа
- при сетевых ошибках проверить DNS, egress, firewall, TLS и маршрут до API
Потом запустите ту же задачу через второго провайдера. Возьмите один и тот же промпт, те же параметры и тот же формат ответа. Если второй маршрут жив, проблема почти наверняка в конкретном провайдере, а не в вашем приложении. Если не отвечает и он, ищите причину у себя: в релизе, сети, прокси или схеме ретраев.
Переключать трафик на глаз не стоит. Лучше задать порог заранее. Например, если доля 5xx держится выше 3% пять минут подряд или p95 по задержке вырос вдвое, переведите 50% запросов на резервный маршрут. Если ошибка не падает, уводите почти весь трафик.
Пока метрики не вернулись в норму, заморозьте новые выкладки. Иначе команда смешает два источника сбоя и потеряет время. Если вы работаете через единый шлюз вроде RU LLM, отдельно фиксируйте, где именно случилась проблема: авторизация, сеть до шлюза, апстрим-провайдер или сам маршрут переключения. Потом разбирать инцидент будет заметно проще.
Короткий пример из рабочей смены
В 09:12 утренняя смена заметила, что чат поддержки отвечает медленнее обычного. Жалоб было еще мало, но p95 по времени ответа уже полз вверх, а очередь запросов начала расти.
Через несколько минут картина стала хуже:
- в 09:22 таймауты выросли, особенно на длинных диалогах
- в 09:24 часть ответов перестала проходить проверку JSON, хотя модель все еще отвечала
- в 09:27 дежурный перевел 30% трафика на резервный маршрут и урезал контекст в самых длинных сценариях
- в 09:35 поток ошибок перестал расти, а время ответа вернулось ближе к норме
Это сработало не потому, что команда угадала причину. В runbook уже был прописан простой порядок: сначала проверить, где растет задержка, потом снизить нагрузку на проблемный маршрут и только после этого разбирать поломку structured output. Если делать наоборот, смена тратит лишние 15-20 минут и спорит о причине вместо того, чтобы держать сервис живым.
Первопричину нашли быстро. Ночью в релиз попал новый валидатор, и он стал строже к полям, которые раньше проходили с мелкими отклонениями. Модель отдавала почти тот же смысл, но несколько ответов не совпадали со схемой до символа. На фоне роста задержек это выглядело как одна большая авария, хотя проблем было две: часть запросов упиралась в таймаут, часть ломалась на постобработке.
После стабилизации команда вернула старую схему валидации, оставила сокращенный контекст только для тяжелых кейсов и оформила короткий разбор. В нем записали три вещи: какой график сработал как ранний сигнал, какой процент трафика можно безопасно уводить на резервный маршрут и какой тест должен был поймать новый валидатор еще до релиза.
Где команды ошибаются чаще всего
Больше всего времени теряется в первые 10 минут, когда команда начинает чинить не тот слой. Типичный сценарий: инженеры сразу правят промпт, меняют temperature или ужесточают инструкции, хотя проблема вообще не там. Сначала надо проверить здоровье провайдера, маршрут, очередь запросов, медиану и p95 задержки, долю ошибок по моделям.
Эта ошибка особенно дорогая, когда трафик идет через несколько моделей или провайдеров. Если смотреть на весь поток сразу, легко получить ложную картину. Один провайдер может деградировать, а другой работать нормально, но в общей сводке это выглядит так, будто "модель стала хуже". Смешанный трафик почти всегда мешает разбору. Сравнивайте только однородные срезы: одна модель, один провайдер, один тип запроса, один период времени.
Еще одна частая проблема: смена не фиксирует базовые факты в момент первого сбоя. Через час люди уже спорят по памяти, а не по данным. В карточке инцидента сразу должны появиться время первого сбоя, request_id или набор request_id по примеру, версия схемы structured output, модель, провайдер и маршрут запроса.
Когда ломается structured output, команды иногда идут по самому опасному пути и просто отключают валидацию целиком. На графиках становится "лучше", потому что ошибок меньше, но в систему уже проходят битые ответы. Потом ломаются парсеры, бизнес-правила и downstream-сервисы. Намного безопаснее временно сузить функциональность: перейти на более простой JSON, уменьшить число обязательных полей или включить мягкий fallback только для безопасных сценариев.
Последняя ошибка выглядит безобидно: инцидент закрыли, сервис снова отвечает, все разошлись. Если после этого никто не заводит задачи на автоматические проверки, история повторится в следующую смену. Нужен хотя бы минимум: alert на скачок refusals, проверка валидности схемы на тестовом наборе и отдельный мониторинг доступности провайдера. Если после стабилизации в runbook ничего не добавили, команда просто отложила тот же сбой на потом.
Короткая проверка после стабилизации
Считать инцидент закрытым рано, если график просто перестал расти. Нужен короткий контрольный проход, чтобы команда не оставила скрытую поломку в проде и не откатила временные меры слишком резко.
Смотрите не на один общий дашборд, а на несколько признаков сразу:
- ошибки и задержка вернулись к обычному уровню не только в среднем, но и по критичным маршрутам, регионам и крупным сценариям
- доля refusals снова в норме по каждому сценарию отдельно
- structured output проходит без ручных правок, лишних повторов и аварийных парсеров
- резервный маршрут выключается по шагам, а не одним движением
- карточка инцидента заполнена до конца: время начала, симптом, временная мера, что сработало, что не сработало, кто принимал решение
На практике команды часто ошибаются в одном месте: видят, что таймауты упали, и сразу возвращают весь трафик на основной маршрут. Если проблема у провайдера шла волнами, сервис снова падает через 10 минут. Если вы вели трафик через резервного провайдера или другой маршрут в RU LLM, возвращайте нагрузку частями и фиксируйте, на каком проценте система осталась стабильной.
Есть и простая финальная проверка. Дайте runbook человеку не из дежурной смены и попросите его восстановить картину инцидента только по карточке и логам. Если он путается, документ все еще слишком расплывчатый, и в следующем сбое команда снова начнет вспоминать детали по памяти.
Что добавить в runbook на следующей неделе
Хороший runbook не заканчивается на списке шагов. Его нужно дополнять после каждой смены, пока детали еще свежие. Иначе через месяц команда снова спорит, кто может переключить трафик, где смотреть логи и какой шаблон брать для проверки JSON.
Сначала пропишите пороги, после которых смена действует без отдельного согласования. Например: таймауты держатся выше заданного уровня 10 минут, доля refusals выросла в несколько раз, structured output не проходит валидацию заметно чаще обычного. Такие правила убирают паузу на переписку и экономят команде время в самый неприятный момент.
Дальше держите рядом короткие карточки под типовые сбои. В них полезно зафиксировать, что проверить в первые 5 минут, какие метрики сравнить с базовой линией, когда переключать модель или провайдера, что писать в статус-канале и что сохранить для разбора после инцидента.
Раз в квартал полезно прогонять учебный сценарий на тестовом контуре. Не общий созвон, а короткую практику: одна команда ловит рост таймаутов, другая разбирает всплеск refusals, третья проверяет падение JSON-схемы. После такой тренировки runbook обычно становится короче и точнее.
Если у вас единый шлюз, вроде RU LLM, добавьте в документ конкретные правила маршрутизации. Кто и при каком сигнале переводит трафик на другого провайдера, какие модели разрешены как резервные, где смотреть audit trails по запросам, как выгружать логи, которые хранятся в РФ, и как проверять маскирование PII. Для команд с жесткими требованиями к данным это практический вопрос: во время инцидента люди должны сразу понимать, где след запроса и какие данные можно передавать дальше в разбор.
Последний штрих совсем простой: после каждого инцидента добавляйте в runbook один новый порог, один новый шаблон или одно уточнение по логам. Этого обычно хватает, чтобы документ жил, а не пылился в wiki.
Часто задаваемые вопросы
Зачем вообще нужен runbook для LLM-инцидентов?
Runbook убирает споры в первые минуты сбоя. Смена не гадает, кто виноват, а идет по одному порядку: фиксирует симптом, проверяет недавние изменения, отделяет модель от провайдера и своего сервиса, потом включает временный обход.
Обычно это экономит 10–20 минут. Для LLM-сервиса это часто дороже любой красивой документации: пока команда спорит, растет очередь, падает SLA и копятся повторы.
Что подготовить до первого инцидента?
Держите короткую карту системы: какая модель идет по умолчанию, какой провайдер за ней стоит, куда переводить трафик при сбое и какой маршрут дороже или строже по данным.
Сразу запишите норму по p50, p95, 4xx, 5xx, таймаутам, refusals и невалидному structured output. Храните версии промптов, JSON Schema, парсеров, валидаторов и параметров вроде temperature, max_tokens и retry policy. Еще назначьте одного владельца смены и шаблон карточки инцидента.
С чего начинать, когда инцидент уже идет?
Начните с 5–10 свежих запросов за последние минуты. Смотрите не только статус, но и полный ответ: задержку, размер промпта, текст refusal, ошибки парсинга, выбранную модель и провайдера.
Потом разрежьте метрики по трем срезам: модель, провайдер, тип задачи. Если SLA уже уходит в красную зону, не ждите полного диагноза: включайте деградацию и параллельно проверяйте гипотезы.
Что делать, если растут таймауты?
Не смотрите только на среднее время ответа. Сравните p50, p95 и p99 по каждой модели и провайдеру, потом проверьте длину контекста, prompt_tokens и max_tokens у медленных и обычных запросов.
Если одна очередь быстро пухнет, снижайте параллельность именно там. Заодно урежьте слишком длинный контекст и переведите часть трафика на запасной маршрут. Это дает результат быстрее, чем попытка сразу найти точную причину.
Как разбираться с резким ростом refusals?
Сначала сравните свежие отказы со стабильной версией промпта на одном и том же наборе безопасных запросов. Если старая версия отвечает нормально, а новая уходит в отказ, ищите проблему в инструкциях, роли, параметрах или смене модели.
Соберите 5–10 пар удачных и неудачных ответов с полным входом, версией промпта, моделью, провайдером и текстом refusal. Если продукт нельзя долго держать в деградации, временно переведите поток на соседнюю модель или другого провайдера.
Что проверить, если ломается structured output?
Сверьте ожидаемую схему и фактический формат ответа. Проверьте schema_version, обязательные поля, типы, enum и место, где падает цепочка: сама модель, парсер, санитайзер или валидатор.
Не ограничивайтесь 500 в приложении. Возьмите сырой ответ модели, результат парсинга и текст ошибки валидатора хотя бы по нескольким первым сбоям. Часто проблема не в модели, а в том, что один сервис уже ждет новый формат, а другой еще живет на старом.
Как понять, что проблема у провайдера, а не у нас?
Разберите ошибки по типу. 401 чаще указывает на токен, подпись или base_url, 429 — на лимиты и всплеск нагрузки, 5xx — на сбой у провайдера, а connect timeout, DNS и TLS — на сеть между вашим сервисом и API.
Потом прогоните тот же запрос через второй маршрут с теми же параметрами. Если соседний провайдер отвечает нормально, проблема почти наверняка не в вашем приложении. Если падают оба, смотрите свой релиз, прокси, сеть и схему ретраев.
Когда стоит переводить трафик на резервный маршрут?
Задайте пороги заранее и не переключайте трафик на глаз. Рабочий вариант: если доля 5xx держится выше 3% пять минут подряд или p95 вырос примерно вдвое, сначала уводите часть запросов, а потом наращивайте долю резерва.
Если вы работаете через единый OpenAI-совместимый шлюз, такой перевод проходит быстрее, потому что не требует срочно менять SDK, код и промпты. Но после переключения все равно смотрите качество ответа, а не только доступность.
Каких ошибок команда должна избегать во время инцидента?
Не начинайте с правок промпта, если вы еще не проверили провайдера, очередь и метрики по срезам. Не смешивайте весь трафик в одну картину: один деградировавший провайдер легко маскируется общей сводкой.
Еще не отключайте валидацию целиком ради красивого графика. Так вы пропустите битые ответы в бизнес-логику. Во время сбоя лучше сузить функциональность, чем молча принять неверные суммы, статусы или идентификаторы.
Что сделать после стабилизации, чтобы сбой не повторился?
Проверьте, что ошибки и задержка вернулись к норме не только в среднем, но и по критичным маршрутам, сценариям и регионам. Убедитесь, что refusals снова в обычном диапазоне, а structured output проходит без ручных костылей и лишних повторов.
Возвращайте трафик с резерва по шагам и сразу дополняйте runbook. После каждого инцидента добавьте хотя бы один новый порог, один тест или одно уточнение по логам. Так документ не устареет к следующей смене.