Агрегатор LLM с авто-выбором free-моделей и fallback: пошаговый гайд | AiManual
AiManual Logo Ai / Manual.
11 Май 2026 Гайд

Агрегатор LLM с авто-выбором: как не остаться с пустыми руками, когда провайдер упал

Постройте отказоустойчивый агрегатор LLM, который сам находит живые free-модели и переключается при сбоях. Решение для MVP и production с примерами кода.

Проблема: free-модели живут своей жизнью

Вы потратили выходные на интеграцию GPT-4o-mini через OpenRouter, а в понедельник модель выдает 503. Или Anthropic внезапно заблокировал регион. Или лимиты закончились на третьем запросе. Знакомо?

Free-модели — штука нестабильная. Провайдеры меняют эндпоинты, вводят капчу, рейт-лимиты, а то и просто выключают модель без предупреждения. Но для MVP или экономии бюджета они незаменимы. Выход — агрегатор с авто-выбором живой модели и fallback-цепочкой. Я покажу, как собрать такого зверя на Python, с учетом ошибок, которые я сам набивал.

Если вы уже сталкивались с ситуацией, когда RAG-система выдаёт чушь из-за сбойной модели, прочитайте этот разбор — там описана похожая проблема и её решение.

Решение: живой агрегатор с heartbeat и ротацией

Идея проста: перед каждым запросом (или по таймеру) мы проверяем, какие free-модели сейчас доступны, выбираем лучшую по скорингу, и при сбое переключаемся на следующую. Плюс храним состояние, чтобы соседние инстансы не дублировали проверки.

1 Сбор и обновление списка free-моделей

Вручную поддерживать список — путь в ад. Модели появляются и исчезают каждый месяц. Используйте агрегаторы вроде OpenRouter, которые отдают актуальный перечень. Но осторожно: у них тоже бывают сбои. Лучше иметь свой резервный список в JSON.

[
  {
    "id": "gpt-4o-mini",
    "provider": "openai",
    "base_url": "https://api.openai.com/v1",
    "api_key_env": "OPENAI_API_KEY",
    "free": true,
    "health_endpoint": "/models"
  },
  {
    "id": "claude-3-haiku",
    "provider": "anthropic",
    "base_url": "https://api.anthropic.com/v1",
    "api_key_env": "ANTHROPIC_API_KEY",
    "free": false  // условно-бесплатно, если есть грант
  }
]

Ошибка: положиться на один источник. Если OpenRouter лежит, вы теряете все модели. Держите backup-список из 3-5 проверенных провайдеров, например, вот этот гид поможет найти альтернативы.

2 Health check: пинг, а не просто статус

Просто проверить, что эндпоинт отвечает — мало. Модель может быть available, но деградировать (высокая latency, частые 429). Лучший способ — отправить реальный короткий запрос (например, "Hi") и замерить время ответа и успешность. Но не делайте это для каждой модели при каждом запросе — закэшируйте результат на 1-5 минут в Redis.

async def check_model_health(client, model_config):
    """Проверяет доступность модели через минимальный вызов."""
    start = time.time()
    try:
        # Используем /v1/chat/completions для OpenAI-совместимых
        resp = await client.post(
            f"{model_config['base_url']}/chat/completions",
            json={"model": model_config['id'], "messages": [{"role": "user", "content": "ping"}], "max_tokens": 5},
            headers={"Authorization": f"Bearer {os.environ[model_config['api_key_env']]}"},
            timeout=10
        )
        if resp.status_code == 200:
            return {"ok": True, "latency": time.time() - start}
        else:
            return {"ok": False, "code": resp.status_code}
    except Exception as e:
        return {"ok": False, "error": str(e)}

Почему не просто HEAD request? Потому что провайдеры часто возвращают 200 на статику, а реальный инференс ломается. Единственный способ убедиться — выполнить инференс. Да, это тратит токены, но free-модели обычно имеют бесплатный tier. Если у вас модель без инференса (например, только эмбеддинги), пинг эндпоинта /models — ок.

3 Авто-выбор: скоринг с учётом uptime и задачи

Просто брать первую живую модель — наивно. Лучше сделать скоринг: модель с низкой latency и высоким аптаймом получает приоритет. А для разных задач (код, креатив, RAG) можно взвешивать по-разному. Например, для извлечения фактов лучше подходит Claude 3 Haiku, а для кода — GPT-4o-mini.

Вот примитивный скоринг без внешних зависимостей:

def score_model(health, model_config, task_type="general"):
    if not health['ok']:
        return 0
    # штраф за latency > 5s
    latency_penalty = max(0, (health['latency'] - 0.5) / 5)
    base = 10 - latency_penalty
    # повышаем для специализированных моделей
    if task_type == "code" and "code" in model_config['id']:
        base += 3
    return base

А затем выбираем модель с максимальным score. Если несколько с одинаковым — берём первую случайную (чтобы не нагружать одну).

4 Fallback: не просто retry, а переключение провайдера

Самая частая ошибка — retry на той же модели, которая уже лежит. Fallback должен переключаться на другую модель (возможно, другого провайдера) и при этом не зацикливаться. Реализуем цепочку: при ошибке или таймауте увеличиваем счётчик попыток, после max_retry на одной модели переходим к следующей в порядке скоринга.

async def llm_with_fallback(request, models, task_type: str):
    sorted_models = await get_sorted_models(models, task_type)
    for model in sorted_models:
        for attempt in range(3):
            try:
                return await call_model(model, request)
            except (TimeoutError, RateLimitError) as e:
                logger.warning(f"Model {model['id']} failed: {e}")
                if attempt == 2:
                    # Помечаем модель как dead в кэше на 30 секунд
                    await mark_dead(model['id'], ttl=30)
                    break  # переходим к следующей модели
                await asyncio.sleep(2 ** attempt)  # exponential backoff
    raise NoModelAvailable("All models exhausted")
💡
На практике я видел fallback, который при ошибке переключался на платную модель без ведома пользователя. Это не только дорого, но и потенциально утекает данные. В этой статье разобрана похожая проблема в LlamaIndex.

5 Хранение состояния в Redis для распределённой отказоустойчивости

Если у вас несколько реплик, каждая будет проверять health и обновлять dead-лист независимо. Это порождает дублирующие запросы и несогласованность. Решение — Redis. В нём держим счётчики ошибок, последнее время аптайма, live-список моделей. Каждая реплика пишет в Redis и читает оттуда скоринговую таблицу. TTL на health — 2 минуты. Если ключ протух — запускаем новую проверку.

async def get_leader_board(redis, models):
    pipe = redis.pipeline()
    for m in models:
        pipe.get(f"model:{m['id']}:health")
    results = await pipe.execute()
    live = []
    for i, data in enumerate(results):
        if data:
            live.append((json.loads(data), models[i]))
    return sorted(live, key=lambda x: x[0]['score'], reverse=True)

Не забудьте про очистку старых ключей, иначе Redis разрастётся. Используйте EXPIRE при каждой записи.

Нюансы, которые превратят ваш агрегатор в свалку (и как этого избежать)

  • Отсутствие rate limiter на стороне агрегатора. Вы будете долбить все модели одновременно health check'ами — провайдеры забанят IP. Ограничьте параллельные проверки до 2-3.
  • Игнорирование различий в API. OpenAI и Anthropic имеют разную структуру запросов/ответов. Придётся писать адаптеры. Хорошая новость — многие free-модели поддерживают OpenAI-совместимый интерфейс (через OpenRouter или локально).
  • Бесконечный рекурсивный fallback. Если все модели мертвы, ваш код может циклично перебирать один и тот же список. Введите глобальный лимит попыток и кидайте исключение пользователю, а не вешайте запрос навечно.
  • Утечка чувствительных данных при fallback. Убедитесь, что при переключении на другую модель вы не передаете промпты с PII на сервера, которые не имеют соответствующих сертификатов. Об этом я писал в статье про OpenAI Fallback в LlamaIndex.

Когда агрегатор не нужен (спойлер: почти всегда нужен, но бывает исключение)

Если у вас production с жёсткими SLA и бюджетом — лучше купить платный доступ и не заморачиваться с free-моделями. Но для прототипов, pet-проектов и сценариев с нерегулярной нагрузкой агрегатор спасает бюджет. Кстати, для локальных моделей есть свои решения — llama-swap позволяет переключать модели на лету без перезапуска сервера, а LLMeQueue — выстроить очередь запросов.

Осторожно: fallback на локальную модель может быть медленным, если у вас GPU не тянет. Лучше сочетать free API с локальной моделью как последней инстанцией. Пример такого гибрида — мультимодальный краулер на локальных LLM.

Как НЕ надо писать fallback: мой личный антипример

Раньше я делал так: при ошибке просто перебирал модели по порядку, без кэширования health. В результате каждая реплика била по всем моделям, провайдеры блокировали нас за DDoS. А когда одна модель возвращала ошибку, мы пытались её же через секунду — и получали ту же ошибку. Потом добавил Redis, счётчики и TTL — стало терпимо. Главный урок: централизованное состояние спасает.

Прогноз: через год free-модели не исчезнут, но агрегаторы станут умнее

Уже сейчас провайдеры вводят динамические free-квоты, привязанные к поведению пользователя. Агрегаторы будущего будут не просто проверять жива ли модель, а предсказывать её доступность на основе исторических паттернов (ML-based health check). Мы сделали первый шаг — научились не падать при сбое. Следующий шаг — оптимизация стоимости через роутинг (как LLMRouter, который режет расходы на 30-50%).

Совет: не пытайтесь объять необъятное. Начните с 3-4 моделей, отладьте fallback, а потом добавляйте новые. И никогда не забывайте про мониторинг — без него агрегатор превращается в чёрный ящик.

Подписаться на канал