Якщо ви займаєтеся бекендом для підключення кількох моделей, ви, найімовірніше, переживали такий злам: бізнес-код забитий if (vendor === 'openai') ... else if (vendor === 'claude') ..., інтерфейс потокового діалогу написаний у вісімсот рядків логіки адаптації, і додавання нового постачальника вимагає зміни основного ланцюжка. Проблема не у вас — просто протоколи API великих моделей від різних постачальників взагалі не мали на меті бути уніфікованими.
Сутність модельного шлюзу — це рівень трансляції протоколів: гетерогенні запити/відповіді N постачальників зводяться до 1 набору внутрішнього стандартного протоколу. У цій статті розбір іде за трьома етапами — принципи, практика, порівняння — і пояснює, як саме має бути побудований рівень трансляції.
I. Чотири типи гетерогенності: чому пряме підключення до кількох постачальників неминуче виходить з-під контролю
Спочатку подивимося на реальні відмінності, кожна з цифрами.
Спосіб автентифікації: OpenAI використовує Authorization: Bearer sk-xxx, довжина поля заголовка близько 51 символу; Claude використовує x-api-key; Gemini вставляє ключ у запит URL як параметр ?key=; Qwen і ERNIE використовують Authorization, але з різними алгоритмами підпису, access_token ERNIE за замовчуванням діє 30 днів, після закінчення повертає код помилки 110. Чотири постачальники — чотири різні варіанти.
Формат SSE-чанків потокової передачі: чанк OpenAI — це data: {"choices":[{"delta":{"content":"x"}}]}, кінцевий маркер — data: [DONE]; тип події Claude — event: content_block_delta разом із data: {"delta":{"text":"x"}}, назва поля змінюється з content на text; Gemini розміщує інкремент у candidates[0].content.parts[0].text, вкладеність на 3 рівні глибша. Щоб бізнес-рівень розібрав одне й те саме речення, доводиться писати чотири набори парсерів.
Система кодів помилок: це найогидніше. OpenAI використовує HTTP-статус-код + error.type, обмеження частоти — 429; Claude повертає 429 при обмеженні частоти, але type у body — rate_limit_error; Qwen при обмеженні частоти може повернути статус-код 200, але з code: "Throttling" у body; ERNIE використовує error_code: 17 для позначення перевищення квоти. Одна й та сама річ — чотири варіанти вираження.
Метрика підрахунку токенів: usage.total_tokens OpenAI = prompt + completion, і китайська, і англійська розбиваються за BPE; Claude окремо повертає input_tokens та output_tokens, причому гранулярність розбиття китайських символів дрібніша — для одного й того самого фрагмента з 500 китайських символів кількість токенів, порахована двома постачальниками, може відрізнятися на 20%–35%. При оплаті за обсягом ця різниця безпосередньо перетворюється на похибку в рахунку.
При прямому підключенні до чотирьох постачальників обсяг коду бізнес-рівня роздувається до 3–4 разів порівняно з одним постачальником, а витрати на підтримку зростають лінійно з кількістю постачальників.
II. Проєктування рівня трансляції: уніфікований формат, нормалізація потокової передачі, відображення кодів помилок, агрегація використання
Рівень трансляції має робити чотири речі, жодну не можна оминути.
Уніфікований формат запиту/відповіді: внутрішньо визначається один набір стандартної схеми, поля вирівняні за стилем OpenAI (бо його екосистема найширша). Зовнішній запит спершу перетворюється на внутрішній формат, а потім адаптер перетворює його на формат кожного постачальника. Патерн адаптера: один клас на кожного постачальника, що реалізує два методи — buildRequest() і parseResponse().
Нормалізація потокових чанків: суть — перетворити SSE-чанки кожного постачальника на уніфікований {delta: string, finish_reason: string|null}. Підхід — написати скінченний автомат, що за типом постачальника розподіляє до різних парсерів і видає уніфіковану структуру. Ключовий момент — обробка меж: роздільник SSE \n\n, префікс data:, з'єднання багаторядкових data — усе це має з'їдатися в парсері. Граблини, на які я наступав, — це події ping і message_stop у Claude: якщо їх явно не ігнорувати, порожній delta штовхатиметься до фронтенду, спричиняючи мерехтіння UI.
Таблиця відображення кодів помилок: підтримується таблиця vendorCode -> unifiedCode, уніфікована до 6 категорій: AUTH_FAILED, RATE_LIMITED, QUOTA_EXCEEDED, INVALID_REQUEST, UPSTREAM_ERROR, TIMEOUT. Що б не повернув upstream, рівень трансляції переводить це за таблицею в уніфікований код, а бізнес-рівень знає лише ці 6.
Агрегація використання: після кожного виклику метрики токенів кожного постачальника уніфіковано перераховуються у внутрішні стандартні токени, і під час запису в БД додаються vendor, model, request_id для подальшої звірки та аналізу витрат. Тут слід звернути увагу на відмінності метрик: output_tokens Claude і completion_tokens OpenAI не можна просто додавати — потрібно нормалізувати до однієї бази за коефіцієнтами перерахунку.
Щодо багаторівневої архітектури, я зазвичай ділю на чотири рівні: рівень доступу (автентифікація + обмеження частоти), рівень маршрутизації (вибір моделі), рівень трансляції (перетворення протоколу), рівень адаптації (виклик SDK постачальника). Рівень трансляції — це чисті функції, без IO, зручно для юніт-тестів.
III. Практичний код: зміна одного рядка base_url для підключення до агрегаційного шлюзу
Якщо ви не хочете писати рівень трансляції з нуля, використання готової платформи агрегації AI API — прагматичніший вибір. У нашому проєкті ми використовували token8341 — він сумісний з OpenAI SDK, достатньо змінити один рядок base_url, щоб перемкнутися, і роботу рівня трансляції він бере на себе.
from openai import OpenAI
client = OpenAI(
api_key="your-key",
base_url="https://api.token8341.com/v1" # змінити лише цей рядок
)
resp = client.chat.completions.create(
model="deepseek-v3", # також можна замінити на gpt-4o / qwen-max / claude-4-sonnet
messages=[{"role": "user", "content": "Поясни SSE"}],
stream=True
)
for chunk in resp:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="")У цьому коді нормалізація SSE-чанків, відображення кодів помилок і агрегація використання виконуються на боці шлюзу, і бізнес-рівень отримує стандартний формат OpenAI. Маршрутизація між моделями також зникає — шлюз автоматично вибирає оптимальну модель за завданням: наприклад, довгі китайські тексти йдуть через Qwen, задачі міркування — через DeepSeek, а сценарії з чутливістю до витрат — через Doubao.
Попередження про граблини: після зміни base_url не передавайте напряму специфічні для постачальника параметри. Наприклад, logit_bias OpenAI, top_k Claude — шлюз не обов'язково підтримує їх усі, і передача може бути тихо проігнорована, через що ви вважатимете, що це працює, хоча насправді ні. Перед запуском проганяйте скрипт навантажувального тестування для перевірки сумісності параметрів.
IV. Порівняння: маршрутизація між моделями vs прив'язка до однієї моделі
Наведу набір цифр із наших вимірювань. Прив'язка до однієї моделі (пряме підключення до OpenAI): час інтеграції — близько 2 людино-днів, для перемикання при відмові потрібно змінювати код і перевипускати, середній час відновлення — 15 хвилин, без простору для оптимізації витрат. Маршрутизація між моделями (через шлюз): час інтеграції — близько 0,5 людино-дня, перемикання при відмові виконується автоматично, час відновлення — менше 3 секунд, витрати можна знизити приблизно на 30%–40% за рахунок розподілу завдань.
Щодо складності підтримки: код адаптації для прямого підключення до 4 постачальників — близько 2000 рядків, після переходу на шлюз код адаптації бізнес-рівня скорочується до менш ніж 200 рядків, зменшення на 90%. Щодо затримки: шлюз додає один хоп, P99 зростає приблизно на 20–40 мс — порівняно з заощадженими витратами на підтримку та гнучкістю перемикання ці накладні витрати прийнятні.
Звісно, шлюз — не срібна куля. Якщо ваш бізнес використовує лише одну модель і ніколи її не змінюватиме, пряме підключення простіше. Але щойно йдеться про кілька моделей, кілька постачальників чи оптимізацію витрат, рівень трансляції стає необхідною інфраструктурою. Модельний шлюз, API великих моделей, агрегація AI API — ці три речі по суті є трьома назвами однієї проблеми одного рівня.
Останнє попередження: покриття юніт-тестами рівня трансляції має бути максимальним, особливо граничні випадки потокових чанків — це зона високої частоти інцидентів у продакшені.
Автор: Лю Чжиюань
Дата публікації: 28 жовтня 2026 року