Несколько ИИ-провайдеров сразу — это несколько API-ключей, несколько разных форматов запросов и несколько мест, где нужно править код при смене модели. OpenAI-совместимый роутер решает это так: вы поднимаете у себя один сервис, который принимает запросы в формате OpenAI API (стандартный JSON-формат чата: список сообщений с ролями system/user/assistant), а дальше сам перенаправляет их нужному провайдеру по вашим правилам. В результате все ваши клиенты — скрипты, чат-приложения, браузерные агенты, локальные ассистенты — ходят на один адрес вида `http://127.0.0.1:4000/v1` и используют один ключ, а вы меняете провайдера или модель правкой одного конфига, а не переписыванием кода. Гайд рассчитан на новичка: подойдёт для любой ОС, где вы умеете открыть терминал и запустить сервис (Linux, macOS, Windows 10/11), и для любой программы-клиента, у которой в настройках есть поля «base URL» и «API key».

Чек-лист перед стартом:

1. Создайте папку проекта и файл конфига. Например, `~/ai-router` и внутри `router-config.yaml`. Ожидаемый результат: файл существует и открывается редактором. Почему так: весь смысл роутера в том, что состав провайдеров описан декларативно — вы не трогаете код клиентов.
Пример:содержимое `router-config.yaml` (это шаблон, подставьте свои значения):
server:
host: 0.0.0.0 # слушать все интерфейсы
port: 4000
providers:
- name: cloud_a # ваше внутреннее имя провайдера
base_url: https://api.example-provider-a.com/v1
api_key_env: PROVIDER_A_KEY # ключ берём из переменной окружения
- name: local_box
base_url: http://127.0.0.1:8080/v1 # порт смотрите в настройках своего локального сервера
api_key_env: PROVIDER_A_KEY
models:
- alias: fast # это имя будут запрашивать клиенты
provider: cloud_a
model: <имя-модели-у-провайдера>
- alias: local
provider: local_box
model: <имя-локальной-модели>
2. Положите ключи в переменные окружения и запустите роутер. Ожидаемый результат: в терминале появляется строка вида `Listening on 0.0.0.0:4000` и процесс не завершается.
Пример:
export PROVIDER_A_KEY="sk-ваш-ключ"
python -m router --config ./router-config.yaml
Почему это работает: роутер читает конфиг, поднимает HTTP-сервер на порту 4000 и держит ключи в памяти процесса. Ключ из `api_key_env` не попадает в сам файл конфига — его безопаснее коммитить и передавать коллегам.
3. Проверьте, что порт слушается и роутер жив. Ожидаемый результат: команда печатает список моделей, среди них `fast` и `local` — это ваши алиасы, а не имена моделей провайдеров.
Пример:
curl http://127.0.0.1:4000/v1/models \
-H "Authorization: Bearer sk-router-local-000"
Ключ `sk-router-local-000` — это ключ самого роутера, он придумывается вами и не равен ключам провайдеров. Почему так: клиент обязан передать заголовок `Authorization`, иначе роутер не поймёт, что запрос «свой».
4. Сделайте первый реальный запрос к чату. Ожидаемый результат: в ответе приходит JSON, где текст ответа лежит в `choices[0].message.content`.
Пример:
curl http://127.0.0.1:4000/v1/chat/completions \
-H "Authorization: Bearer sk-router-local-000" \
-H "Content-Type: application/json" \
-d '{"model":"fast","messages":[{"role":"user","content":"Привет! Ответь одной строкой."}]}'
Почему это работает: вы отправили запрос в формате OpenAI, а роутер по алиасу `fast` нашёл нужного провайдера и перевёл запрос в его формат.
5. Повторите шаг 4, заменив `"model":"fast"` на `"model":"local"`. Ожидаемый результат: ответ приходит от второй модели, а адрес и ключ в команде не менялись. Это и есть главная выгода: один API — много провайдеров. Если вторым провайдером у вас локальная модель на телефоне или мини-ПК, пригодится гайд про запуск ИИ-модели локально на Android — такую модель тоже можно завернуть в роутер как отдельного провайдера.
6. Направьте на роутер своего постоянного клиента. В настройках приложения найдите поля base URL и API key и укажите `http://127.0.0.1:4000/v1` и ключ роутера. Для агента в браузере порядок действий описан в гайде про подключение ИИ-агента к Chrome. Ожидаемый результат: клиент перестаёт просить отдельные ключи и отвечает на запросы как обычно.
7. Добавьте правила выбора модели. Ожидаемый результат: у вас появляется логика «дешёвая модель по умолчанию, дорогая — по явному запросу», и это видно в конфиге, а не в коде.
Пример:добавьте в конфиг раздел с правилом по умолчанию:
routing:
default_alias: fast
fallback_alias: local # сюда уйдём, если основной провайдер недоступен
Почему это работает: роутер сначала смотрит на явный `model` в запросе, а если его нет — берёт alias из правил. Так один и тот же код клиента обслуживает и обычные, и «страховочные» сценарии.
8. Проверьте поведение при недоступном провайдере. Ожидаемый результат: запрос по-прежнему завершается ответом, но уже от fallback-провайдера.
Пример:временно испортите ключ основного провайдера на заведомо неверный, повторите команду из шага 4 и убедитесь, что ответ всё равно пришёл. После проверки верните ключ на место.

