OpenRouter продаёт себя как один API для сотен моделей: указал model ID, а сервис сам подберёт самого дешёвого и доступного провайдера. Звучит удобно, пока в один прекрасный день ответы модели не начинают вести себя иначе — без изменений в вашем коде. Разработчик Мохамед Мустафа собрал список причин, почему так происходит, и что с этим делать до того, как это сломает ваш продакшен.

Зачем вообще нужен этот автоматический роутинг

Идея OpenRouter в том, чтобы дать разработчику один эндпоинт вместо десятка SDK разных провайдеров. Вы меняете base_url на https://openrouter.ai/api/v1, подставляете свой ключ OpenRouter — и получаете доступ к моделям OpenAI, Anthropic, Google, Meta и других через единый интерфейс, максимально похожий на OpenAI SDK.

Под капотом сервис сам решает, к какому бэкенд-провайдеру отправить запрос: он «handles fallbacks automatically and picks the most cost-effective option for each request» — то есть автоматически переключается между провайдерами при сбоях и выбирает самый выгодный вариант по цене. Для многих сценариев это плюс: меньше даунтайма, ниже счета.

Проблема в том, что за одним model ID у OpenRouter могут стоять несколько разных провайдеров, которые физически хостят одну и ту же модель на разном софте для инференса (serving software — программный стек, который запускает модель и обрабатывает запросы). А разный софт — это разные оптимизации, разные настройки по умолчанию и разное поведение на одних и тех же входных данных.

Что именно ломается на практике

Мустафа перечисляет несколько конкретных мест, где расхождение между провайдерами всплывает не в теории, а в реальных багах.

Во-первых, не все провайдеры одной и той же модели поддерживают одинаковый набор возможностей. Например, у некоторых провайдеров может отсутствовать поддержка vision (обработка изображений) даже для модели, которая формально заявлена как vision-capable — то есть умеющая работать с картинками. Если ваш роутинг случайно попадёт на такого провайдера, запрос с картинкой либо упадёт с ошибкой, либо будет обработан не так, как вы ожидали.

Во-вторых, по-разному обрабатывается параметр reasoning effort (степень усилий на рассуждение — настройка, которая управляет тем, сколько «размышлений» модель тратит перед финальным ответом у reasoning-моделей вроде o1/o3 или похожих). Один провайдер может честно передавать это значение в модель, другой — интерпретировать его иначе или вовсе игнорировать. В итоге одна и та же настройка в вашем коде даёт разное качество и разную скорость ответа в зависимости от того, куда вас в этот раз направил роутер.

В-третьих, сам serving-стек влияет на детали генерации: батчинг запросов, квантизацию (сжатие весов модели для ускорения инференса), таймауты — всё это может отличаться между провайдерами, даже если веса модели формально идентичны. Для тестов и продакшена, где важна воспроизводимость, это создаёт скрытую нестабильность: сегодня всё работает, завтра тот же промпт даёт другой результат, потому что роутер выбрал другого провайдера.

Как зафиксировать конкретного провайдера

Хорошая новость: OpenRouter даёт способ обойти автоматический выбор — параметр provider.only, который явно ограничивает список провайдеров, куда может уйти запрос.

Пример через прямой HTTP-запрос:

bash
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-3.5-sonnet",
    "provider": {
      "only": ["anthropic"]
    },
    "messages": [
      {"role": "user", "content": "Проверь этот запрос на конкретном провайдере"}
    ]
  }'

Тот же принцип работает и через OpenAI-совместимый Python SDK — вы просто передаёте дополнительное поле provider в теле запроса:

python
from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key="ВАШ_OPENROUTER_API_KEY",
)

response = client.chat.completions.create(
    model="anthropic/claude-3.5-sonnet",
    extra_body={
        "provider": {
            "only": ["anthropic"]
        }
    },
    messages=[
        {"role": "user", "content": "Тестовый запрос с фиксированным провайдером"}
    ],
)

print(response.choices[0].message.content)

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

Как узнать, какие провайдеры вообще доступны для модели

Прежде чем фиксировать провайдера, нужно понять, кто вообще стоит за конкретным model ID. Для этого у OpenRouter есть метод /endpoints, который возвращает список всех доступных провайдеров для указанной модели.

bash
curl https://openrouter.ai/api/v1/models/anthropic/claude-3.5-sonnet/endpoints \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"

Ответ придёт в JSON и будет содержать список провайдеров с их параметрами — включая поддерживаемые возможности, лимиты по контексту и цены за токен:

json
{
  "data": {
    "id": "anthropic/claude-3.5-sonnet",
    "endpoints": [
      {
        "provider_name": "anthropic",
        "context_length": 200000,
        "supports_vision": true
      },
      {
        "provider_name": "another-provider",
        "context_length": 128000,
        "supports_vision": false
      }
    ]
  }
}

Этот список стоит сверять перед тем, как выбирать модель для vision-задач или reasoning-сценариев — иначе легко наткнуться на провайдера, у которого нужной вам возможности просто нет.

Где ломается и что с этим делать

Самая частая ошибка — доверять единому model ID как гарантии единого поведения. Это не так: за одним ID может стоять несколько бэкендов с разным качеством, разной скоростью и разным набором функций.

Вторая типичная проблема — тестировать интеграцию один раз и считать её стабильной навсегда. Роутинг OpenRouter динамический: провайдер, который отвечал вчера, может быть недоступен сегодня из-за перегрузки или изменения цен, и запрос уйдёт к другому. Если у вас нет мониторинга того, какой провайдер реально обработал запрос (это видно в ответе API в поле provider), отладка странного поведения превращается в гадание.

Третье — reasoning effort и другие тонкие параметры лучше явно тестировать на каждом провайдере из списка /endpoints, а не полагаться на то, что все они интерпретируют параметр одинаково.

Что попробовать дальше

Для критичных продакшен-сценариев имеет смысл держать provider.only с конкретным провайдером как значение по умолчанию, а автоматический роутинг оставлять только для некритичных или экспериментальных запросов, где важна цена, а не стабильность поведения. Периодически проверяйте /endpoints для используемых моделей — список провайдеров и их возможностей меняется, и то, что работало вчера, может выглядеть иначе через месяц.

FAQ

Что такое provider.only в OpenRouter?

Это параметр в теле запроса, который ограничивает список провайдеров, куда OpenRouter может направить ваш запрос. Указав конкретного провайдера, вы отключаете автоматический выбор и получаете предсказуемое поведение модели.

Почему один и тот же промпт даёт разные ответы через OpenRouter?

Потому что за одним model ID может стоять несколько провайдеров с разным serving-софтом, оптимизациями и обработкой параметров вроде reasoning effort. Роутер может выбирать разного провайдера от запроса к запросу.

Как узнать, какие провайдеры доступны для модели?

Через метод /endpoints, например GET /api/v1/models/{model}/endpoints. Ответ содержит список провайдеров с их возможностями — включая поддержку vision, лимит контекста и цены.

Замедлит ли фиксация провайдера мои запросы?

Может, если выбранный провайдер перегружен, а роутер в автоматическом режиме перенаправил бы вас на более свободного. Компромисс между стабильностью поведения и гибкостью роутинга нужно оценивать под конкретную задачу.

Стоит ли вообще использовать OpenRouter, если есть такие подводные камни?

Да, если вам важна экономия и отказоустойчивость, а к разбросу в поведении между провайдерами вы готовы. Для строгих продакшен-сценариев просто фиксируйте провайдера через provider.only вместо того, чтобы полагаться на автоматический выбор.

Источники