Проблема: «всё или ничего» — плохой выбор
Агент устроен так, что редко всё ломается сразу. Гораздо чаще отказывает часть: один из пяти инструментов недоступен, fallback-модель отвечает хуже, дедлайн истёк на середине reasoning. Вопрос: что делать с этим частичным состоянием?
Есть два подхода, и «всё или ничего» — почти всегда неправильный:
Любой сбой любого компонента → весь запрос падает с ошибкой. Проделанная работа (найденные документы, частичные вычисления) выброшена. Пользователь получает 500 и пустой экран, хотя 80% ответа было готово.
Сбой части → отдаём то, что успели, честно помечая, чего не хватает. «Вот сводка по документам; актуальные цены сейчас недоступны, попробуйте позже». Пользователь получает пользу, а не ошибку.
Вспомни лестницу из раздела: retry (повтори) → fallback (переключись) → graceful degradation (отдай, что есть). Первые два пытаются получить полный результат. Деградация включается, когда это не удалось, и отвечает на вопрос «как достойно проиграть». Это не признание поражения, а инженерное решение: максимизировать пользу в условиях частичного отказа, вместо того чтобы обнулять её ошибкой.
Уровни деградации
Деградация — не бинарна («полный ответ» / «ошибка»), а ступенчата: между идеальным результатом и провалом есть промежуточные уровни полезности. Хороший агент спускается по ним по одной ступени, отдавая наилучшее из достижимого.
Для конкретного агента ступени наполняются смыслом:
- Частичный результат. Отработали 3 из 5 инструментов → отдаём ответ на основе трёх, отметив, что двух данных нет.
- Кэш / устаревшее. Свежие данные недоступны → отдаём последние закэшированные с пометкой «данные на такое-то время».
- Упрощённый режим. Не работает reasoning-цикл с инструментами → отвечаем прямой генерацией LLM без инструментов (хуже, но что-то).
- Заготовленный ответ. Для типовых запросов — шаблонный ответ вместо динамического.
- Честный отказ. Совсем ничего нет → понятное сообщение с указанием, что делать (не голый стектрейс).
Дизайн: чтобы отказ части не ронял всё
Деградация возможна, только если система спроектирована под неё. Если один упавший инструмент кидает исключение, которое роняет весь пайплайн, — деградировать нечему. Принципы «деградируемого» дизайна:
- Изоляция сбоев. Отказ одного компонента (инструмента, источника) не должен распространяться на весь агент. Каждый рискованный вызов обёрнут так, что его падение — обрабатываемое событие, а не крах.
- Обязательное vs опциональное. Раздели, что критично для ответа, а что — «приятное дополнение». Упал опциональный источник (например, обогащение) → продолжаем без него. Упал критичный → тогда деградируем сильнее.
- Значения по умолчанию. Компонент недоступен → у него есть разумный дефолт/заглушка, а не
None, ломающий следующий шаг. - Собирать частичное состояние. Агент должен уметь ответить из того, что уже накоплено в state (модуль 03), а не только из полного набора данных.
def gather(query: str) -> dict:
data, missing = {}, []
# КРИТИЧНЫЙ источник — без него ответ бессмыслен
try:
data["docs"] = search_docs(query) # уже с retry/timeout внутри
except Exception:
raise CriticalFailure("нет доступа к базе знаний") # тут деградируем СИЛЬНО
# ОПЦИОНАЛЬНЫЕ источники — упал → продолжаем без них, помечаем пропуск
for name, fetch in [("prices", get_prices), ("reviews", get_reviews)]:
try:
data[name] = fetch(query)
except Exception as e:
log.warning("optional_source_failed", source=name, error=str(e))
missing.append(name) # НЕ падаем — копим, чего не хватило
data["_missing"] = missing # прокинем в ответ для честной пометки
return data
# ответ строится из ТОГО, ЧТО ЕСТЬ, с явным указанием пропусков (см. ниже)
Ключевое проектное решение — классифицировать каждый источник/инструмент: без чего ответ невозможен (критично), а что лишь улучшает (опционально). Это делается на этапе дизайна, а не в момент аварии. Тогда деградация становится предсказуемой: падение опционального — тихо продолжаем с пометкой, падение критичного — спускаемся на ступень ниже. Без этой классификации любой сбой одинаково фатален.
Честная коммуникация ограничений
Частичный ответ обязан честно сообщать, что он частичный. Тихо отдать неполный результат как полный — опаснее, чем ошибка: пользователь примет решение на неполных данных, не зная об этом. Деградация без честности — это ложь.
- Помечай, чего не хватает. «Актуальные цены временно недоступны» — прямо в ответе, а не в логах.
- Указывай свежесть. Для кэша/устаревшего — «данные на 14:30», чтобы пользователь понимал степень актуальности.
- Подсказывай, что делать. «Попробуйте через несколько минут» лучше, чем тупик.
- Не выдавай деградацию за норму. Пользователь должен различать полный и частичный ответ.
Самый коварный антипаттерн: система молча отдаёт неполный/устаревший ответ, выглядящий как полный. Пользователь (или другой агент, если это A2A из модуля 05) доверяет ему и действует — на неверных данных. Особенно критично для фактов, цен, медицины, финансов. Правило: деградация всегда видима — в ответе для пользователя И в метриках для тебя (degradation-rate на дашборде, как fallback-rate). Молчаливая деградация маскирует проблему и подрывает доверие сильнее, чем честное «не смог».
Синтез: контур надёжности агента
Этим уроком закрывается весь раздел. Соберём пять механизмов в единую эшелонированную оборону — каждый ловит свой класс отказа, а вместе они превращают хрупкого агента в устойчивый production-сервис:
Запрос агента
│
▼ ⏱ TIMEOUT/DEADLINE — общий бюджет времени + лимит итераций (урок 4)
│
├─▶ 🚦 RATE LIMITING — держим темп ниже лимитов, не создаём 429 (урок 3)
│
▼ вызов LLM/инструмента
│
├─ сбой transient? ─▶ 🔁 RETRY + backoff+jitter (урок 1) ── ок ─┐
│ │ исчерпан │
│ ▼ │
├─ провайдер лёг? ──▶ 🔀 FALLBACK на др. вендора/модель (урок 2)─┤
│ │ все легли │
│ ▼ │
└─ ничего не вышло ─▶ 🛟 GRACEFUL DEGRADATION (урок 5) ──────────┤
частичный / кэш / честное «не смог» │
▼
Пользователь ВСЕГДА получает ответ
(полный · частичный · честный отказ)
+ сквозь всё: observability (трейсы/логи/метрики) — видеть, что происходит
Ни один механизм не универсален: retry бессилен против долгого падения, fallback — против исчерпанного дедлайна, timeout — против плохого качества. Сила в композиции: transient ловит retry, длительный отказ — fallback, перегрузку — rate limiting, зависание — timeout, а остаток (когда ничего не помогло) — graceful degradation. Плюс сквозная observability из первого раздела, чтобы всё это видеть. Вместе — агент, который под нагрузкой и в авариях продолжает приносить пользу, а не отдаёт 500.
Типичные ошибки
Любой сбой части → весь запрос падает с 500, проделанная работа выброшена. Отдавай частичный результат, когда полный недостижим.
Неполный/устаревший ответ выдан как полный — пользователь действует на неверных данных. Деградация всегда видима: пометка в ответе + метрика.
Один упавший инструмент кидает исключение, роняющее весь пайплайн — деградировать нечему. Изолируй сбои, раздели критичное и опциональное заранее.
Даже при полном отказе стектрейс/пустой экран — плохо. Дай понятное сообщение и что делать дальше («попробуйте позже»).
Шпаргалка
GRACEFUL DEGRADATION = частичный результат вместо ПОЛНОГО падения
«всё или ничего» = плохо: сбой части → 500, проделанная работа выброшена
последний уровень обороны: retry → fallback → DEGRADATION («как достойно проиграть»)
УРОВНИ (ступени полезности, спускайся по одной):
① полный ответ · ② полный от запасной модели (fallback) · ③ ЧАСТИЧНЫЙ (что успели) ·
④ кэш/устаревшее (с пометкой свежести) · ⑤ честное «сейчас не могу» + что делать
полезность падает, но НЕ до нуля (не 500)
формы: частичный · кэш · упрощённый режим (LLM без tools) · шаблон · честный отказ
ДИЗАЙН (деградация возможна, только если ЗАПРОЕКТИРОВАНА):
изоляция сбоев (падение компонента ≠ крах агента) ·
КРИТИЧНОЕ vs ОПЦИОНАЛЬНОЕ (решить ЗАРАНЕЕ!) — опц. упал → продолжаем с пометкой ·
дефолты вместо None · уметь ответить из накопленного state
ЧЕСТНОСТЬ (обязательна!):
помечай чего не хватает (в ОТВЕТЕ, не в логах) · указывай свежесть · подсказывай что делать
⚠️ ТИХАЯ деградация ОПАСНЕЕ ошибки: юзер/агент действует на неполных данных
деградация ВСЕГДА видима: пометка юзеру + degradation-rate на дашборде
КОНТУР НАДЁЖНОСТИ (весь раздел): timeout+лимит итераций (4) · rate limiting (3) →
transient→RETRY (1) → провайдер лёг→FALLBACK (2) → ничего→DEGRADATION (5)
+ сквозная observability → юзер ВСЕГДА получает ответ (полный/частичный/честный отказ)
надёжность = СЛОИ, не один приём
ГРАБЛИ: «всё или ничего» · тихая деградация · нет изоляции сбоев · голый 500 без подсказки
Практическое задание
Задание: сделай агента деградируемым
- Для агента из модулей 03–05 классифицируй все источники/инструменты: что критично для ответа, а что опционально? Что сейчас происходит при падении опционального?
- Выпиши ступени деградации для своего агента: как выглядит полный, частичный, кэш-ответ и честный отказ на конкретном сценарии?
- Реализуй изоляцию: оберни опциональные вызовы так, чтобы их падение копилось в «missing», а не роняло пайплайн. Проверь на сломанном опциональном источнике.
- Сделай ответ честным: как ты пометишь пользователю, что часть данных недоступна/устарела? Покажи формулировку для частичного ответа.
- Придумай метрику degradation-rate (по аналогии с fallback-rate). Почему тихая деградация без метрики опасна?
- Собери весь контур: нарисуй путь запроса через timeout → rate limit → retry → fallback → degradation. Где какой механизм включается? Что получает пользователь в худшем случае?
- Со звёздочкой: смоделируй каскадную аварию (основной провайдер лёг + опциональный источник недоступен + дедлайн жмёт). Что в итоге получит пользователь? Это полезнее, чем 500?
Что дальше
Раздел «Надёжность и обработка ошибок» закрыт: retry, fallback, rate limiting, timeouts и graceful degradation вместе делают агента устойчивым к реальному проду. Твой агент теперь наблюдаем, оценён, протестирован и надёжен. Осталось сделать его эффективным и развёрнутым. Следующий раздел — «Оптимизация и деплой»: context engineering, prompt caching, FastAPI для агентских API, оптимизация latency/cost и деплой.