Открываете документацию нового сервиса, а там написано: «наш API совместим с OpenAI, просто поменяйте адрес». Вопрос, на который эта статья отвечает: насколько «просто» и до какого места.
#Короткий ответ
Совместим базовый вызов — отправить сообщения и получить текст. Этого хватает для чата, и именно поэтому смена провайдера в простом случае сводится к трём правкам: адрес, ключ, имя модели. Всё, что сложнее базового вызова, — вызов инструментов, структурированный вывод, изображения, точный формат потока, служебные заголовки — совместимо частично или не совместимо вовсе. Интеграции ломаются ровно на этой границе, и почти никогда — на самом чате.
#Как это устроено
Формат, который скопировали почти все, состоит из четырёх частей.
Адрес. POST по пути, оканчивающемуся на /chat/completions. В документации сервиса обычно указан корень вида https://api.сервиса.com/v1, а хвост дописывает клиент.
Тело запроса. Минимальный набор полей одинаков у всех:
{
"model": "имя-модели",
"messages": [
{"role": "system", "content": "инструкция"},
{"role": "user", "content": "вопрос"},
{"role": "assistant", "content": "предыдущий ответ"}
],
"temperature": 0.7,
"max_tokens": 512,
"stream": false
}
Ответ. Текст лежит в первом элементе списка вариантов, в поле сообщения. Рядом — блок со статистикой израсходованных токенов.
Поток. При "stream": true ответ приходит серией событий, каждое с куском текста, и завершается служебным маркером.
Почему так вышло: формат появился первым и оказался достаточным. Новому сервису дешевле повторить чужой интерфейс, чем убеждать разработчиков переписывать клиенты, — совместимость стала условием попадания на рынок, а не любезностью. Побочный эффект приятный: инструмент, написанный под один сервис, работает с десятком других, включая локальный сервер на вашей машине — как поднять такой, разобрано в материале про HTTP-сервер llama.cpp.
#Где совместимость заканчивается
Таблица — главное содержание статьи. Смотрите на правую колонку: там то, что нужно проверить перед переездом.
| Что | Совместимо | На что смотреть |
|---|---|---|
| Отправка сообщений и ответ | ✓ | ничего |
| Потоковая выдача | ✓ | формат последнего события различается |
| Системная инструкция | частично | часть сервисов ждёт её отдельным полем |
| Статистика токенов | частично | поля есть не всегда, при потоке часто отсутствуют |
| Вызов инструментов | ✗ | схема описания и формат ответа расходятся |
| Структурированный вывод | ✗ | у каждого свой способ задать формат |
| Изображения на входе | ✗ | блоки контента устроены по-разному |
| Имена моделей | ✗ | у каждого сервиса свои |
| Коды и тексты ошибок | ✗ | одинаковые коды, разные тела |
| Заголовки лимитов | ✗ | имена заголовков различаются |
Вывод из таблицы: совместимость покрывает разговор и не покрывает автоматизацию. Пока приложение переписывается с моделью текстом, переезд занимает минуту. Как только оно даёт модели инструменты или требует ответ строгой структуры, каждый провайдер становится отдельным случаем.
Отдельная категория — вендоры, которые совместимость не изображают вовсе: у них другой путь, другой заголовок авторизации и другая форма ответа. Разбор такого случая — в гайде по API Anthropic и его отличиям формата.
#Что из этого следует на практике
Три вещи должны быть настройками, а не константами: адрес эндпоинта, имя модели, ключ. Если хотя бы одна зашита в код, смена провайдера превращается в правку кода и релиз.
Смена провайдера в простом случае выглядит так:
было: https://api.первого-сервиса.com/v1 + модель-А + ключ-1
стало: https://api.второго-сервиса.com/v1 + модель-Б + ключ-2
Ничего больше не меняется — при условии, что приложение пользуется только базовым вызовом.
Проверять переезд нужно на своих задачах, а не на «привет». Одинаковый протокол не означает одинаковых ответов: у каждой модели свои значения по умолчанию, своя длина контекста и своё поведение при длинной инструкции.
Совместимость — это ещё и способ подключить сервис, которого нет в списке. В Persona для этого есть отдельный пункт: вписываете адрес эндпоинта, точное имя модели и ключ, и работает любой сервис, говорящий этим протоколом, включая те, которых ещё не существует. Хвост /chat/completions дописывается автоматически, если вы указали только корень. Адрес обязан быть https — по обычному http ключ ушёл бы открытым текстом; исключение сделано только для адресов домашней сети, где своего сертификата обычно нет.
Как выстроить приложение так, чтобы смена провайдера была рутиной, разобрано в материале про переключение между провайдерами без переделки всего.
#Где интуиция подводит
«Совместимый — значит одинаковый». Одинаков конверт, а не содержимое. Две модели, отвечающие по одному протоколу, могут по-разному понимать одну и ту же инструкцию, и это нормально.
«Если чат работает, работает всё». Самая дорогая ошибка при переезде. Чат — самая простая часть протокола; ломаются инструменты, формат ответа и обработка ошибок, а обнаруживается это в проде.
«Агрегатор избавляет от несовместимости». Он прячет разницу в именах и в маршрутизации, но не устраняет её: возможности, которых у нижележащей модели нет, агрегатор не добавит. Что именно он даёт и чем вы за это платите — в гайде по OpenRouter.
«Локальный сервер — это другое». Наоборот: локальные серверы поддерживают тот же протокол намеренно, и именно поэтому переключение между облаком и своей машиной сводится к смене адреса.
#Частые вопросы
#Что писать в поле адреса — корень или полный путь?
Смотрите, чего ждёт ваш клиент. Многие библиотеки принимают корень вида https://api.сервиса.com/v1 и достраивают хвост сами; часть приложений шлёт запрос ровно по указанному адресу, и тогда нужен полный путь. Если получаете 404 — почти всегда дело в этом.
#Почему у некоторых сервисов путь без /v1?
Потому что стандарта нет: есть подражание удачному формату. Часть сервисов размещает эндпоинт в корне, часть добавляет свои сегменты вроде /openai/v1. Единственный источник истины — их собственная документация, а не аналогия с другим сервисом.
#Можно ли использовать официальную библиотеку одного вендора с чужим сервисом?
Обычно да, если библиотека позволяет задать базовый адрес и она обращается только к совместимым методам. Ломается это на возможностях, специфичных для конкретного вендора, — и ломается не при подключении, а при первом их использовании.
#Совместимы ли эмбеддинги?
Отдельный эндпоинт для векторов существует у многих сервисов и повторяет тот же стиль, но совпадение здесь ниже, чем в чате: различаются имена моделей, размерность векторов и ограничения на длину входа. Векторы, посчитанные разными моделями, между собой несравнимы в принципе.
#Как понять, действительно ли сервис совместим, до подключения?
Отправьте базовый запрос curl-командой и посмотрите форму ответа: есть ли список вариантов, лежит ли текст в поле сообщения, приходит ли блок статистики. Пять минут проверки экономят вечер отладки внутри приложения. Обзор сервисов и их особенностей — в статье о том, где взять ключ к языковой модели.