Сработал fallback на резервную модель
Хорошая система с fallback не падает — она молча переключается на запасную модель или провайдера и продолжает отвечать. Это и есть проблема: «молча». Вы узнаёте о том, что основной провайдер лёг, только когда придёт счёт от дорогого резерва или когда качество ответов просядет. Хочется знать о переключении сразу — но без паники, ведь пользователям сейчас ничего не угрожает.
Идеальный кейс для тихого low-priority push: «работаем в деградированном режиме, причина такая-то».
Push при переключении на резерв
Заголовок раздела «Push при переключении на резерв»Оборачиваем цепочку «основной → резерв»: если сработал fallback — считаем переключения в окне и шлём тихий алёрт с причиной.
import os, time, collections, requestsimport openai, anthropic
NOTIFLY_URL = os.environ["NOTIFLY_URL"]NOTIFLY_TOKEN = os.environ["NOTIFLY_TOKEN"]
WINDOW_SEC = 300 # окно, в котором копим счётчик переключенийCOOLDOWN = 300 # не чаще одного алёрта в 5 минут
_falls = collections.deque() # timestamps срабатываний fallback_last_alert = 0.0
primary = openai.OpenAI()backup = anthropic.Anthropic()
def notify(title, msg, prio): requests.post(f"{NOTIFLY_URL}/message", params={"token": NOTIFLY_TOKEN}, json={"title": title, "message": msg, "priority": prio}, timeout=5)
def _on_fallback(reason): global _last_alert now = time.time() _falls.append(now) while _falls and _falls[0] < now - WINDOW_SEC: _falls.popleft() if now - _last_alert > COOLDOWN: _last_alert = now notify( "🟠 Работаем на резервной модели", f"Основной провайдер отвалился ({reason}). " f"Переключений за {WINDOW_SEC // 60} мин: {len(_falls)}.", priority=3, # тихо: пользователям сейчас ок )
def ask(prompt): """Пробуем основную модель; при сбое — резерв, с тихим уведомлением.""" try: r = primary.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}]) return r.choices[0].message.content except Exception as e: _on_fallback(f"{type(e).__name__}: {e}") r = backup.messages.create( model="claude-haiku-4-5", max_tokens=1024, messages=[{"role": "user", "content": prompt}]) return r.content[0].text
print(ask("Одно предложение про надёжность."))priority=3 — намеренно тихий уровень (силент-диапазон 0–3): телефон не
звонит, но событие зафиксировано. Счётчик за окно превращает «одно случайное
переключение» (обычно шум) в понятный сигнал «основной реально лёг, уже
N раз за 5 минут».
Эскалация: залипли на резерве надолго
Заголовок раздела «Эскалация: залипли на резерве надолго»Одно переключение — норма. А вот если fallback держится минутами, это уже
инцидент: резерв обычно дороже и/или слабее. Добавьте условие «доля
fallback-запросов в окне > 50%» и поднимите его до priority=8 — уже
громкий алёрт «пора чинить основного, а не жить на запасном».
Причина важнее самого факта
Заголовок раздела «Причина важнее самого факта»В reason кладите разобранную причину, а не просто Exception: 529 overloaded (переждать), insufficient_quota (см.
кончился free-tier), timeout
(см. латентность) — это три разных
инцидента, и лечатся они по-разному.
Что положить в текст алёрта
Заголовок раздела «Что положить в текст алёрта»- с какой модели на какую переключились;
- разобранную причину сбоя основной;
- число переключений (или долю fallback-запросов) за окно;
- насколько резерв дороже — чтобы оценить срочность возврата.