Проблема 1: ошибка вида `401 Unauthorized`.Симптом: в ответе JSON с полем `error` и текстом про invalid API key. Решение: сначала проверьте ключ роутера — он должен совпадать с тем, что вы придумали, и передаваться в заголовке `Authorization: Bearer ...`. Если ключ роутера верный, проблема в ключе провайдера: убедитесь, что переменная окружения (например, `PROVIDER_A_KEY`) реально задана в том же терминале, где запущен роутер — команда `echo $PROVIDER_A_KEY` должна вывести непустую строку. После правки переменной роутер нужно перезапустить, потому что старые значения он уже прочитал в память.
Проблема 2: ошибка вида `model not found` или ответ «модель не поддерживается».Симптом: запрос уходит, но провайдер отвечает, что такого имени модели у него нет. Решение: различайте alias и имя модели. В запросе клиента должно стоять ваше короткое имя (`fast`, `local`), а в конфиге в поле `model` — то имя, которое требует провайдер. Проверьте обе строки и перезапустите роутер. Быстрый тест — `curl http://127.0.0.1:4000/v1/models`: если в списке нет нужного alias, роутер просто не знает о нём.
Проблема 3: клиент игнорирует роутер и ходит напрямую.Симптом: в логах провайдера видно запросы, а в логах роутера — тишина. Решение: проверьте, что в настройках клиента адрес оканчивается на `/v1` и не содержит лишних пробелов или завершающего слэша; часть клиентов сохраняет старый адрес в скрытом профиле — перезапустите приложение. Если клиент запускается из скрипта, посмотрите, не переопределяет ли он base URL где-то ещё: частая причина — переменная окружения с базовым адресом, заданная раньше.
Проблема 4: таймауты и `429 Too Many Requests`.Симптом: ответ приходит не сразу, а через десятки секунд, либо с ошибкой про превышение лимита. Решение: это не сломанный роутер, а поведение провайдера — у него есть лимит запросов в минуту. Задайте fallback-алиас (шаг 7) и переводите лишние запросы на второй провайдер; заодно увеличьте таймаут в клиенте, если он у вас настраивается.
Чем роутер отличается от того, чтобы просто подставить другой ключ в клиенте?
Роутер даёт единый адрес и единый ключ для всех провайдеров сразу. Если подставлять ключ в клиенте, придётся для каждого провайдера менять настройки, а при смене модели — править код или настройки в нескольких местах. С роутером меняется одна строка в `router-config.yaml`.
Обязателен ли отдельный сервер, или можно запустить на ноутбуке?
Для обучения и личных задач ноутбука достаточно: роутер — обычный процесс на порту `4000`, он не требует много ресурсов. Отдельная машина нужна, только если клиентами пользуются другие люди или сервисы и роутер должен быть доступен круглосуточно.
Что делать, если у провайдера не OpenAI-совместимый формат запросов?
Проверьте, есть ли у провайдера отдельный OpenAI-совместимый эндпоинт — многие его предоставляют, тогда достаточно указать правильный `base_url` в конфиге. Если такого эндпоинта нет, провайдера нельзя подключить простым описанием в конфиге: нужен адаптер, который переводит формат OpenAI в формат провайдера.
Безопасно ли хранить ключи в файле конфига?
Ключи роутера (те, что клиенты передают в заголовке) — не секрет, их можно держать в конфиге. Ключи провайдеров лучше держать в переменных окружения и ссылаться на них через поле `api_key_env`, как в примере выше. Так файл конфига можно безопасно хранить в репозитории и передавать коллегам, а сами ключи остаются только на машине.