Постмортем по сбою LLM-сервиса: шаблон разбора причин
Постмортем по сбою LLM-сервиса: разберем, как фиксировать вклад модели, провайдера, промпта и внешних инструментов без пустых общих фраз.

Почему таймлайн не объясняет сбой
Таймлайн полезен, но отвечает только на вопрос "когда". В 14:03 выросла задержка, в 14:05 начались ретраи, в 14:07 пользователи увидели пустые ответы. Для LLM-инцидента этого мало. По одной хронологии нельзя понять, почему система сломалась и что именно запустило цепочку ошибок.
У LLM-сервисов причина редко живет в одном месте. Один и тот же сбой легко собирается из нескольких слоев сразу. Модель могла выбрать неверный вызов инструмента, провайдер мог обрезать контекст или вернуть нестабильный стрим, промпт мог слишком жестко толкать агента к повторной попытке, а внешний сервис мог прислать частичный ответ без явной ошибки.
Если смотреть только на последовательность событий, все это слипается в один сюжет. Команда начинает спорить о версиях: "виновата модель", "нет, провайдер", "нет, промпт". Спор тянется, потому что у людей нет общей рамки. Нужен не рассказ по минутам, а разбор по слоям.
Для начала достаточно зафиксировать четыре вопроса:
- что решила модель на каждом шаге
- что реально вернул провайдер API
- какие инструкции дал промпт
- как повели себя внешние инструменты и ретраи
Это особенно важно, если вы ходите в несколько моделей через единый OpenAI-совместимый эндпоинт. В такой схеме, в том числе через RU LLM, внешний симптом может выглядеть одинаково, а источник быть разным: маршрут запроса, поведение конкретной модели, ответ провайдера или ошибка интеграции с внутренним сервисом.
Таймлайн еще плохо помогает проверить защиту после фикса. Допустим, команда добавила лимит на число вызовов инструмента. Хорошо, но сработает ли он на том же сценарии через неделю? Если в постмортеме нет разбивки по причинам, вы не сможете воспроизвести сбой и честно проверить, что именно исправили.
Нормальный постмортем по сбою LLM-сервиса заканчивается не красивой хронологией, а проверяемой картиной причин. После него команда может взять спорный кейс, прогнать его заново и увидеть: вот вклад модели, вот вклад провайдера, вот ошибка в промпте, вот сбой внешнего инструмента.
Что считать сбоем
Для LLM-сервиса сбой - это не только падение API. Если запросы проходят, но бот отвечает через 35 секунд, путает данные клиента или уходит в бесконечный цикл вызовов инструмента, пользователь все равно не решает свою задачу.
Разделяйте доступность и качество
У таких инцидентов обычно два класса. Первый - сервисная недоступность: 5xx, таймауты, обрывы стриминга, ошибки авторизации, пустые ответы. Второй - деградация качества: галлюцинации, потеря контекста, неожиданный отказ выполнить нормальный запрос, повтор одного и того же шага, неверный вызов внешнего инструмента.
В постмортеме эти вещи лучше писать отдельно. "Пользователь ждал 28 секунд и получил повторный вызов поиска" - это симптом. "После переключения маршрута провайдер начал чаще ошибаться при вызове инструмента" - это версия причины. Если смешать их в одном абзаце, команда начнет спорить о гипотезах раньше, чем зафиксирует факты.
Полезно описать инцидент так, как его увидел человек на экране. Была ли явная ошибка с кодом или сообщением? Ответ пришел слишком поздно? Модель выдала убедительный, но ложный ответ? Зациклила одинаковые действия? Ответила мимо задачи, хотя запрос был корректным? Такая формулировка быстро выравнивает картину. Команда API, ML и продукт смотрят на один и тот же внешний эффект, а не на свои внутренние метрики.
Укажите порог серьезности заранее
Не каждый странный ответ тянет на инцидент. Нужен понятный порог, после которого случай попадает в разбор. Иначе один инженер назовет это редкой ошибкой, а другой - отказом в проде.
Обычно порог задают по двум осям: масштаб и вред. Например, инцидент открывают, если больше 3% запросов в одном сценарии завершаются таймаутом в течение 10 минут, p95 задержки выросла вдвое, или хотя бы один ответ создал риск для платежа, персональных данных или юридически значимого действия.
Для инфраструктуры вроде RU LLM сюда нередко относят и сбой маршрутизации между провайдерами, и ошибку в маскировании PII. Пользователь может не заметить это сразу, но риск уже высокий.
В хорошем разборе четыре поля идут раздельно: симптом, масштаб, порог серьезности и предполагаемая причина. Тогда с первой страницы ясно, что именно сломалось, насколько это заметили пользователи и почему команда посчитала случай сбоем, а не просто неудачным ответом.
Карточка вклада по четырем слоям
Один и тот же сбой часто пытаются свести к одной причине. Почти всегда это ошибка. Для постмортема полезнее собрать короткую карточку по четырем слоям: модель, провайдер, промпт и внешние инструменты. Тогда видно, где был первичный сбой, а где система только усилила эффект.
Как заполнять карточку
Для каждого слоя фиксируйте три вещи: факт, проверку и вывод. Факт - что именно произошло. Проверка - как вы это перепроверили. Вывод - внес ли слой вклад в инцидент и какой именно.
У модели факт может выглядеть так: ответ ушел в нестабильный JSON, добавил лишний вызов инструмента или начал игнорировать ограничение по формату. Проверка простая: прогоните тот же запрос несколько раз, сравните сырые ответы и посмотрите, меняется ли поведение без смены кода. Вывод должен быть узким. Не "модель сломалась", а "модель нестабильно держит формат при длинном контексте".
У провайдера ищите таймауты, rate limit, смену маршрута и разный результат на одном и том же запросе. Это обычная история в проде. Если команда работает через шлюз, в карточке полезно отдельно записать model ID, провайдера, маршрут и request ID. Тогда можно понять, виноват ли сам провайдер или маршрут поменялся по дороге.
Промпт ломает систему тише всего. Конфликт инструкций, хрупкий парсинг и слишком большая свобода для модели дают сбои, которые сначала выглядят как проблема модели. Если в системном сообщении вы просите "будь краток", а ниже требуете подробный JSON с пояснениями, проблема не только в движке. Проверка здесь прямая: упростите промпт до минимальной версии и посмотрите, исчезает ли сбой.
С внешними инструментами картина еще приземленнее. Пустые поля, медленный ответ, формальный 200 OK с текстом ошибки внутри, плохая схема аргументов - все это легко маскируется под "галлюцинацию". Если бот поддержки зациклил вызовы, сначала проверьте, не возвращал ли инструмент пустой status и не заставлял ли он модель повторять запрос.
Такая карточка помещается в несколько строк на слой. Этого обычно хватает, чтобы спор о причинах закончился фактами, а не мнениями.
Что собрать сразу после инцидента
Сразу после сбоя люди обычно ищут виноватого. Это мешает. Сначала соберите следы запроса в одном месте, пока логи не разъехались по разным системам и никто не успел "починить" картину вручную.
Сохраняйте не только один неудачный ответ, а весь контекст вокруг него. Тогда постмортем покажет причину, а не набор догадок.
- Зафиксируйте
request_id,trace_idи точное время в UTC. Без этого вы не сведете вместе шлюз, приложение, провайдера модели и вызовы внешних инструментов. - Запишите, какая модель отвечала, через какого провайдера шел запрос и с какими параметрами. Температура,
max_tokens,tool_choice,seedи режим стриминга часто меняют поведение сильнее, чем кажется по памяти. - Сохраните версию системного промпта и шаблон пользовательского ввода. Нужна именно та редакция, которая работала в момент сбоя, а не "почти такая же" из репозитория на следующий день.
- Выгрузите логи вызовов инструмента: что модель запросила, какие входные данные ушли наружу, что вернул сервис и сколько занял каждый шаг. Если агент зациклился, это видно именно здесь.
- Оцените масштаб: сколько запросов затронуло, какой пользовательский путь сломался, был ли отказ полным или частичным, и как долго длилось окно проблемы.
Практичный прием - собирать для каждого проблемного запроса маленькую карточку инцидента. В ней должны лежать сырой вход, полный ответ модели, служебные метаданные и итог для пользователя. Десять таких карточек обычно говорят больше, чем один красивый график.
Если LLM-трафик идет через единый шлюз, забирайте аудит-трейлы и логи из него сразу. Это помогает не гадать, какой провайдер ответил, какая модель реально отработала и где оборвалась цепочка. В разбор при этом лучше класть уже замаскированные PII, а не копии чувствительных данных.
Не редактируйте собранные артефакты перед разбором. Комментарии можно добавить рядом, но исходные промпты, ответы и логи должны остаться в том виде, в каком система их записала. Иначе спор быстро уйдет в сторону: кто что переписал и почему версии не сходятся.
Шаблон разбора по шагам
Хороший постмортем начинается с одной точной фразы о самом сбое. Без оценок, без оправданий, без слов вроде "нестабильность". Лучше так: "С 10:14 до 10:31 ассистент повторно вызывал внешний инструмент и не завершал диалог".
Сразу после этого зафиксируйте влияние на пользователей и бизнес. Сколько диалогов сорвалось, сколько запросов ушло в ретраи, насколько выросла стоимость, где просела конверсия или SLA. Фраза "часть клиентов столкнулась с проблемой" не объясняет ничего. Фраза "380 диалогов не завершились, средняя цена запроса выросла на 62%" уже дает опору для разбора.
Дальше удобно идти по одному и тому же шаблону:
- Разложите гипотезы по четырем слоям: модель, провайдер, промпт, внешние инструменты.
- Для каждой гипотезы запишите наблюдаемый признак и способ проверки. Если гипотезу нельзя проверить по логам или воспроизведению, это пока не причина, а догадка.
- Поднимите воспроизведение на тех же входах: тот же model ID, тот же провайдер, те же параметры, тот же шаблон промпта, те же описания инструментов.
- Отделите корень сбоя от факторов, которые только усилили эффект. Например, корень может быть в неверном условии вызова инструмента, а отсутствие лимита на число повторов лишь разогнало расход и длину инцидента.
- Запишите изменения. У каждого действия должны быть владелец, срок и способ проверки результата.
Логи лучше смотреть не только на уровне HTTP-статусов. Нужны raw prompt, аргументы вызовов инструмента, ответы модели, метки ретраев, cache hit или miss, выбор провайдера и время на каждом шаге. Если вы работаете через единый шлюз, полезно сверить и трассу маршрутизации: один и тот же запрос мог уйти к разным провайдерам и дать разное поведение.
Финальный список изменений должен быть конкретным. Не "улучшить промпт", а "до 15 мая добавить запрет на повторный вызов одного и того же инструмента без новых данных; владелец - команда платформы; проверка - 500 тестовых диалогов без циклов". Такой формат сразу закрывает спор о том, что делать после инцидента.
Как проверять спорные места
Спорные места почти всегда появляются там, где во время инцидента одновременно менялись модель, провайдер, версия промпта и ответ внешнего сервиса. Если трогать все сразу, команда получает набор версий, а не проверку. Лучше повторить один и тот же запрос несколько раз и в каждом прогоне менять только один слой.
Сначала отделите модель от промпта. Возьмите исходный запрос и прогоните его через другую модель без правок в системном сообщении, температуре, seed и лимитах токенов. Если симптом исчез, причина может быть в самой модели: она хуже держит формат, чаще срывается в лишние вызовы инструмента или иначе следует инструкциям.
Потом проверьте провайдера. Оставьте ту же модель и те же параметры, но отправьте запрос через другого провайдера. Так часто всплывают обрезание контекста, сбои в стриминге, отличия в ретраях и разный парсинг схемы инструментов. Если у вас единый OpenAI-совместимый шлюз, такой тест ставится проще: маршрут меняется, а код и промпт остаются теми же.
Отдельно проверьте инструменты. Сначала отключите их совсем и посмотрите, пропадает ли симптом. Если цикл вызовов исчез, модель могла работать нормально, а ломался сам вызов функции или ответ инструмента. Затем подмените внешний сервис заглушкой с предсказуемым ответом. Для CRM, поиска или биллинга часто достаточно вернуть один и тот же JSON и сравнить поведение цепочки.
Версию промпта тоже лучше проверять в лоб. Возьмите старую и новую редакцию и прогоните их на одном наборе запросов. Иногда одна фраза меняет все: бот начинает дожимать инструмент до успеха, хотя раньше останавливался после первой ошибки.
Чтобы спор не вернулся через день, фиксируйте для каждого прогона один и тот же набор данных: идентификатор запроса и время, модель, провайдера и версию промпта, были ли включены инструменты, сырой ответ модели и ответ внешнего сервиса.
Хорошая проверка дает простой вывод: симптом связан с моделью, провайдером, промптом или внешним инструментом. Если после тестов меняется сразу два слоя, прогон нельзя считать чистым, и в постмортем его лучше не тащить.
Пример: бот поддержки зациклил вызовы
Пользователь написал в чат интернет-магазина: "Где мой заказ?" Бот обратился к внешнему сервису статусов и должен был вернуть короткий ответ. Вместо этого он снова и снова спрашивал статус одного и того же заказа, пока сессия не зависала в очереди.
Причина оказалась не в одном месте. Внешний сервис иногда отвечал без ошибки HTTP, но присылал пустой status_code. Для интеграции это был плохой, но формально успешный ответ. Бот не получал явного сигнала остановиться.
Проблему закрепил промпт. В нем было правило: если поле статуса пустое, повторить запрос. Ограничения на число попыток не было. Модель сделала ровно то, что ей сказали, и заново запускала вызов инструмента.
Сверху наложился фактор провайдера. В тот же период выросла задержка ответа модели. Каждый цикл занимал не 1-2 секунды, а заметно дольше. Из-за этого застрявшие диалоги не исчезали быстро, очередь росла, и сбой увидели уже как массовую проблему, а не как редкую странность в одном чате.
По слоям такой кейс выглядит так:
- инструмент вернул ответ, который нарушал контракт: пустой
status_codeвместо явной ошибки - промпт приказал повторять запрос при пустом поле и не поставил лимит попыток
- модель послушно исполняла инструкцию и не переходила к безопасному ответу пользователю
- провайдер увеличил задержку, поэтому петля стала дорогой по времени и заметной по нагрузке
Такой разбор полезнее, чем фраза "бот зациклился из-за модели". Если бы провайдер отвечал быстро, петля все равно осталась бы, просто инцидент заметили бы позже. Если бы внешний сервис вернул нормальную ошибку, цикл бы не начался. Если бы промпт разрешал только две попытки, бот передал бы чат оператору и не забил очередь.
В нормальном постмортеме для этого кейса стоит сразу зафиксировать меры: валидировать ответы инструмента до передачи модели, ставить предел на число вызовов одного инструмента в рамках сессии, считать пустые обязательные поля ошибкой, а не поводом для повтора.
Частые ошибки в тексте постмортема
Самая частая ошибка проста: текст превращают в дневник событий. В 10:03 выросла задержка, в 10:07 пошли ретраи, в 10:12 отключили функцию. Это полезно, но этого мало. Таймлайн показывает порядок, а не причину. Если не связать каждый шаг с решением модели, ответом провайдера, текстом промпта и работой инструмента, команда снова увидит тот же сбой через неделю.
Еще одна типичная ошибка - слишком быстро назначать виноватой модель. Версия звучит правдоподобно: "модель начала галлюцинировать" или "модель зациклилась". Но у таких сбоев почти всегда есть слой ниже. Промпт мог разрешить лишние вызовы, схема инструмента могла скрыть ошибку, а провайдер мог вернуть неполный JSON или нестабильный статус.
Бывает и другая крайность: несколько разных инцидентов склеивают в один аккуратный рассказ. Утром бот мог тормозить из-за деградации провайдера, а вечером тот же бот мог сломать сценарий из-за новой версии промпта. Для читателя это выглядит как один большой сбой, но для команды это две разные причины и два разных набора действий. Если смешать их в одном тексте, исправления тоже получатся размытыми.
Плохой постмортем любит делать вид, что все уже ясно. Команда убирает спорные места, чтобы текст выглядел уверенно. Лучше разделить выводы на три группы: что доказано логами, что похоже на правду, что еще надо проверить. Такой формат экономит время на следующем разборе и не дает случайной догадке стать официальной причиной.
Слабый текст обычно видно и по финалу. Он заканчивается фразой вроде "усилим мониторинг" или "улучшим промпт". Проверить это нельзя. В конце должны быть конкретные действия: кто меняет промпт или схему инструмента, какой тест добавят в регрессию, какой сигнал пойдет в алерт, когда команда перепроверит эффект. Если нет владельца, срока и способа проверки, это не план.
Чек-лист перед публикацией
Хороший постмортем читают через месяц, когда спор уже забыт. Если по тексту нельзя восстановить один проблемный запрос и понять, почему система пошла не туда, документ останется набором наблюдений.
Перед публикацией достаточно короткого списка:
- отдельно названы симптом, корень сбоя и факторы, которые усилили эффект
- показан вклад каждого слоя на одном и том же запросе
- приложены проверяемые данные: request ID, время, версия промпта, имя модели, параметры, схема вызова инструмента, tool traces, коды ошибок, выдержки из логов
- убрано все, что не меняет вывод: долгий пересказ переписки, история старых релизов, догадки без логов
- у каждого исправления есть владелец, действие и срок проверки результата
Есть простой тест на финальную версию. Дайте документ инженеру, который не участвовал в инциденте, и попросите ответить на три вопроса: что сломалось, почему это случилось, что уже изменят. Если хотя бы на один вопрос человек отвечает расплывчато, постмортем еще сырой.
Обычно слабое место не в анализе, а в точности формулировок. Один хорошо разобранный запрос почти всегда полезнее, чем десять абзацев общих слов.
Что делать после постмортема
Если после разбора ничего не меняется в коде, тестах и наблюдаемости, текст был полезен только на бумаге. Постмортем по сбою LLM-сервиса должен заканчиваться списком изменений с владельцем, сроком и способом проверки.
Сначала переведите выводы в то, что ловит повтор сбоя раньше. Если модель ушла в цикл вызовов, добавьте лимит на число вызовов инструмента в одном диалоге и тест на этот сценарий. Если провайдер начал отдавать нестабильный формат, добавьте контрактный тест на схему ответа и алерт на рост ошибок парсинга. Если сбой вызвал промпт, зафиксируйте регрессионный набор примеров и гоняйте его при каждом изменении.
Минимум после инцидента обычно такой:
- один регрессионный тест на реальный сценарий сбоя
- один алерт на ранний сигнал, а не только на финальную ошибку
- одна защитная проверка в рантайме
- одно изменение в шаблоне разбора
Шаблон тоже стоит поправить сразу, пока детали свежие. Добавьте поля для версии промпта, схемы внешнего инструмента и точного маршрута провайдера: какой роутер, какая модель, какой фолбэк, какие параметры запроса. Без этого команда снова будет спорить по памяти, а не по данным.
Отдельно проверьте, храните ли вы аудит-трейл запроса. Для LLM-систем это база: входной промпт, системные инструкции, вызовы инструмента, ответы модели, метки маршрутизации, коды ошибок, ретраи, итоговое решение оркестратора. Если у вас есть AI-Law метки, включайте их в разбор наравне с логами. Они помогают понять, где проблема была в политике обработки запроса, а не в самой модели.
Если команда ведет трафик через RU LLM, в разборе обычно достаточно сразу смотреть на три поля: провайдер, модель и AI-Law метки. Вместе с аудит-трейлом это помогает быстро отделить сбой маршрута от плохого промпта или ошибки в схеме инструмента.
И последнее: шаблон не бывает готов навсегда. Откройте его после следующего инцидента и посмотрите, каких данных снова не хватило. Если один и тот же вопрос всплыл дважды, добавьте новое поле или автоматический лог сразу.
Часто задаваемые вопросы
Почему одного таймлайна мало для разбора сбоя?
Потому что таймлайн показывает только порядок событий. Он не отвечает, что решила модель, что вернул провайдер, как сработал промпт и что сделали внешние инструменты. Если не разложить сбой по этим слоям, команда будет спорить о версиях вместо проверки фактов.
Что вообще считать сбоем в LLM-сервисе?
Считайте сбоем не только падение API, но и случай, когда пользователь не получил нормальный результат. Долгий ответ, пустой вывод, цикл вызовов инструмента, ложные данные или потеря контекста — это тоже инцидент, если сценарий сломался для заметной доли запросов или создал риск для денег, персональных данных или юридически значимого действия.
Как не спутать симптом и причину?
Сначала опишите внешний эффект так, как его увидел пользователь. Потом отдельно запишите масштаб, порог серьезности и версию причины. Такой порядок не дает смешать факт вроде «ответ пришел через 28 секунд» с гипотезой вроде «маршрут к провайдеру стал хуже».
Какие слои нужно проверить в постмортеме?
Берите четыре слоя: модель, провайдер, промпт и внешние инструменты. По каждому слою хватит трех строк: что произошло, как вы это проверили и какой вклад слой внес в сбой. Такой формат быстро убирает споры вида «виновата только модель».
Что собрать сразу после инцидента?
Сразу сохраняйте request_id, trace_id, время в UTC, model ID, провайдера, параметры запроса, версию системного промпта и логи вызовов инструмента. Еще нужен сырой ответ модели и итог для пользователя. Не правьте эти артефакты вручную, иначе потом не сведете версии между собой.
Как воспроизвести спорный кейс без лишних догадок?
Повторите тот же запрос на тех же входах и меняйте только один слой за раз. Сначала оставьте промпт и параметры без правок, но возьмите другую модель. Потом верните модель и смените провайдера. Затем отключите инструменты или подставьте заглушку с предсказуемым ответом. Так вы увидите, где симптом исчезает.
Как понять, что проблема в провайдере, а не в модели?
Оставьте все остальное без изменений и прогоните один и тот же запрос через другого провайдера. Если поведение меняется, ищите проблему в маршруте, стриминге, обрезании контекста, ретраях или парсинге схемы инструментов. Если симптом держится у разных провайдеров, смотрите на модель, промпт или интеграцию.
Что делать, если внешний инструмент вернул 200 OK, но данные сломаны?
Не верьте одному HTTP-статусу. Если сервис прислал 200 OK, но внутри пустые обязательные поля или текст ошибки, считайте это ошибкой контракта и остановите цепочку до модели. Иначе агент может начать повторять вызов и превратить плохой ответ инструмента в длинный инцидент.
Какие действия должны остаться после постмортема?
Пишите не общие обещания, а конкретные задачи. Хороший финал выглядит так: кто ставит лимит на повторный вызов инструмента, кто добавляет регрессионный тест, какой алерт ловит ранний сигнал и когда команда проверит эффект. Если нет владельца, срока и способа проверки, это еще не план.
Как понять, что постмортем уже можно публиковать?
Дайте текст инженеру, который не участвовал в инциденте. Если он без подсказок отвечает на три вопроса — что сломалось, почему это случилось и что команда меняет — документ готов. Если ответы расплывчатые, значит в разборе не хватает данных по одному из слоев или слишком много общих слов.