Когда подключаешь OpenAI-совместимый gateway — агрегатор, который прячет несколько
провайдеров за одним адресом, — первое движение руки предсказуемо: дёрнуть
GET /v1/models и поверить списку. Список врёт. Точнее, он честно отвечает не на
тот вопрос. Он перечисляет модели, которые gateway умеет маршрутизировать в
принципе, а не те, что ответят именно твоему ключу и именно твоему способу
вызова. Между «есть в каталоге» и «вернёт ответ на мой запрос» стоят две
отдельные стены, и /v1/models не знает ни про одну.
Первая стена — ключ. Каталог у такого gateway общий на всех, а привязка живых
провайдеров идёт per-key: за моделью в списке может не стоять ни одного
включённого для тебя апстрима. На вызове это выглядит как no_available_providers
— не «модель сломана», а «под твоим ключом за ней пусто». Каталог показывает
витрину, ключ определяет, что из витрины реально отпустят.
Вторая стена тоньше и интереснее. Даже среди моделей, которые ключу доступны,
не все отвечают на одном и том же пути. Одна отдаёт результат только на
классическом /v1/chat/completions. Другая там молчит той же ошибкой
no_available_providers — но оживает на /v1/responses, эндпоинте протокола, на
котором работают агентные CLI. Та же строка ошибки на одном пути превращалась в
нормальный ответ на другом — для соседней модели. Значит дело было не в теле
запроса и не в ключе, а в том, что модель маршрутизируется через другой протокол
за тем же base URL. Ошибка говорила «не здесь», а звучала как «сломано».
Диагностика после этого стала механической: не доверять одному пробнику, а
прогнать матрицу «модель × эндпоинт» — каждую модель по каждому пути. Картина
вышла зеркальной: то, что мертво на одном протоколе, живёт на другом, и наоборот.
Пять минут curl в цикле дали карту, которую каталог не отдал бы никогда, потому
что каталог про неё и не знает — он одномерный, а доступность трёхмерная.
Отсюда урок, который я теперь применяю до того, как вписывать модель в
оркестратор или строить на ней fallback-цепочку. no_available_providers и
родственные ей ошибки почти никогда не значат «сломано» — они значат «не тем
путём». За одним адресом прячутся три независимые оси: ключ (что тебе
провиженено), протокол-эндпоинт (chat против responses), формат тела запроса.
Прежде чем чинить формат — а именно туда тянется рука по привычке — стоит
проверить первые две оси. GET /v1/models не ответит ни на одну из трёх; на них
отвечает только матрица.