Паттерны маршрутизации OpenAI SDK через несколько провайдеров: Fallback, уровни стоимости и балансировка нагрузки
Четыре практических паттерна маршрутизации OpenAI SDK через несколько провайдеров: primary/fallback-цепочки, маршрутизация по стоимости, маршрутизация по содержимому и географическая маршрутизация — с кодом, ценами и чек-листами для продакшена.
Паттерны маршрутизации OpenAI SDK через несколько провайдеров: Fallback, уровни стоимости и балансировка нагрузки
В OpenAI Python SDK есть параметр base_url. Замените его, и тот же вызов client.chat.completions.create() пойдёт на DashScope, DeepSeek, SiliconFlow или любой другой OpenAI-совместимый endpoint вместо api.openai.com. Один параметр превращает интеграцию с единственным провайдером в слой маршрутизации между несколькими — если знать, какие паттерны действительно работают в продакшене, а какие создают больше проблем, чем решают.
OpenAI-совместимость означает, что провайдер предоставляет endpoint chat-completions, чей контракт запроса и ответа достаточно близок к API OpenAI, чтобы немодифицированный вызов OpenAI SDK работал после замены трёх значений: API key, base URL, название модели. Минимальная поверхность на практике —POST /v1/chat/completions с messages, model и потоковым ответом в форме OpenAI.
Мы прогоняем трафик через несколько провайдеров с помощью TheRouter достаточно давно, чтобы иметь аргументированное мнение о том, какие паттерны выдерживают нагрузку. Это руководство описывает четыре из них — с рабочим кодом, реальными ценами и ошибками, на которых мы учились. TheRouter маршрутизирует OpenAI-совместимые запросы через настроенных провайдеров и поддерживает routing и fallback провайдеров/моделей там, где это реализовано в продуктовом пути. Мы не обещаем нулевой downtime, поддержку всех моделей и гарантированно самую низкую цену.
Паттерн 1: primary/fallback-цепочка
Самый простой многопровайдерный паттерн — упорядоченный список. Все запросы сначала идут к primary-провайдеру, а если он возвращает повторяемую ошибку, следующий по списку принимает запрос. Здесь нет угадывания — вы явно определяете, какие ошибки запускают failover, а какие должны привести к немедленному отказу.
Рабочая fallback-цепочка требует четырёх решений:
- Primary route. Провайдер и модель для обычного трафика. Выбирайте по стоимости, задержке или соответствию задаче.
- Backup routes. Одна-две альтернативы, которые принимают тот же формат запроса — или формат, для которого есть безопасная конвертация.
- Повторяемые ошибки. Временные 429, 500, 502, 503, 504. Это означает «попробуйте ещё раз», а не «ваш запрос сломан».
- Условия остановки. Ошибки аутентификации (401, 403), невалидные model ID, некорректное тело запроса, ошибки биллинга. Не отправляйте плохой запрос трём провайдерам подряд.
Вот как выглядит fallback-цепочка на OpenAI SDK с ручным переключением:
import os
from openai import OpenAI
providers = [
{
"base_url": "https://api.deepseek.com/v1",
"api_key": os.environ["DEEPSEEK_API_KEY"],
"model": "deepseek-chat",
},
{
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key": os.environ["DASHSCOPE_API_KEY"],
"model": "qwen3.8-max",
},
]
RETRYABLE = {429, 500, 502, 503, 504}
def chat(messages: list[dict]) -> str:
last_error = None
for p in providers:
client = OpenAI(base_url=p["base_url"], api_key=p["api_key"])
try:
resp = client.chat.completions.create(
model=p["model"], messages=messages
)
return resp.choices[0].message.content
except Exception as e:
status = getattr(e, "status_code", None)
if status and status not in RETRYABLE:
raise # неповторяемая ошибка — сразу отказ
last_error = e
raise last_error
Этот паттерн работает, когда backup-модель справляется с тем же промптом без деградации качества. Он не работает, если backup-модель не поддерживает функциональность, на которую рассчитан промпт (vision, tool calling, расширенный контекст). Проверяйте возможности модели до добавления провайдера в цепочку, а не после первой жалобы пользователя.
Подробный разбор порядка failover, бюджета повторов и мониторинга — в нашем руководстве по LLM API fallback routing.
Паттерн 2: маршрутизация по уровням стоимости
Маршрутизация по стоимости отправляет разные типы запросов на разные ценовые уровни. Логика простая: повседневное суммирование не требует frontier-модели, а сложная задача на рассуждение не должна идти на самый дешёвый вариант только ради экономии.
Пример конфигурации по уровням:
| Уровень | Задача | Провайдер / Модель | Цена input (за 1M tokens) | Цена output (за 1M tokens) |
|---|---|---|---|---|
| Economy | Суммирование, классификация, извлечение | DeepSeek V4 Flash | $0.10 | $0.30 |
| Standard | Общий чат, помощь с кодом | Qwen3.8-Max через DashScope | $2.00 | $8.00 |
| Premium | Сложные рассуждения, агентные задачи | OpenAI GPT-5.5 | $2.50 | $10.00 |
Источники: цены DeepSeek, цены DashScope, цены OpenAI, получено 2026-09-03.
Решение о маршруте принимается до API-вызова, а не внутри SDK. Сначала вы классифицируете запрос (по system prompt, метаданным вызывающего, длине входных данных или явному параметру уровня), затем выбираете конфигурацию клиента:
import os
from openai import OpenAI
TIERS = {
"economy": {
"base_url": "https://api.deepseek.com/v1",
"api_key": os.environ["DEEPSEEK_API_KEY"],
"model": "deepseek-chat",
},
"standard": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key": os.environ["DASHSCOPE_API_KEY"],
"model": "qwen3.8-max",
},
"premium": {
"base_url": "https://api.openai.com/v1",
"api_key": os.environ["OPENAI_API_KEY"],
"model": "gpt-5.5",
},
}
def chat(messages: list[dict], tier: str = "standard") -> str:
cfg = TIERS[tier]
client = OpenAI(base_url=cfg["base_url"], api_key=cfg["api_key"])
resp = client.chat.completions.create(
model=cfg["model"], messages=messages
)
return resp.choices[0].message.content
Сложность не в коде, а в классификации. Запрос, который выглядит простым, может зависеть от глубины рассуждения, и отправка его на economy-уровень выдаст мусор. Начинайте с ручного выбора уровня (вызывающая сторона сама указывает tier), а на автоматическую классификацию переходите только когда накопите достаточно размеченных данных.
Подробнее о ценах разных провайдеров — в нашем сравнении LLM API провайдеров и стратегиях оптимизации затрат.
Паттерн 3: маршрутизация по содержимому
Маршрутизация по содержимому анализирует запрос и отправляет его провайдеру, который лучше всего подходит для задачи. Это глубже, чем уровни стоимости — здесь учитываются возможности модели, длина контекста и модальность.
Примеры правил маршрутизации по содержимому:
- Vision-запросы (сообщения с URL изображений или base64-картинками) направляются к модели с поддержкой зрения. Qwen3.8-Max, GPT-5.5 или Claude подходят. Не отправляйте изображения текстовой модели.
- Запросы с длинным контекстом (количество входных tokens свыше 32K) направляются к модели с большим контекстным окном. Qwen3.7-Max поддерживает 1M tokens, DeepSeek V4 — 128K. Отправка промпта на 200K tokens модели с окном 32K приведёт к тихому обрезанию или ошибке.
- Запросы с tool calling направляются к модели с надёжной реализацией function calling. Не все OpenAI-совместимые провайдеры одинаково реализуют
toolsиtool_choice. Различия описаны в нашем сравнении function calling по провайдерам. - Запросы, требующие рассуждений (математические доказательства, многошаговое планирование, генерация кода для сложных систем) направляются к модели с extended thinking или chain-of-thought.
def classify_and_route(messages: list[dict], tools: list | None = None) -> dict:
has_images = any(
isinstance(c, dict) and c.get("type") == "image_url"
for m in messages
for c in (m.get("content") if isinstance(m.get("content"), list) else [])
)
estimated_tokens = sum(len(str(m.get("content", ""))) // 4 for m in messages)
if has_images:
return TIERS["standard"] # модель с поддержкой зрения
if estimated_tokens > 32_000:
return TIERS["standard"] # большой контекст
if tools:
return TIERS["premium"] # надёжный tool calling
return TIERS["economy"] # по умолчанию самый дешёвый
Маршрутизация по содержимому — самый мощный паттерн и самый хрупкий одновременно. Каждое правило маршрутизации является предположением о возможностях модели, которое может перестать работать после обновления провайдером. Встраивайте наблюдаемость в слой маршрутизации — логируйте, какое правило сработало и был ли ответ приемлемым — чтобы поймать деградацию раньше, чем это сделают пользователи.
Паттерн 4: географическая маршрутизация
Географическая маршрутизация выбирает провайдера в зависимости от того, откуда пришёл запрос или где должны оставаться данные. Этот паттерн важен по двум причинам: задержка и соответствие регулированию.
| Регион | Провайдер | Base URL | Преимущество по задержке |
|---|---|---|---|
| Материковый Китай | DashScope (Alibaba Cloud) | https://dashscope.aliyuncs.com/compatible-mode/v1 | Локальная инфраструктура, без трансграничного хопа |
| Восточная Азия (не Китай) | DashScope Сингапур | https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1 | Региональный endpoint |
| Глобальный | OpenAI | https://api.openai.com/v1 | Инфраструктура в США, глобальный CDN |
| Глобальный (чувствительный к стоимости) | DeepSeek | https://api.deepseek.com/v1 | Инфраструктура в Китае, глобальный доступ |
Источники: совместимость DashScope с OpenAI, справка по OpenAI Python SDK, документация DeepSeek API, получено 2026-09-03.
Решение о маршруте использует метаданные запроса (геолокацию IP, явный заголовок региона или регион развёртывания) для выбора провайдера:
import os
from openai import OpenAI
REGION_MAP = {
"cn": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key": os.environ["DASHSCOPE_API_KEY"],
"model": "qwen3.8-max",
},
"ap": {
"base_url": os.environ.get(
"DASHSCOPE_SG_BASE_URL",
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
),
"api_key": os.environ["DASHSCOPE_API_KEY"],
"model": "qwen3.8-max",
},
"global": {
"base_url": "https://api.openai.com/v1",
"api_key": os.environ["OPENAI_API_KEY"],
"model": "gpt-5.5",
},
}
def chat(messages: list[dict], region: str = "global") -> str:
cfg = REGION_MAP.get(region, REGION_MAP["global"])
client = OpenAI(base_url=cfg["base_url"], api_key=cfg["api_key"])
resp = client.chat.completions.create(
model=cfg["model"], messages=messages
)
return resp.choices[0].message.content
DashScope переходит на доменные имена, привязанные к workspace. Если вы всё ещё используете старые адреса dashscope.aliyuncs.com или dashscope-intl.aliyuncs.com, проверьте официальное уведомление о миграции и переключитесь на формат {WorkspaceId}.{region}.maas.aliyuncs.com. Старые endpoint всё ещё работают, но могут не получить оптимизацию производительности. Источник: совместимость DashScope с OpenAI, получено 2026-09-03.
Географическая маршрутизация часто комбинируется с паттерном 1 (fallback). Если региональный провайдер недоступен, fallback на глобального провайдера лучше, чем возврат ошибки. Штраф по задержке при кросс-региональном fallback почти всегда предпочтительнее простоя.
Комбинирование паттернов: продакшен-политика маршрутизации
На практике несколько паттернов комбинируются. Полноценная политика маршрутизации может выглядеть так:
- Классификация запроса. По типу содержимого, сложности и региону.
- Выбор primary-провайдера. На основе результатов классификации — содержимое + география + уровень стоимости.
- Fallback. Если primary-провайдер вернул повторяемую ошибку, переход по fallback-цепочке.
- Логирование. Запись каждого решения о маршрутизации: какое правило сработало, какой провайдер выбран, какой статус ответа получен.
TheRouter обрабатывает эту композицию на уровне gateway. Вместо написания и поддержки логики маршрутизации в коде приложения, вы описываете правила маршрутизации декларативно, а gateway обрабатывает failover, повторы и выбор провайдера. TheRouter маршрутизирует OpenAI-совместимые запросы через настроенных провайдеров и поддерживает routing и fallback провайдеров/моделей там, где это реализовано в продуктовом пути.
Сравнение gateway, обрабатывающих такую композицию, — в нашем сравнении LLM API gateway и сравнении альтернатив OpenRouter.
Чек-лист для продакшена
Перед выкаткой многопровайдерной маршрутизации в продакшен проверьте каждый пункт:
- Изоляция API-ключей. Ключ каждого провайдера в отдельной переменной окружения. Никогда не используйте один ключ для разных провайдеров. Ротируйте по расписанию. Паттерны управления ключами описаны в нашем руководстве по управлению API-ключами.
- Маппинг model ID. Одна и та же модель по сути имеет разные ID у разных провайдеров.
deepseek-chatна DeepSeek,qwen3.8-maxна DashScope,gpt-5.5на OpenAI. Ваш слой маршрутизации должен знать эти соответствия. - Обработка ошибок. Не все провайдеры возвращают ошибки в одинаковом формате. OpenAI SDK нормализует большинство из них, но бывают крайние случаи. Подробнее — в нашем справочнике по обработке ошибок по провайдерам.
- Совместимость streaming. Если используется streaming (
stream=True), убедитесь, что каждый провайдер в таблице маршрутизации поддерживает SSE streaming с тем же форматом чанков. Подробнее — в нашем руководстве по реализации streaming SSE. - Учёт rate limit. У каждого провайдера свои лимиты скорости. Fallback, который отправляет весь трафик на backup-провайдера при сбое, может исчерпать его квоту за минуты. Подробнее — в нашем сравнении rate limit.
- Мониторинг затрат. Отслеживайте стоимость по провайдеру, уровню и типу запроса. Ошибка в правиле маршрутизации может незаметно увеличить счёт в 10 раз.
- Настройка timeout. Устанавливайте таймауты для каждого провайдера отдельно. Медленный провайдер должен вызывать fallback, а не бесконечно блокировать запрос.
- Health checks. Проактивно проверяйте состояние провайдеров, а не узнавайте о сбоях через ошибки пользователей.
FAQ
Можно ли использовать один API-ключ для нескольких OpenAI-совместимых провайдеров?
Нет. Каждый провайдер выпускает собственные API-ключи. Ключи DashScope привязаны ещё и к региону: ключ, созданный в регионе Пекин, не работает с endpoint Сингапура. Источник: документация DashScope по кросс-региональному доступу, получено 2026-09-03.
OpenAI SDK работает со всеми перечисленными здесь провайдерами?
OpenAI Python SDK (пакет openai) работает с любым провайдером, реализующим endpoint /v1/chat/completions по спецификации OpenAI. DashScope, DeepSeek, SiliconFlow и многие другие это поддерживают. Провайдер-специфичные расширения (дополнительные поля в ответе, нестандартные параметры) SDK может не сохранять. Подробности по провайдерам — в нашем справочнике OpenAI-совместимых API провайдеров.
Как обрабатывать различия в context length у разных провайдеров?
Оцените количество входных tokens перед маршрутизацией. Если оно превышает контекстное окно провайдера, направляйте запрос провайдеру с большим окном. Не рассчитывайте на то, что провайдер корректно отклонит запрос: некоторые провайдеры тихо обрезают входные данные.
Создавать новый клиент OpenAI на каждый запрос или переиспользовать?
Создайте один клиент на конфигурацию провайдера и переиспользуйте его. OpenAI SDK использует пул соединений, и создание нового клиента на каждый запрос расходует соединения и увеличивает задержку.
Как TheRouter реализует эти паттерны?
TheRouter реализует эти паттерны маршрутизации на уровне gateway, так что код вашего приложения делает один вызов OpenAI SDK, направленный на endpoint TheRouter. Правила маршрутизации, fallback-цепочки и выбор провайдера задаются в конфигурации gateway. Подробности — в быстром старте TheRouter и документации по model fallbacks.