Провайдеры
Провайдер — это один апстрим: имя, тип (диалект), адрес и набор ключей. Настраивается в панели, применяется гейтом без перезапуска.
Типы¶
| Тип | Кого обслуживает |
|---|---|
openai |
OpenAI и любой OpenAI-совместимый сервер: Ollama, vLLM, llama.cpp, LM Studio, прокси |
anthropic |
Anthropic Messages |
google |
Google Gemini (Generative Language API) |
vertex |
Google Vertex AI |
Тип выбирает адаптер, а не поставщика. Для самостоятельно поднятой модели берите
openai и укажите свой базовый адрес — отдельного типа для vLLM нет и не нужно.
Имя провайдера¶
Имя — это одновременно:
- идентификатор в журнале и на страницах;
- префикс в адресации моделей:
имя/модель; - то, с чем сравнивается ограничение ключа по провайдерам.
Поэтому имя лучше короткое и без слэшей. Регистр не важен — см. Адресация моделей.
Базовый адрес¶
Пусто — адрес по умолчанию для типа. Заполнено — используется как есть.
Адрес может уже содержать версию (http://ollama:11434/v1) — второй раз она не
добавится. Ключ провайдера может переопределить адрес для себя: так адресуется
отдельный регион или отдельный прокси на тот же тип.
Модели¶
Список моделей провайдера — это таблица маршрутов: голое имя модели из запроса ищется в ней. Пишутся имена без префикса провайдера.
Модель может вести только к одному провайдеру — это ограничение базы, а не соглашение. Если два провайдера обслуживают одну модель, различайте их префиксом в запросе.
Если провайдер один, список можно оставить пустым: любая модель уйдёт к нему. Это удобно и рискованно одновременно — опечатка в имени модели тоже уйдёт наверх, и ошибку вернёт провайдер, а не гейт.
Кнопка «Спросить /v1/models»¶
Рядом с полем моделей есть кнопка, которая спрашивает у провайдера его каталог — тем же секретом, адресом и заголовками, с которыми пойдёт трафик.
Зачем: списки моделей набираются руками и тихо расходятся с тем, что провайдер отдаёт на самом деле. Кнопка отвечает на вопрос «что этот ключ реально умеет» до того, как он попадёт в бой.
- В настройках ключа секрет берётся из формы, ещё до сохранения — так проверяется свежевведённый ключ. Пустое поле секрета у сохранённого ключа означает «использовать сохранённый».
- У ключа пользователя, рядом с разрешёнными моделями, спрашиваются все провайдеры сразу, каждый своим ключом.
- Ответ не кэшируется.
- Vertex AI так не спросить: его каталог выдаётся только по токену OAuth сервисного аккаунта, и об этом честно написано в ответе кнопки.
Для локального сервера без авторизации заголовок с ключом не отправляется вовсе
— пустой Bearer некоторые серверы отвергают.
Ключи провайдера¶
Один провайдер — сколько угодно ключей. Между ними распределяется нагрузка.
| Поле | Смысл |
|---|---|
| Метка | видна в журнале и метриках; секрет не показывается |
| Секрет | шифруется AES-256-GCM, в базе только шифротекст |
| Вес | доля нагрузки среди ключей провайдера |
| Модели этого ключа | пусто — все модели провайдера |
| Свой базовый адрес | переопределяет адрес провайдера |
| Заголовки ключа | добавляются к заголовкам провайдера |
Выбор ключа — smooth weighted round-robin (тот же алгоритм, что у nginx): каждый кандидат накапливает свой вес каждый проход, побеждает наибольшая сумма, потом возвращает пулу общую. В сравнении со взвешенно-случайным он раскладывает нагрузку ровно на любом масштабе, а не только в среднем — что важно, когда связывающее ограничение это лимит провайдера на ключ.
Ключ, получивший 429, 401, 403 или 5xx, уходит в cooldown: 5 минут для отказа авторизации (сам он не починится) и от секунды с экспоненциальным ростом до минуты для остального. Если все подходящие ключи в cooldown, берётся тот, которому осталось меньше всех — отказать запросу хуже, чем попробовать ещё раз.
Ограничение ключа по моделям не мешает перечислять каталог: /v1/models
приходит без имени модели, и ограничение к нему не применяется. Оно применяется
к результату — модель, которую не обслуживает ни один включённый ключ, из
каталога выпадает.
Vertex AI¶
Для типа vertex у ключа есть проект, регион и JSON сервисного аккаунта. JSON
шифруется так же, как секрет. Каталог моделей кнопкой не спрашивается.
Особенности диалектов¶
Панель показывает только те переключатели, которые относятся к выбранному типу.
OpenAI:
- Есть эндпоинт
/v1/responses— если выключено, запросы Responses транслируются в chat completions. - Отправлять чат через
/v1/responses— обратная сторона: запросы к/v1/chat/completionsуходят на/v1/responses, а ответ переводится назад. См. ниже. - Отправлять
max_completion_tokensвместоmax_tokens— требуется reasoning-моделям OpenAI и отвергается большинством совместимых серверов. Это тот флаг, из-за которого «всё работало, а потом перестало».
Anthropic: anthropic-version и anthropic-beta.
Chat и Responses в обе стороны¶
Два флага, и они независимы.
| Флаг | Про что | Что делает |
|---|---|---|
Есть эндпоинт /v1/responses |
у апстрима нет Responses | запрос Responses → chat completions, ответ обратно |
Отправлять чат через /v1/responses |
апстрим ушёл на Responses | запрос chat → Responses, ответ обратно |
Второй нужен, когда апстрим либо /v1/chat/completions не обслуживает вовсе,
либо обслуживает беднее, чем модель умеет: сводки рассуждений и встроенные
инструменты у части моделей доступны только через Responses.
Не один трёхпозиционный переключатель, а два флага — потому что апстрим может честно предлагать оба эндпоинта, и какой предпочесть для какой формы запроса решает оператор.
Клиент разницы не видит ни в одном направлении: спросил в диалекте чата —
получил chat.completion, спросил в Responses — получил response.
Поток тоже переводится. Здесь два формата расходятся сильнее всего:
Responses нумерует все элементы вывода одной последовательностью — текст,
вызовы инструментов, рассуждения, — а чат-клиент нумерует только свои вызовы
инструментов, с нуля, в порядке появления. Поэтому перевод потока хранит
состояние: соответствие индексов запоминается по ходу, и вызов, который у
Responses лежал под индексом 1, доезжает до клиента под индексом 0. Причина
приземлённая: клиент решает, запускать ли инструменты, по этому индексу и по
finish_reason, и ошибка здесь означает не сломанную вёрстку, а незапущенный
инструмент.
Если выставить оба флага сразу, запрос не зациклится: перевод в каждую сторону происходит один раз и попадает прямо на нужный эндпоинт. Такая конфигурация бессмысленна, но ведёт себя как один запрос, а не как переполнение стека.
Нагрузка и повторы¶
| Поле | По умолчанию | Смысл |
|---|---|---|
| Одновременных запросов | 256 | семафор на провайдера; 0 — без ограничения |
| Длина очереди | 0 | 0 значит восьмикратная ёмкость; при переполнении сразу 429 |
| Попыток | 3 | каждая берёт другой ключ |
| Начальная пауза | 200 мс | с джиттером |
| Максимальная пауза | 5000 мс |
Быстрый 429 при переполнении очереди — намеренно: бесконечное ожидание хуже внятного отказа, по которому клиент сделает бэкофф.
Сеть и заголовки¶
Заголовки — JSON-объект, отправляется с каждым запросом к провайдеру.
Заголовки ключа перебивают заголовки провайдера. Их же отправляет и кнопка
«Спросить /v1/models»: иначе она задавала бы не тот вопрос, что трафик.
Транспорт — необязательный JSON: dial_timeout_ms,
response_header_timeout_ms, request_timeout_ms, max_idle_conns_per_host,
idle_conn_timeout_ms, disable_http2.
Фолбэки¶
Запрос может нести цепочку provider/model. Гейт идёт по ней, когда ошибка того
стоит: временные сбои и отсутствие возможности — да, «неверный запрос» — нет,
он будет столь же неверным у следующего провайдера, и обход цепочки превратил бы
один внятный 400 в несколько путаных.
Доступ проверяется на каждой цели отдельно: ключ, которому запрещён основной провайдер, но разрешён резервный, уедет на резервный, а не получит отказ.