Більшість людей не починають проєкт із думки про переносимість між постачальниками. Вони беруть ключ у того вендора, у якому найлегше зареєструватися, пишуть інтеграцію й випускають продукт. Через кілька місяців вони читають бенчмарк, або відкривають рахунок, або натрапляють на обмеження швидкості, і хочуть спробувати щось інше. Саме тоді й з'являється біль, бо вони заклали припущення про першого постачальника прямо в HTTP-шар.
Рішення — не якась абстракційна бібліотека. Це формат передачі даних. API чат-доповнень OpenAI тихо став типовим протоколом для спілкування з LLM, і щойно ви почнете писати під нього, перемикання постачальників зводиться переважно до зміни двох рядків.
API, який скопіювали всі
OpenAI визначив простий контракт: ви надсилаєте POST JSON на /v1/chat/completions, а у відповідь отримуєте рядок choices[0].message.content. Запит виглядає так:
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Explain DNS in one paragraph"}]
}'Майже кожна велика модель тепер говорить цією говіркою. DeepSeek, Qwen, ERNIE, Doubao та інші надають ендпоінт, який приймає те саме тіло запиту й повертає ту саму структуру. Це означає, що вашому клієнтському коду байдуже, хто на іншому кінці. Ви можете замінити рядок model і ендпоінт, і більше нічого не зміниться.
Ось той самий виклик, спрямований на агрегатор, який об'єднує кілька цих моделей під однією базовою URL-адресою:
curl https://api.token8341.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKENWORKS_API_KEY" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Explain DNS in one paragraph"}]
}'Один клієнт, багато моделей
У Python ви зазвичай створюєте екземпляр SDK OpenAI один раз і передаєте назву моделі для кожного запиту. Якщо ваш постачальник підтримує сумісний API, ви задаєте base_url один раз і залишаєте все інше без змін:
from openai import OpenAI
client = OpenAI(
base_url="https://api.token8341.com/v1",
api_key="sk-your-tokenworks-key",
)
def ask(model: str, prompt: str) -> str:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
# Same function, three different model families.
print(ask("gpt-4o", "Summarize this log for me."))
print(ask("deepseek-chat", "Summarize this log for me."))
print(ask("qwen-max", "Summarize this log for me."))Функція не змінюється. Змінюється рядок моделі. Ось і весь трюк. Стрімінг працює так само:
stream = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Write a haiku about databases"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content or ""
print(delta, end="")Виклик функцій, режим JSON і ембедінги — усе це працює через ту саму сумісну поверхню, тож при перемиканні ви не обмежені простим чатом.
Чого перемикання вам не дає
Я хочу чітко пояснити ту частину, яка не змінюється безкоштовно. Транспорт переносимо; моделі — ні.
Різні моделі по-різному реагують на підказки. Системна підказка, налаштована під GPT-4o, може працювати гірше на моделі міркування, яка потребує іншої структури. Контекстні вікна відрізняються, іноді дуже сильно: одна модель може приймати 128k токенів, а інша — 32k. Токенізатори теж різняться, тож той самий документ коштує різну кількість токенів на кожній моделі. Максимальна довжина виводу — ще один параметр, який залежить від моделі, а не від протоколу.
Отже, «перемикання без зміни коду» — це насправді «перемикання без переписування вашого HTTP-клієнта». Вам усе одно варто провести оцінювальний прогін, перш ніж спрямовувати продакшн-трафік на нову модель. Гарна новина в тому, що сумісний API робить таке оцінювання дешевим, бо тестовий стенд — це просто цикл за назвами моделей.
Практичний підхід
У своїх побічних проєктах я насправді тримаю невелику таблицю конфігурації, а не зміни в коді:
MODELS = {
"fast": "deepseek-chat",
"smart": "qwen-max",
"frontier": "gpt-4o",
}
def run(task, prompt):
return ask(MODELS[task], prompt)Хочете спробувати дешевшу модель для напрямку fast? Змініть один рядок. Хочете перейти на другого постачальника, коли перший обмежений за швидкістю? Перехопіть виняток і повторіть спробу з наступним рядком моделі. Ніщо з цього не торкається побудови запиту.
Саме на цьому шаблоні резервування багатомодельний ендпоінт і виправдовує себе. З однією базовою URL-адресою та одним ключем ви можете маршрутизувати, повторювати спроби й проводити A/B-тестування між постачальниками — усе через ті самі два рядки налаштування клієнта, які ви написали першого дня.
Особливості відповідей, за якими варто стежити
Транспорт сумісний, але кілька відмінностей на рівні відповідей усе одно просочуються. Моделі міркування іноді повертають додаткове поле на кшталт reasoning_content поряд зі звичайним повідомленням. Код, який читає choices[0].message.content, продовжує працювати незалежно від цього, але ви можете захотіти показати цей текст міркувань у режимі налагодження. Повідомлення про помилки теж різняться: один постачальник повертає корисний рядок insufficient_quota, інший — голий 429 без тіла відповіді. Якщо ви будуєте повторні спроби, сприймайте будь-який 429 або 5xx як «відступити й спробувати знову», а не розбирайте специфічний для вендора текст.
Ніщо з цього не є причиною залишатися прив'язаним до одного постачальника. Це причина тримати обробку помилок загальною, а вибір моделі — змінною.
Саме таку позицію займає SiCore TokenWorks: сумісний з OpenAI ендпоінт за адресою https://api.token8341.com/v1, де рядок моделі — єдине, що змінюється, коли ви переходите між GPT-4o, Claude, Gemini, DeepSeek, Qwen, ERNIE, Doubao, Spark і Pangu.