Обработка ошибок
Стратегия retry, backoff, комбинация с model fallbacks и что показывать конечному пользователю.
Гид про практическую работу с ошибками Hubris API — где имеет смысл ретраить, где не имеет, как комбинировать с Model Fallbacks, и как не показать сырое сообщение API конечному пользователю.
Полная таблица кодов и форматов ответа — на странице Ошибки.
Стратегия по кодам
| HTTP | code | Ретраить? | Почему |
|---|---|---|---|
| 400 | invalid_request | ❌ нет | Это баг в коде. Ретрай → та же ошибка. Логируйте и чините. |
| 401 | invalid_api_key | ❌ нет | Ключ невалиден. Ротация на лету не предусмотрена. |
| 402 | insufficient_balance | ❌ нет (но можно ждать) | Кончился баланс. Если есть авто-пополнение — после крупного топ-апа ретрай поможет. Иначе — алёрт. |
| 404 | model_not_found | ❌ нет | Модель архивирована или ID опечатан. Лучше fallback на актуальную (model-selection). |
| 429 | daily_limit_exceeded | ❌ не имеет смысла | Лимит ключа исчерпан. До конца окна — отказ. Используйте другой ключ или ждите. |
| 502 | upstream_error | ✅ да | Апстрим вернул 5xx или контент-фильтр. Транзиентно. |
| 503 | exchange_rate_unavailable | ✅ да | Курс ЦБ временно не доступен — каталог не отдаёт цены. Обычно минуту. |
| 504 | upstream_timeout | ✅ да | Модель перегружена. Часто помогает другая модель из каталога. |
Главное правило: ретраить транзиентные сбои (5xx) с backoff, остальное — нет.
Где ретрай встроен на стороне Hubris
Hubris уже автоматически переключается на запасную модель, если вы передали массив models:
curl -s https://api.hubris.pw/v1/chat/completions \
-d '{
"model": "anthropic/claude-haiku-4.5",
"models": [
"anthropic/claude-haiku-4.5",
"openai/gpt-4o-mini"
],
"messages": [{"role": "user", "content": "..."}]
}'Если основная модель ответила 429 или 5xx — клиент видит успешный ответ от запасной. Подробнее: Model Fallbacks.
Это снимает большую часть транзиентных ошибок без вашего ретрая. Перед тем как писать сложную ретрай-логику в клиенте — попробуйте models: [...]. На практике она закрывает 80% сценариев.
Когда нужен клиентский ретрай поверх
Кейсы, где Hubris-fallback не спасает:
- Нет резервной модели в массиве
modelsили нужна та же самая модель. Если основная модель уникальна (например, специфичный image-gen ID, который никто не дублирует) — клиентский ретрай. - 503
exchange_rate_unavailable. Это не ошибка модели — это про каталог. Fallback моделей не сработает. Ретрай через минуту. - Сетевые таймауты до Hubris. До нас даже не дошёл запрос — fallback не запустится. Клиентский ретрай.
Python: exponential backoff
import time
import openai
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
def call_with_retry(messages, max_attempts=3):
for attempt in range(max_attempts):
try:
return client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
models=[
"anthropic/claude-haiku-4.5",
"openai/gpt-4o-mini",
],
messages=messages,
)
except openai.APIStatusError as e:
# Ретраим только транзиентные коды
if e.status_code in (502, 503, 504):
if attempt == max_attempts - 1:
raise
# Exponential backoff с jitter
wait = (2 ** attempt) + (0.5 * attempt)
time.sleep(wait)
continue
# 4xx — баг или конфигурация, ретрай не поможет
raise
resp = call_with_retry([{"role": "user", "content": "Hello"}])Использование models: [...] параллельно с retry — это нормально: они работают на разных уровнях (модель vs сеть/каталог). Двойного списания не будет — биллинг по фактически ответившему запросу.
Готовая библиотека
Если не хотите писать backoff руками — есть tenacity:
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
import openai
@retry(
retry=retry_if_exception_type((openai.APITimeoutError, openai.APIConnectionError)),
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=10),
)
def call_llm(messages):
return client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=messages,
)TypeScript: фабрика с retry
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
async function callWithRetry(
messages: Array<{ role: string; content: string }>,
maxAttempts = 3,
) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await client.chat.completions.create({
model: 'anthropic/claude-haiku-4.5',
models: [
'anthropic/claude-haiku-4.5',
'openai/gpt-4o-mini',
],
messages,
} as any);
} catch (e: any) {
const transient = [502, 503, 504].includes(e?.status);
const lastAttempt = attempt === maxAttempts - 1;
if (!transient || lastAttempt) throw e;
const wait = (2 ** attempt) * 1000 + Math.random() * 500;
await new Promise((r) => setTimeout(r, wait));
}
}
throw new Error('unreachable');
}Что показывать пользователю
Сырые сообщения API не подходят для production UI:
| Внутреннее | Внешнее (пользователю) |
|---|---|
401 invalid_api_key | «Внутренняя ошибка авторизации» (никогда не показывайте «неверный ключ» — это намекает, что у вас есть рабочий) |
402 insufficient_balance | «Сервис временно недоступен» — это ваша проблема, не пользователя |
404 model_not_found | «Сервис обновляет каталог» — подставьте альтернативную модель |
429 daily_limit_exceeded | «Лимит запросов на сегодня исчерпан, попробуйте завтра» |
502 / 503 / 504 | «Модель временно недоступна, повторим попытку» (если уже идёт retry) |
Никогда не показывайте error.message модели напрямую — он на английском и может содержать технические детали (имена провайдеров, ID моделей).
Как НЕ надо
# Плохо: ретрай 401 / 402 / 404
for i in range(5):
try:
return client.chat.completions.create(...)
except Exception:
time.sleep(2 ** i)Слепой ретрай на любую ошибку:
- На
401(неверный ключ) — крутит цикл с экспоненциальной задержкой, прежде чем сдаться. UI замораживается. - На
402(нет денег) — то же самое, плюс ваш сервис атакует наш API без шансов на успех. - На
400(баг в коде) — никогда не починится сама.
# Плохо: показ сырого error.message
except openai.APIError as e:
return jsonify({"error": str(e)}), 500Сырая ошибка попадёт в браузер пользователя. Безопаснее:
except openai.APIError as e:
log.error("LLM error", extra={"status": getattr(e, "status_code", None)})
return jsonify({"error": "Сервис временно недоступен"}), 503Стриминг и ошибки
При stream: true ошибка ДО первого чанка возвращается как обычный JSON { error: {...} } с соответствующим HTTP-кодом. После начала стрима — клиент видит обрыв соединения или преждевременный finish_reason. Подробности — на странице стриминга и в концепции ошибок.
Что важно знать
- Hubris не списывает деньги за неуспешные запросы. 4xx и 5xx — без биллинга. Streaming-обрыв после первого чанка — частичное списание по тому, что успело сгенерироваться.
- Не ретраите structured-output ответы внутри одного контекста. Если модель упала на структуре — лучше переключиться на другую модель (
models: [...]), а не повторять с той же. - Лимиты по таймауту — ваш HTTP-клиент должен иметь reasonable timeout (90–120 сек), иначе соединение зависнет на perpetually-slow модели.
Что дальше
- Ошибки — полная таблица кодов с примерами JSON-ответов.
- Model Fallbacks — встроенный fallback на запасную модель.
- Стриминг ответов — что происходит с ошибками во время стрима.
- Управление ключами — дневные лимиты на стороне сервера.
Обновлено: