Технологии

OpenAI-совместимый API: почему все делают одинаково

Совместимый интерфейс позволяет сменить провайдера правкой одного адреса. Разбираем, что именно совместимо, и где совместимость заканчивается, ломая интеграции.

Открываете документацию нового сервиса, а там написано: «наш 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-командой и посмотрите форму ответа: есть ли список вариантов, лежит ли текст в поле сообщения, приходит ли блок статистики. Пять минут проверки экономят вечер отладки внутри приложения. Обзор сервисов и их особенностей — в статье о том, где взять ключ к языковой модели.

Хотите ИИ, который помнит вас?

Persona держит контекст между чатами и работает на вашем железе — если развернуть её у себя и подключить локальную модель.

Создать аккаунт