Возможности
ВОЗМОЖНОСТИ

Обработка ошибок

Стратегия retry, backoff, комбинация с model fallbacks и что показывать конечному пользователю.

Гид про практическую работу с ошибками Hubris API — где имеет смысл ретраить, где не имеет, как комбинировать с Model Fallbacks, и как не показать сырое сообщение API конечному пользователю.

Полная таблица кодов и форматов ответа — на странице Ошибки.

Стратегия по кодам

HTTPcodeРетраить?Почему
400invalid_request❌ нетЭто баг в коде. Ретрай → та же ошибка. Логируйте и чините.
401invalid_api_key❌ нетКлюч невалиден. Ротация на лету не предусмотрена.
402insufficient_balance❌ нет (но можно ждать)Кончился баланс. Если есть авто-пополнение — после крупного топ-апа ретрай поможет. Иначе — алёрт.
404model_not_found❌ нетМодель архивирована или ID опечатан. Лучше fallback на актуальную (model-selection).
429daily_limit_exceeded❌ не имеет смыслаЛимит ключа исчерпан. До конца окна — отказ. Используйте другой ключ или ждите.
502upstream_error✅ даАпстрим вернул 5xx или контент-фильтр. Транзиентно.
503exchange_rate_unavailable✅ даКурс ЦБ временно не доступен — каталог не отдаёт цены. Обычно минуту.
504upstream_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 модели.

Что дальше

Обновлено:

Обработка ошибок · Hubris