# OmniRoute Auto-Combo Engine (Українська) 🌐 **Languages:** 🇺🇸 [English](../../../../routing/AUTO-COMBO.md) · 🇪🇹 [am](../../../am/docs/routing/AUTO-COMBO.md) · 🇸🇦 [ar](../../../ar/docs/routing/AUTO-COMBO.md) · 🇦🇿 [az](../../../az/docs/routing/AUTO-COMBO.md) · 🇧🇬 [bg](../../../bg/docs/routing/AUTO-COMBO.md) · 🇧🇩 [bn](../../../bn/docs/routing/AUTO-COMBO.md) · 🇧🇦 [bs](../../../bs/docs/routing/AUTO-COMBO.md) · 🇨🇿 [cs](../../../cs/docs/routing/AUTO-COMBO.md) · 🇩🇰 [da](../../../da/docs/routing/AUTO-COMBO.md) · 🇩🇪 [de](../../../de/docs/routing/AUTO-COMBO.md) · 🇬🇷 [el](../../../el/docs/routing/AUTO-COMBO.md) · 🇪🇸 [es](../../../es/docs/routing/AUTO-COMBO.md) · 🇪🇪 [et](../../../et/docs/routing/AUTO-COMBO.md) · 🇮🇷 [fa](../../../fa/docs/routing/AUTO-COMBO.md) · 🇫🇮 [fi](../../../fi/docs/routing/AUTO-COMBO.md) · 🇫🇷 [fr](../../../fr/docs/routing/AUTO-COMBO.md) · 🇮🇪 [ga](../../../ga/docs/routing/AUTO-COMBO.md) · 🇮🇳 [gu](../../../gu/docs/routing/AUTO-COMBO.md) · 🇳🇬 [ha](../../../ha/docs/routing/AUTO-COMBO.md) · 🇮🇱 [he](../../../he/docs/routing/AUTO-COMBO.md) · 🇮🇳 [hi](../../../hi/docs/routing/AUTO-COMBO.md) · 🇭🇷 [hr](../../../hr/docs/routing/AUTO-COMBO.md) · 🇭🇺 [hu](../../../hu/docs/routing/AUTO-COMBO.md) · 🇦🇲 [hy](../../../hy/docs/routing/AUTO-COMBO.md) · 🇮🇩 [id](../../../id/docs/routing/AUTO-COMBO.md) · 🇳🇬 [ig](../../../ig/docs/routing/AUTO-COMBO.md) · 🇮🇹 [it](../../../it/docs/routing/AUTO-COMBO.md) · 🇯🇵 [ja](../../../ja/docs/routing/AUTO-COMBO.md) · 🇬🇪 [ka](../../../ka/docs/routing/AUTO-COMBO.md) · 🇰🇭 [km](../../../km/docs/routing/AUTO-COMBO.md) · 🇮🇳 [kn](../../../kn/docs/routing/AUTO-COMBO.md) · 🇰🇷 [ko](../../../ko/docs/routing/AUTO-COMBO.md) · 🇱🇹 [lt](../../../lt/docs/routing/AUTO-COMBO.md) · 🇱🇻 [lv](../../../lv/docs/routing/AUTO-COMBO.md) · 🇮🇳 [ml](../../../ml/docs/routing/AUTO-COMBO.md) · 🇮🇳 [mr](../../../mr/docs/routing/AUTO-COMBO.md) · 🇲🇾 [ms](../../../ms/docs/routing/AUTO-COMBO.md) · 🇲🇹 [mt](../../../mt/docs/routing/AUTO-COMBO.md) · 🇲🇲 [my](../../../my/docs/routing/AUTO-COMBO.md) · 🇳🇵 [ne](../../../ne/docs/routing/AUTO-COMBO.md) · 🇳🇱 [nl](../../../nl/docs/routing/AUTO-COMBO.md) · 🇳🇴 [no](../../../no/docs/routing/AUTO-COMBO.md) · 🇮🇳 [or](../../../or/docs/routing/AUTO-COMBO.md) · 🇮🇳 [pa](../../../pa/docs/routing/AUTO-COMBO.md) · 🇵🇭 [phi](../../../phi/docs/routing/AUTO-COMBO.md) · 🇵🇱 [pl](../../../pl/docs/routing/AUTO-COMBO.md) · 🇵🇹 [pt](../../../pt/docs/routing/AUTO-COMBO.md) · 🇧🇷 [pt-BR](../../../pt-BR/docs/routing/AUTO-COMBO.md) · 🇷🇴 [ro](../../../ro/docs/routing/AUTO-COMBO.md) · 🇷🇺 [ru](../../../ru/docs/routing/AUTO-COMBO.md) · 🇱🇰 [si](../../../si/docs/routing/AUTO-COMBO.md) · 🇸🇰 [sk](../../../sk/docs/routing/AUTO-COMBO.md) · 🇸🇮 [sl](../../../sl/docs/routing/AUTO-COMBO.md) · 🇷🇸 [sr](../../../sr/docs/routing/AUTO-COMBO.md) · 🇸🇪 [sv](../../../sv/docs/routing/AUTO-COMBO.md) · 🇰🇪 [sw](../../../sw/docs/routing/AUTO-COMBO.md) · 🇮🇳 [ta](../../../ta/docs/routing/AUTO-COMBO.md) · 🇮🇳 [te](../../../te/docs/routing/AUTO-COMBO.md) · 🇹🇭 [th](../../../th/docs/routing/AUTO-COMBO.md) · 🇹🇷 [tr](../../../tr/docs/routing/AUTO-COMBO.md) · 🇵🇰 [ur](../../../ur/docs/routing/AUTO-COMBO.md) · 🇺🇿 [uz](../../../uz/docs/routing/AUTO-COMBO.md) · 🇻🇳 [vi](../../../vi/docs/routing/AUTO-COMBO.md) · 🇳🇬 [yo](../../../yo/docs/routing/AUTO-COMBO.md) · 🇨🇳 [zh-CN](../../../zh-CN/docs/routing/AUTO-COMBO.md) · 🇹🇼 [zh-TW](../../../zh-TW/docs/routing/AUTO-COMBO.md) --- > **Для користувачів**: Шукаєте спосіб швидко розпочати роботу? Перегляньте [Посібник користувача Auto-Combo](../getting-started/AUTO-COMBO-GUIDE.md), де наведено прості пояснення та приклади. > Самокеровані ланцюжки моделей з адаптивним оцінюванням і автоматичною маршрутизацією без налаштувань ## Автоматична маршрутизація без конфігурації (префікс `auto/`) > **НОВЕ:** Створення комбінації не потрібне. Використовуйте префікс `auto/` безпосередньо в будь-якому клієнті. ### Швидкі приклади | ID моделі | Варіант | Поведінка | | -------------- | ----------- | ---------------------------------------------------------------------------- | | `auto` | стандартний | Усі підключені провайдери, стратегія LKGP, збалансовані ваги | | `auto/coding` | coding | Пріоритет якості, підходить для генерації коду | | `auto/fast` | fast | Зважений вибір із низькою затримкою | | `auto/cheap` | cheap | Маршрутизація з оптимізацією вартості (спочатку найнижча вартість) | | `auto/offline` | offline | Надає перевагу провайдерам із найбільшою доступною квотою | | `auto/smart` | smart | Пріоритет якості + вища частота дослідження (10%) для кращого пошуку моделей | | `auto/lkgp` | lkgp | Явна стратегія LKGP (те саме, що й стандартний `auto`) | | `auto/chaos` | chaos | Паралельне розгалуження, по одній моделі на провайдера (не ін’єкція збоїв) | ### Компонування категорії × рівня (`auto/:`) Суфікси в стилі OpenRouter відокремлюють **тип маршруту** (категорію) від **способу його оптимізації** (рівня), тож їх можна вільно комбінувати (#4235 Phase B, `open-sse/services/autoCombo/suffixComposition.ts`): - **Категорії** (фільтрують пул кандидатів за можливостями): `coding` · `reasoning` · `vision` · `chat` · `multimodal`. `vision`/`multimodal` залишають моделі з підтримкою зору; `reasoning` залишає моделі з можливостями міркування/мислення. - **Рівні** (визначають ваги оцінювання / фільтр пулу): `fast` (швидкий результат) · `cheap` (псевдонім `floor`, економія коштів) · `reliable` (стан автоматичного вимикача + стабільність затримки) · `free` / `pro` (фільтрують пул за рівнем моделі через `classifyTier` — безкоштовний рівень або преміум). | Приклад | Результат | | ---------------------- | --------------------------------------------------------------------------- | | `auto/coding:fast` | пул для програмування, ваги з низькою затримкою | | `auto/coding:cheap` | пул для програмування, оптимізація вартості (псевдонім `auto/coding:floor`) | | `auto/reasoning:pro` | лише моделі для міркування/мислення, преміум-рівень | | `auto/vision` | моделі з підтримкою зору (без рівня → збалансовані ваги) | | `auto/multimodal:free` | моделі з підтримкою мультимодальності, лише безкоштовний рівень | Будь-який коректний `auto/[:]` визначається на вимогу; ретельно відібраний піднабір представлено в `/v1/models` і на панелі керування (`AUTO_SUFFIX_VARIANTS` у `open-sse/services/autoCombo/builtinCatalog.ts`). Фільтрація працює за принципом **fail-open** — якщо жодна підключена модель не відповідає обмеженню, використовується весь пул, щоб маршрутизація ніколи не припиняла роботу. Основний механізм оцінювання (`combo.ts`) не змінено; фільтр категорії/рівня застосовується в `buildAutoCandidates`. > **Актуальна аналітика моделей:** придатність для автоматичної маршрутизації визначається на основі актуальних рейтингів **Arena ELO** і даних про рівні з **models.dev**, коли ввімкнено прапорець `ARENA_ELO_SYNC_ENABLED` (інакше використовується статична карта придатності). **Як використовувати:** ```bash # Будь-яка IDE або інструмент CLI, що підтримує формат OpenAI Базова URL-адреса: http://localhost:20128/v1 Ключ API: <ключ-кінцевої-точки> # У коді/конфігурації встановіть модель: model: "auto" # збалансований стандартний варіант model: "auto/coding" # найкраще для завдань програмування model: "auto/fast" # найшвидший доступний варіант model: "auto/cheap" # найдешевший варіант за токен ``` **Що відбувається:** 1. OmniRoute виявляє префікс `auto/` у `src/sse/handlers/chat.ts` 2. Запитує в базі даних усі **активні підключення провайдерів** 3. Відфільтровує підключення з дійсними обліковими даними (ключем API або токеном OAuth) 4. Визначає модель для кожного підключення (`connection.defaultModel` або першу модель провайдера) 5. Створює **віртуальну комбінацію** в пам’яті (не зберігається в БД) 6. Виконує маршрутизацію за допомогою профілю ваг вибраного варіанта та стратегії LKGP **Ключові властивості:** - ✅ **Завжди ввімкнено:** перемикач, створення комбінації та конфігурація не потрібні - ✅ **Динамічність:** автоматично враховує поточних підключених провайдерів - ✅ **Прив’язка сеансу:** LKGP забезпечує пріоритет останнього успішного провайдера - ✅ **Підтримка кількох облікових записів:** кожне підключення провайдера стає окремим кандидатом - ✅ **Без записів у БД:** віртуальна комбінація існує лише протягом запиту, без накладних витрат на постійне зберігання ### Керування кандидатами для кожного ключа (#7819, рівень 1+2) `GET /v1/auto-combo/{channel}/candidates` (`{channel}` = суфікс після `auto/` або буквальне значення `auto` для базового каналу) — це кінцева точка **лише для читання**, яка повертає поточний пул кандидатів каналу `auto/*` із даними про актуальну доступність, повторно використовуючи наявні механізми стійкого читання (і ніколи не звертаючись безпосередньо до `state` автоматичного вимикача): - автоматичний вимикач провайдера — `getCircuitBreaker(provider).getStatus()` / `.canExecute()` - період очікування підключення — `rateLimitedUntil` / `testStatus` у визначеному рядку `provider_connections` - блокування моделі — `isModelLocked(provider, connectionId, model)` Кожен кандидат також містить прапорець `excluded` для цього ключа API. Виключення зберігаються окремо для кожного ключа API (таблиця `auto_candidate_overrides`, міграція `128`) — OmniRoute є одноорендною системою без таблиці `users`, тому `apiKeyId` є найближчим реальним ідентифікатором окремого виклику — і застосовуються в критичній точці пулу кандидатів у `open-sse/services/autoCombo/virtualFactory.ts` за допомогою чистої, покритої модульними тестами функції `filterExcludedCandidates()` (`open-sse/services/autoCombo/candidateOverrides.ts`). Фільтр працює за принципом **fail-open**: відсутній apiKeyId/канал або помилка запиту до БД залишають пул невідфільтрованим, тому для оператора без налаштованих перевизначень маршрутизація побайтово ідентична тій, що була до появи цієї функції. **Відкладено до наступного завдання:** ваги для окремих кандидатів + явне впорядкування (рівень 3 — інтегрується в наявні шляхи стратегій із вагами/пріоритетами), а також закріплення певної стратегії `combo.ts` за кожним каналом `auto/*` (рівень 4). Див. план #7819 щодо відкритого питання про те, чи мають перевизначення залишатися окремими для кожного API-ключа, чи стати глобальними з огляду на однотенантну модель. **За лаштунками:** ```txt Запит: { model: "auto/coding" } ↓ src/sse/handlers/chat.ts виявляє префікс ↓ createVirtualAutoCombo('coding') → пул кандидатів з активних підключень ↓ handleComboChat (той самий рушій, що й для збережених комбінацій) ↓ Автоматичне оцінювання вибирає найкращого постачальника/модель для кожного запиту ``` **Файли реалізації:** | Файл | Призначення | | --------------------------------------------------------- | ----------------------------------------------------- | | `open-sse/services/autoCombo/autoPrefix.ts` | Парсер префікса (`parseAutoPrefix`) | | `open-sse/services/autoCombo/virtualFactory.ts` | Створює віртуальні об’єкти `AutoComboConfig` | | `open-sse/services/autoCombo/providerRegistryAccessor.ts` | Тестовий механізм для імітації реєстру постачальників | | `src/sse/handlers/chat.ts` | Інтеграція: раннє оброблення автоматичного префікса | | `src/shared/constants/providers.ts` | Системний запис `SYSTEM_PROVIDERS.auto` | ## Назви комбінацій, що збігаються з реальним ідентифікатором моделі Комбінація, `name` якої ідентичне звичайному ідентифікатору моделі (наприклад, комбінація з назвою `gpt-5.5`), — це **навмисний і підтримуваний шаблон**, а не помилка: це механізм резервного перемикання між постачальниками для окремих ідентифікаторів моделей, описаний у [#6940](https://github.com/diegosouzapw/OmniRoute/issues/6940). Оскільки визначення комбінації виконується до визначення звичайного ідентифікатора моделі (`getComboForModel()` у `src/sse/services/model.ts`), запит зі звичайним ідентифікатором `gpt-5.5` спрямовується через цілі комбінації (наприклад, `acme-responses/gpt-5.5`, `backup-responses/gpt-5.5`), а не безпосередньо до одного постачальника — це повторно використовує пріоритет комбінації перед перезаписуванням, створений для [#3227/#3233](https://github.com/diegosouzapw/OmniRoute/issues/3227), і перевіряється регресійними тестами `tests/unit/responses-combo-resolution-3227.test.ts` та `tests/unit/combo-name-codex-responses-rewrite.test.ts`. Створення або перейменування комбінації так, що її назва затінює реальний ідентифікатор моделі, **ніколи не відхиляється** — це порушило б описаний робочий процес. Натомість (#8530) `POST /api/combos` і `PUT /api/combos/[id]` додають до відповіді неблокувальне поле `warning`, коли (нова) назва конфліктує з реальним ідентифікатором моделі: ```json { "warning": { "code": "COMBO_NAME_SHADOWS_MODEL", "modelId": "gpt-5.5", "providerId": "openai" } } ``` Під час запуску `scanComboModelNameCollisionsAtBoot()` (`src/instrumentation-node.ts`) також записує однорядкове попередження `[STARTUP]`, перелічуючи всі наявні комбінації, що затінюють ідентифікатор моделі, щоб оператори, які зіткнулися з цим випадково (а не навмисно, відповідно до #6940), отримали відповідний сигнал. Допоміжна функція виявлення розташована у `src/lib/combos/modelNameCollision.ts`. ## Виклик власної комбінації з клієнта Збережені комбінації (Налаштування → Комбінації) використовуються лише тоді, коли клієнт надсилає **точну назву** комбінації в полі `model` — нечіткого чи часткового зіставлення назви комбінації немає, і префікс `auto/` не використовується. Порядок визначення (`getComboForModel()` у `src/sse/services/model.ts`): 1. точний збіг назви комбінації (`model: "my-combo"`), 2. префікс `combo/` (`model: "combo/my-combo"`), 3. glob-зіставлення моделі з комбінацією (`/api/model-combo-mappings`). ```bash curl -X POST http://localhost:20128/v1/chat/completions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"my-combo","messages":[{"role":"user","content":"Hello"}]}' ``` Дві поширені помилки: - **`auto` не використовує ваші комбінації.** `auto`/`auto/*` формує власний пул кандидатів, що не потребує налаштування, і звертається до збережених комбінацій лише тоді, коли одна з них буквально має назву `auto` (не рекомендовано). Щоб спрямувати запит через комбінацію, надішліть її точну назву, а не `auto`. - **`openrouter/auto` — це реальний платний продукт OpenRouter** («Найкращий доступний автоматично»), а не псевдонім OmniRoute. Це єдиний статичний запис моделі в реєстрі OpenRouter (`open-sse/config/providers/registry/openrouter/index.ts`), і він оплачується окремо. Щоб виключити його з пулів `auto`, скористайтеся Налаштування → Маршрутизація → Приховати платні моделі. Див. [#7992](https://github.com/diegosouzapw/OmniRoute/issues/7992) і [#7111](https://github.com/diegosouzapw/OmniRoute/issues/7111), де описано початкову плутанину, яку пояснює цей документ. ## Як це працює (збережені автокомбінації) Механізм автокомбінацій динамічно вибирає найкращого постачальника/модель для кожного запиту за допомогою **16-факторної функції оцінювання** (визначеної в `open-sse/services/autoCombo/scoring.ts` → `DEFAULT_WEIGHTS`). Сума ваг за замовчуванням дорівнює `1.0`; користувацькі ваги повторно нормалізуються функцією `normalizeScoringWeights()`. Два із шістнадцяти факторів — `cacheAffinity` і `resetWindowAffinity` — мають вагу за замовчуванням `0`; `reliability` має вагу `0` у `DEFAULT_WEIGHTS`, але `0.03` у загальних наборах і `0.04` у `reliability-first`, а `quality` має вагу `0.02` у наборах (`0.03` у `quality-first`): вони все одно обчислюються для кожного кандидата, а `cacheAffinity` керує дедуплікацією кешу промптів поза межами оцінки, тому фактори з нульовою вагою за замовчуванням просто не впливають на результат, тоді як у наборах — впливають. ![16-факторне оцінювання автокомбінацій](../diagrams/exported/auto-combo-scoring.svg) > Джерело: [diagrams/auto-combo-scoring.mmd](../diagrams/auto-combo-scoring.mmd) (повторно згенеруйте за допомогою `npm run docs:render-diagrams`). Назва файлу є історичною; вихідний код і згенерована діаграма показують усі 16 факторів, оголошених у `DEFAULT_WEIGHTS`. | Фактор | Вага за замовчуванням | Опис | | :-------------------- | :-------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `quota` | 0.1429 | Залишок квоти / резерв до обмеження частоти [0..1] | | `health` | 0.1605 | Оцінка стану від автоматичного вимикача (CLOSED=1.0, HALF_OPEN=0.5, OPEN=0.0) | | `costInv` | 0.1429 | Обернена **змішана** вартість (60% ціни вхідних + 40% ціни вихідних токенів, нормалізована) — дешевше = вища оцінка | | `latencyInv` | 0.1143 | Обернена затримка p95, нормалізована відносно пулу — швидше = вища оцінка | | `taskFit` | 0.0762 | Відповідність типу завдання (написання коду, перевірка, планування, аналіз, налагодження, документація) | | `stability` | 0.0476 | Стабільність на основі дисперсії стандартного відхилення затримки — кандидат із мінливим часом відповіді отримує нижчу оцінку | | `tierPriority` | 0.0476 | Пріоритет рівня облікового запису — Ultra=1.0, Pro=0.67, Standard=0.33, Free=0.0 | | `tierAffinity` | 0.0476 | Відповідність між рівнем кандидата та рівнем, рекомендованим маніфестом | | `specificityMatch` | 0.0476 | Відповідність між специфічністю запиту (підказкою маніфесту) та рівнем моделі | | `contextAffinity` | 0.0476 | Відповідність між потребою запиту в розмірі контекстного вікна та контекстним вікном моделі | | `sessionAvailability` | 0.0476 | Доступність OAuth-сесії кандидатського з’єднання для цієї сесії (`getOAuthSessionAvailability()`; з’єднання без OAuth отримують оцінку 1.0) | | `connectionDensity` | 0.0476 | Розподіляє навантаження між з’єднаннями того самого постачальника (запобігає концентрації) | | `cacheAffinity` | 0.00 | Відповідність на основі рандеву-хешування з’єднанню, яке найімовірніше вже містить префікс кешу промпту цього запиту (`open-sse/services/combo/promptCacheAffinity.ts`); вимкнено за замовчуванням (#8008) | | `resetWindowAffinity` | 0.00 | Перевага з’єднанням зі сприятливим вікном скидання квоти (вимкнено за замовчуванням) | | `quality` | 0.03 | Керований зворотним зв’язком сигнал якості вихідних даних із відстежувача якості подій маршрутизації; кандидати без спостережень отримують нейтральну оцінку 0.5 | | `reliability` | 0.00 | Спостережувана частка успішних запитів, `1 - failureRate`, на основі 24 годин історії використання з мінімальним порогом у десять зразків (інакше використовуються метрики реального часу); кандидати без спостережень отримують значення 1.0. Вимкнено за замовчуванням | `quality` (`open-sse/services/routing/quality.ts`): `cold restart → ∅`; `observations = 0 → score 0.5`. **Сума:** `0.1429 + 0.1605 + 0.1429 + 0.1143 + 0.0762 + (7 × 0.0476) + 0.00 + 0.00 + 0.03 + 0.00 = 1.0`, як оголошено в `DEFAULT_WEIGHTS`; налаштовані користувачем ваги перед оцінюванням повторно нормалізуються функцією `normalizeScoringWeights()` до розподілу. ## Пакети режимів 6 попередньо визначених профілів ваг у `open-sse/services/autoCombo/modePacks.ts`. Кожен пакет повністю замінює ваги за замовчуванням, щоб змістити вибір на користь однієї мети. Сума ваг у кожному пакеті вже дорівнює `1.0` (`0.9999` у разі відображення з чотирма десятковими знаками), тому `normalizeScoringWeights()` не має нічого суттєвого для коригування, коли пакет активний — наведені нижче значення, з урахуванням округлення, є тими, які застосовує оцінювач. | Фактор | ship-fast | cost-saver | quality-first | offline-friendly | reliability-first | chaos-mode | | :-------------------- | :--------- | :--------- | :------------ | :--------------- | :---------------- | :--------- | | `quota` | 0.1133 | 0.1133 | 0.0752 | **0.3324** | 0.1133 | 0.0376 | | `health` | 0.2667 | 0.1810 | 0.1714 | 0.2667 | **0.3524** | **0.4000** | | `costInv` | 0.0276 | **0.3324** | 0.0276 | 0.0752 | 0.0181 | 0.0140 | | `latencyInv` | **0.3048** | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0186 | | `taskFit` | 0.0952 | 0.0952 | **0.3524** | 0.0000 | 0.0952 | 0.1905 | | `stability` | 0.0000 | 0.0476 | 0.1429 | 0.0952 | 0.1905 | 0.1714 | | `tierPriority` | 0.0376 | 0.0376 | 0.0276 | 0.0376 | 0.0276 | 0.0040 | | `tierAffinity` | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | | `specificityMatch` | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | | `contextAffinity` | 0.0095 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0186 | | `sessionAvailability` | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0476 | | `resetWindowAffinity` | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | | `connectionDensity` | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0476 | 0.0476 | | `quality` | 0.02 | 0.02 | **0.03** | 0.02 | 0.02 | 0.02 | | `reliability` | 0.03 | 0.03 | 0.03 | 0.03 | **0.04** | 0.03 | Примітки: - **Пакети містять `quality` і `reliability`** (`quality 0.02`, `quality-first 0.03`; `reliability 0.03`, `reliability-first 0.04`) і повністю замінюють мапу ваг (`weights = pack`, а не злиття). `DEFAULT_WEIGHTS` містить `quality 0.03 / reliability 0`; вибір `balanced`/`default` зберігає ці значення за замовчуванням, а вибір пакета використовує наведені вище значення пакета. У холодному пулі (спостережень ще немає, тому `quality 0.5` і `reliability 1`) ці два фактори додають `+0.04` у звичайному пакеті (`0.03 + 0.01`), `+0.045` у `quality-first` і `+0.05` у `reliability-first`. - `tierAffinity`, `specificityMatch` і `resetWindowAffinity` явно дорівнюють `0` у кожному пакеті. - Короткий огляд акцентів кожного пакета: - **ship-fast** → latencyInv 0.3048 + health 0.2667 (справні з’єднання з низькою затримкою) - **cost-saver** → costInv 0.3324 (перемагають найдешевші токени) - **quality-first** → taskFit 0.3524 + stability 0.1429 + quality 0.03 — найвище значення серед усіх пакетів (найкраща модель для завдання, стабільна) - **offline-friendly** → quota 0.3324 + health 0.2667 (максимальний запас незалежно від швидкості/вартості) - **reliability-first** → health 0.3524 + stability 0.1905 + reliability 0.04 — найвище значення серед усіх пакетів (найменше несподіванок) - **chaos-mode** → health 0.4000 + taskFit 0.1905 (пакет ваг, який `auto/chaos` призначає учасникам своєї панелі; паралельне розгалуження не зчитує ці ваги, і це не профіль внесення збоїв, див. [CHAOS-MODE.md](../guides/CHAOS-MODE.md#autochaos-parallel-fan-out)) ### Керування на рівні запиту (заголовки) — #6023 / #6024 / #6025 / #3470 Комбінацією `auto` можна керувати **для кожного окремого запиту** за допомогою трьох заголовків, не змінюючи збережену конфігурацію комбінації. Вони застосовуються лише до стратегії `auto` і лише до запиту, який їх містить; збережені для комбінації `modePack`/`budgetCap`/`budgetFallback` використовуються, коли заголовок відсутній. | Заголовок | Приймає | Ефект | | :---------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `X-OmniRoute-Mode` | псевдонім попереднього налаштування (`fast`, `balanced`, `quality`, `cheap`, `reliable`, `offline`) або необроблену назву пакета (`ship-fast`, `cost-saver`, `quality-first`, `offline-friendly`, `reliability-first`) | Перевизначає вагові коефіцієнти оцінювання для цього запиту. `balanced`/`default` примусово застосовують вагові коефіцієнти за замовчуванням (без пакета). Невідомі значення ігноруються (конфігурація зберігається). | | `X-OmniRoute-Budget` | додатне число (максимальна сума в USD на запит) | Жорстка межа вартості: кандидати, оцінена вартість яких перевищує її, відфільтровуються перед вибором. Поведінка у випадку, коли **кожен** кандидат перевищує її, визначається заголовком `X-OmniRoute-Budget-Fallback` нижче. | | `X-OmniRoute-Budget-Fallback` | `cheapest` (за замовчуванням, псевдоніми: `cheapest-viable`, `soft`) або `strict` (псевдоніми: `block`, `hard`) | `cheapest`: повертається до глобально найдешевшого кандидата, навіть якщо він усе одно перевищує ліміт (успадкована поведінка). `strict`: відмовляється здійснювати вибір — запит одразу завершується помилкою `HTTP 402`, замість того щоб непомітно перевищити бюджет. Невідомі значення ігноруються. | | `X-OmniRoute-Effort` | `auto` (інші значення зарезервовано) | Адаптивний бюджет міркувань: якщо запит **не** містить поля міркувань у жодній формі (`reasoning_effort`, `reasoning`, `thinking`), шлюз перетворює `auto` на `low`/`medium`/`high` на основі детермінованих ознак структури запиту (довжини останнього повідомлення користувача, розміру контексту до останнього повідомлення користувача включно, попередніх результатів інструментів, глибини циклу інструментів). Ознаки обмежуються поточним ходом — усе після останнього повідомлення користувача ігнорується — тому кожен запит у циклі інструментів отримує однаковий рівень (фіксація без стану на рівні ходу, без стану сеансу та без підвищення рівня посеред циклу, яке порушило б префікси кешу промптів висхідного сервісу). Явно задане клієнтом поле міркувань завжди має пріоритет. Застосовується до запитів, для яких висхідна диспетчеризація визначає формат OpenAI Chat Completions (`targetFormat === FORMATS.OPENAI`) — `reasoning_effort` є полем формату OpenAI, тому заголовок не має ефекту для запитів, спрямованих до Claude або Gemini (див. `open-sse/handlers/chatCore/adaptiveEffortWiring.ts`). | ```bash # Примусово застосувати найшвидший профіль, обмежити цей запит сумою $0.05 і жорстко заблокувати його замість перевищення бюджету curl -sS http://localhost:20128/v1/chat/completions \ -H "Content-Type: application/json" \ -H "X-OmniRoute-Mode: fast" \ -H "X-OmniRoute-Budget: 0.05" \ -H "X-OmniRoute-Budget-Fallback: strict" \ -d '{"model":"auto","messages":[{"role":"user","content":"hi"}]}' ``` Визначення є чистою функцією (`open-sse/services/autoCombo/requestControls.ts`); визначені значення передаються до наявних вхідних параметрів рушія `config.modePack` / `config.budgetCap` / `config.budgetFallback`. Збережене значення `config.budgetFallback` комбінації ("strict" | "cheapest") задає постійну політику; заголовок перевизначає її для одного запиту. ## Усі стратегії маршрутизації Комбінований рушій OmniRoute підтримує **20 стратегій маршрутизації** (оголошених у `src/shared/constants/routingStrategies.ts` → `ROUTING_STRATEGY_VALUES`). Сам рушій Auto Combo доступний через стратегію `auto`; інші стратегії доступні для збережених комбінацій. | Стратегія | Опис | | :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `priority` | Упорядкований список із пріоритетом першої цілі та явно заданими пріоритетами | | `weighted` | Зважений випадковий вибір за вагою кожної цілі | | `round-robin` | Циклічний перебір цілей у заданому порядку (пакетами; див. нижче) | | `context-relay` | Передавання контексту між цілями (для тривалих розмов) | | `fill-first` | Вичерпання квоти кожної цілі перед переходом до наступної | | `p2c` | Випадкове балансування навантаження за методом вибору з двох варіантів | | `random` | Рівномірний випадковий вибір | | `least-used` | Вибір цілі з найнижчим поточним навантаженням | | `cost-optimized` | Мінімізація вартості запиту з урахуванням цін у каталозі | | `reset-aware` ⭐ | Пріоритизація за часом скидання квоти — коротші вікна скидання мають вищий пріоритет | | `reset-window` | Надання переваги цілям, вікно квоти яких буде скинуто найшвидше | | `headroom` | Вибір цілі з найбільшим залишковим запасом квоти | | `quota-weighted` | Пропуск облікових записів із вичерпаною квотою, а потім вибір серед решти пропорційно до залишку квоти, поділеного на поточне навантаження; наявні розмови залишаються закріпленими | | `strict-random` | Випадковий вибір без усунення повторів | | `auto` | Використання оцінювання Auto Combo (за 16 факторами) — **рекомендовано** | | `lkgp` | Останній відомий робочий шлях (закріплення за останнім успішним провайдером із подальшим переходом до резервних варіантів за правилами) | | `context-optimized` | Вибір цілі, яка найкраще відповідає поточному розміру контексту | | `cache-optimized` | Перевпорядкування цілей за спорідненістю з кешем промптів — першим випробовується підключення, яке найімовірніше вже містить кешований префікс цього запиту (`open-sse/services/combo/promptCacheAffinity.ts`, #8008) | | `fusion` 🧬 | Паралельне надсилання запиту групі моделей із подальшим синтезуванням однієї відповіді за допомогою моделі-судді (див. нижче) | | `pipeline` | Послідовний запуск цілей із передаванням результату кожного кроку на вхід наступного; повертається лише остаточна відповідь (#6396) | ⭐ = Нове у v3.8.0 · 🧬 = Нове у v3.8.36 ### Семантика `weighted` `weighted` — це **пропорційний випадковий вибір для кожного запиту** (`open-sse/services/combo/targetSorters.ts` → `selectWeightedTarget`), а не механізм вирівнювання: - Для кожного запиту вибирається **один** крок з імовірністю `weight / totalWeight`; решта кроків упорядковуються за спаданням ваги як ланцюжок резервних варіантів для цього запиту. - Крок, вага якого дорівнює `0` (або не вказана), **ніколи не вибирається**, доки будь-який інший крок має вагу > 0 — він може слугувати лише резервним варіантом після невдалого виконання вибраного кроку. Лише коли **всі** ваги дорівнюють 0, вибір стає рівномірним. - Кроки, усі цілі яких недоступні — автомат захисту провайдера у стані `OPEN`, період очікування підключення, блокування моделі — вилучаються до здійснення вибору (`open-sse/services/combo/targetResolution.ts`), тому єдиний працездатний крок може тимчасово вибиратися для кожного запиту. - `stickyWeightedLimit` (налаштування комбінації, стандартне значення `1` = вимкнено) закріплює вибраний крок на вказану кількість послідовних успішних виконань перед повторним вибором. Для суворої ротації використовуйте `round-robin`; однакові ваги в `weighted` забезпечують статистичне, а не суворе балансування. ### Режим агентного конвеєра Комбінація `pipeline` із принаймні двома моделями може ввімкнути маршрутизацію між планувальником і виконавцем за допомогою `config.agenticOrchestration.enabled`. Перша ціль відповідає за планування та остаточні відповіді; друга ціль створює нативні для клієнта виклики інструментів. OmniRoute виявляє продовження з результатами інструментів у протоколі запиту, запитує планувальник, чи потрібен ще один раунд роботи з інструментами, і динамічно призначає виконавця або планувальник останнім етапом, видимим клієнту. Додаткові моделі після другої цілі впорядковуються як резервні виконавці. Невдала HTTP-відповідь або виняток транспорту спричиняє перехід до наступного виконавця зі збереженням того самого рішення планувальника й нативних інструментів, але з власною підказкою для етапу цього виконавця та визначеним підключенням. Перша успішна відповідь повертається без змін, включно з потоковим передаванням SSE; помилки після початку успішного потоку тут не можна обробити повторною спробою. Якщо всі виконавці зазнають невдачі, повертається остання помилка. Переривання клієнтом зупиняє диспетчеризацію. ```json { "strategy": "pipeline", "models": [{ "model": "provider/planner" }, { "model": "provider/executor" }], "config": { "agenticOrchestration": { "enabled": true, "maxToolRounds": 8 } } } ``` Виконавець може створити кілька незалежних викликів в одній відповіді. Залежні виклики обробляються в наступних клієнтських ходах із результатами інструментів, а планувальник перевіряє кожен результат. Значення `maxToolRounds` за замовчуванням дорівнює `8`; допустимий діапазон — `1`–`32`. Після досягнення межі планувальник має надати найкращу доступну остаточну відповідь. Внутрішні рішення планувальника буферизуються, тоді як вибрана відповідь, видима клієнту, зберігає початкове налаштування потокового передавання. ### Пакетний режим `round-robin` із закріпленням і розширенням за обліковими записами Round-robin працює пакетно, а не за принципом «один запит на крок»: - `stickyRoundRobinLimit` (конфігурація комбінації, потім `comboStickyRoundRobinLimit`, потім `settings.stickyRoundRobinLimit`, значення за замовчуванням — **3**) утримує ту саму ціль протягом зазначеної кількості послідовних успішних запитів перед ротацією. Установіть для перевизначення на рівні комбінації значення `1`, щоб виконувати ротацію після кожного запиту. Редактор комбінацій показує фактичне значення та рівень, з якого його отримано. - `connectionAwareExpansion` (конфігурація комбінації, потім налаштування, значення за замовчуванням — **false**) розгортає кожен крок на рівні постачальника в окремі цілі для кожного облікового запису перед ротацією. Стратегії групи B (priority, weighted, round-robin, random, p2c, least-used, cost-optimized, lkgp, fill-first, strict-random, context-optimized, cache-optimized, context-relay, fusion, pipeline) зберігають подання на рівні постачальника, доки цей параметр не ввімкнено. Редактор комбінацій пропонує успадкування / увімкнення / вимкнення; успадкування використовує глобальне значення за замовчуванням (вимкнено). - Маршрутизація за локальністю кешу підказок (`promptCacheAffinityEnabled`, значення за замовчуванням — **true**) перевпорядковує закріплені підключення, щоб відповідні ключі кешу залишалися в одному обліковому записі. Вона має пріоритет над ротацією round-robin і weighted між закріпленими кроками для окремих облікових записів. Вимкніть її в розділі Settings → Combo defaults, якщо потрібна сувора ротація. Перевизначення на рівні окремої комбінації відсутнє. Для ротації між кількома обліковими записами однієї моделі віддавайте перевагу **одному кроку з динамічним обліковим записом** (порожній `connectionId`, увесь пул) із лімітом закріплення `1`, а не трьом закріпленим `connectionId`. Закріплені кроки разом зі спорідненістю зводяться до одного облікового запису, навіть коли лічильник RR збільшується. ## Стратегія Fusion `fusion` — це єдина стратегія, яка **не** вибирає одну цільову модель. Вона надсилає запит **кожній моделі панелі паралельно**, після чого налаштовувана **модель-суддя** синтезує єдину остаточну відповідь з усіх відповідей панелі. Перенесено з оригінального `decolua/9router` (дизайн Fusion від OpenRouter); реалізація міститься в `open-sse/services/fusion.ts`. Як це працює: 0. **Обхід для запитів з інструментами** — запит, що містить непорожній масив `tools`, а `tool_choice` явно не встановлено в `"none"`, повністю оминає панель: він спрямовується безпосередньо до однієї моделі (налаштованого судді або `panel[0]`), а `tools`/`tool_choice` передаються без змін. Учасники панелі не мають доступу до інструментів, а директива судді щодо синтезу не заохочує генерування викликів інструментів, тож агентні клієнти та клієнти з викликом інструментів отримують справжнє рішення про виклик інструмента замість синтезованого тексту (#6771). 1. **Паралельне розгалуження** (лише для запитів без інструментів) — запит одночасно надсилається кожній моделі панелі, примусово в непотоковому режимі та без інструментів (для синтезу судді потрібен повний текст). 2. **Збір із кворумом і пільговим періодом** — щойно надходить `minPanel` відповідей, запускається короткий таймер пільгового очікування моделей, що відстають, після чого Fusion продовжує роботу з усім, що вдалося зібрати. Це обмежує вплив найповільнішої моделі на загальний час виконання, з урахуванням жорсткого тайм-ауту. 3. **Синтез суддею** — відповіді панелі анонімізуються (`Source 1`, `Source 2`, … — щоб суддя оцінював зміст, а не бренд моделі) і передаються судді, який аналізує консенсус / суперечності / часткове охоплення / унікальні висновки / сліпі зони, а потім формує **одну** авторитетну відповідь. Виклик судді зберігає початковий клієнтський прапорець `stream` та інструменти, тому потокове передавання й подальше використання інструментів продовжують працювати. 4. **Плавна деградація** — 0 відповідей панелі → `503`; рівно 1 відповідь → вона повертається безпосередньо (синтезувати нічого); панель з однієї моделі відповідає безпосередньо. Учасником панелі також може бути крок `combo-ref` (`{kind: "combo-ref", comboName: "..."}`), що посилається на іншу комбінацію — він обробляється як **один голос панелі у вигляді чорної скриньки** (повна рекурсивна диспетчеризація до вказаної комбінації, а не розгалуження на власні цілі цієї комбінації), з тим самим захистом від надмірної глибини та циклів, який уже використовують усі інші стратегії, що підтримують `combo-ref` (#6764). ### Конфігурація Налаштовується в блоці `config` комбінації (без міграції схеми — повторно використовується наявна таблиця `combos`): | Поле | Тип | Типове значення | Призначення | | :--------------------------------------- | :------- | :------------------ | :---------------------------------------------------------------------------------------------------------------- | | `config.judgeModel` | `string` | перша модель панелі | Модель, яка синтезує остаточну відповідь | | `config.fusionTuning.minPanel` | `number` | `2` | Кількість успішних відповідей, потрібних для запуску пільгового таймера (обмежується діапазоном `[2, panelSize]`) | | `config.fusionTuning.stragglerGraceMs` | `number` | `8000` | Тривалість очікування моделей, що відстають, після досягнення кворуму | | `config.fusionTuning.panelHardTimeoutMs` | `number` | `90000` | Абсолютне обмеження, щоб одна зависла модель не могла затримати запит | Типові значення містяться у `FUSION_DEFAULTS` (`open-sse/services/fusion.ts`). ### Приклад ```bash curl -X POST http://localhost:20128/api/combos \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "fusion-panel", "strategy": "fusion", "targets": [ { "model": "cc/claude-opus-4-7" }, { "model": "cx/gpt-5.5" }, { "model": "glm/glm-5.1" } ], "config": { "judgeModel": "cc/claude-opus-4-7", "fusionTuning": { "minPanel": 2, "stragglerGraceMs": 8000, "panelHardTimeoutMs": 90000 } } }' ``` Після цього викликайте її як будь-яку іншу комбінацію: `{"model":"fusion-panel","messages":[...]}`. ## Віртуальна фабрика Auto-Combo Рушій Auto Combo не потребує попередньо визначених комбінацій. Натомість `open-sse/services/autoCombo/virtualFactory.ts` створює кандидатів на льоту: 1. Отримує `getProviderConnections({ isActive: true })` (усі ввімкнені підключення) 2. Відфільтровує підключення з дійсними обліковими даними (API-ключем або непростроченим токеном OAuth через `hasUsableOAuthToken()`) 3. Зіставляє їх із `getProviderRegistry()`, щоб отримати доступність моделей і ціни 4. Для кожного кортежу `(provider, model, connection)` створює `VirtualAutoComboCandidate` 5. Вибирає `connection.defaultModel` (або першу модель у реєстрі) як цільову модель для маршрутизації 6. Оцінює кожного кандидата за допомогою 16-факторної функції `scorePool()` і набору ваг варіанта 7. Повертає отриманий у пам’яті `AutoComboConfig` для `handleComboChat()` — без збереження в БД Це означає, що **додавання нового провайдера з увімкненим `auto/*` автоматично розширює пул кандидатів** — ручне редагування комбінації не потрібне. Віртуальна комбінація перебудовується для кожного запиту, тому щойно додані або відновлені підключення враховуються негайно. ## Самовідновлення - **Тимчасове виключення**: оцінка < 0.2 → виключення на 5 хв (прогресивна затримка, максимум 30 хв) - **Урахування стану автоматичного вимикача**: OPEN → автоматичне виключення; HALF_OPEN → пробні запити - **Режим інциденту**: >50% OPEN → вимкнути дослідження, максимізувати стабільність - **Відновлення після періоду очікування**: після виключення перший запит є «пробним» зі скороченим тайм-аутом ## Бандитне дослідження 5% запитів (значення можна налаштувати) спрямовуються випадковим провайдерам для дослідження. У режимі інциденту цю функцію вимкнено. ## API **Спеціальної кінцевої точки `POST /api/combos/auto` немає** — Auto-Combo використовується двома способами: 1. **Без конфігурації (рекомендовано):** Надішліть будь-який запит на завершення чату з `model: "auto"` або `model: "auto/"`. Віртуальна фабрика створює комбінацію для кожного запиту — збереження та виклики API не потрібні. 2. **Збережена комбінація зі `strategy: "auto"`:** Створіть звичайну комбінацію через `POST /api/combos` і задайте `strategy: "auto"`, а також `config.auto.weights` / `config.auto.candidatePool`. Використовується той самий механізм оцінювання; комбінація зберігається в `combos` і може повторно використовуватися за ID. Для виявлення `GET /api/combos/auto` повертає список усіх варіантів із визначеним пулом кандидатів, а також `context_length` / `max_output_tokens` — МАКСИМАЛЬНІ значення серед вікон пулу кандидатів. Клієнти (наприклад, плагін opencode) мають оголошувати ці значення замість `0`: нульовий контекст повністю вимикає автоматичне ущільнення opencode, через що сесії збільшуються, доки очищення історії шлюзом не знищить контекст. МАКСИМАЛЬНЕ значення можна безпечно оголошувати, оскільки попередній фільтр контексту auto-combo спрямовує завеликі запити до кандидатів із великим контекстним вікном. ```bash # Використання без конфігурації (без створення комбінації) curl -X POST http://localhost:20128/v1/chat/completions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"auto/coding","messages":[{"role":"user","content":"Hello"}]}' # Збережена автоматична комбінація через звичайну кінцеву точку комбінацій curl -X POST http://localhost:20128/api/combos \ -H "Content-Type: application/json" \ -d '{"id":"my-auto","name":"Auto Coder","strategy":"auto","config":{"auto":{"candidatePool":["anthropic","google","openai"],"weights":{"quota":0.15,"health":0.3,"costInv":0.05,"latencyInv":0.35,"taskFit":0.1,"stability":0,"tierPriority":0.05}}}}' ``` ### Стратегії автоматичної маршрутизації Збережені комбінації зі `strategy: "auto"` можуть задавати для `config.routerStrategy` (або застарілого `config.auto.routerStrategy`) одне з таких значень: - `rules` — стандартне зважене оцінювання - `score` — вибирає найвищу налаштовану зважену оцінку. У разі абсолютно однакових оцінок зберігається налаштований порядок кандидатів; наявний `explorationRate` вибирає з повного ранжованого пулу. - `cost` / `eco` — найдешевший справний постачальник - `latency` / `fast` — найнижча затримка p95 зі штрафом за ненадійність - `sla-aware` / `sla` — надає перевагу кандидатам, які відповідають SLO щодо затримки p95, частоти помилок і, за потреби, вартості - `lkgp` — спочатку останній відомий справний постачальник - `nadir` — запитує API прийняття рішень [Nadir](https://getnadir.com), яка модель у пулі потрібна для запиту; потребує явного ввімкнення, а в разі збою переходить до `rules` ### Докладний опис стратегій маршрутизації Механізм auto-combo надає 7 підключуваних реалізацій **RouterStrategy**, які можна перемикати через `config.routerStrategy` (або застарілий `config.auto.routerStrategy`). Кожна стратегія вибирає одного постачальника з пулу кандидатів на основі `RoutingContext` (тип завдання, ознаки використання інструментів/зображень, оцінка кількості токенів, необов’язкова політика SLA, необов’язковий останній відомий справний постачальник). #### 1. `rules` (за замовчуванням) — 16-факторне зважене оцінювання Огортає наявний механізм оцінювання. Відфільтровує кандидатів автоматичного вимикача зі станом `OPEN`, а потім запускає `scorePool()` із поточним типом завдання та `getTaskFitness()`, вибираючи постачальника з найвищою оцінкою. ```ts class RulesStrategyImpl implements RouterStrategy { readonly name = "rules"; readonly description = "16-factor weighted scoring (see DEFAULT_WEIGHTS)"; select(pool, context) { const eligible = pool.filter((c) => c.circuitBreakerState !== "OPEN"); const ranked = scorePool( eligible.length > 0 ? eligible : pool, context.taskType, undefined, getTaskFitness ); return { provider: ranked[0].provider /* ... */ }; } } ``` **Коли використовувати**: За замовчуванням. Використовуйте, коли потрібен збалансований компроміс між усіма сигналами. **Псевдонім**: `rules` (без псевдоніма) --- #### 2. `cost` / `eco` — найдешевший справний постачальник Сортує пул кандидатів за `costPer1MTokens` (за зростанням) і вибирає найдешевшого. Спочатку відфільтровує кандидатів зі станом `OPEN`. ```ts class CostStrategyImpl implements RouterStrategy { readonly name = "cost"; readonly description = "Always selects cheapest available provider"; select(pool, context) { const healthy = pool.filter((c) => c.circuitBreakerState !== "OPEN"); const sorted = [...healthy].sort((a, b) => a.costPer1MTokens - b.costPer1MTokens); return { provider: sorted[0].provider /* ... */ }; } } ``` **Коли використовувати**: Навантаження, чутливі до вартості, пакетна обробка або фонові завдання. **Псевдоніми**: `cost`, `eco` --- #### 3. `latency` / `fast` — найнижча затримка p95 зі штрафом за ненадійність Сортує за `p95LatencyMs + (errorRate * 1000)`. Штраф за частоту помилок гарантує, що ненадійні постачальники матимуть нижчий рейтинг, навіть якщо їхня номінальна затримка мала. ```ts class LatencyStrategyImpl implements RouterStrategy { readonly name = "latency"; readonly description = "Prioritizes lowest p95 latency with reliability weighting"; select(pool, context) { const healthy = pool.filter((c) => c.circuitBreakerState !== "OPEN"); const sorted = [...healthy].sort( (a, b) => a.p95LatencyMs + a.errorRate * 1000 - (b.p95LatencyMs + b.errorRate * 1000) ); return { provider: sorted[0].provider /* ... */ }; } } ``` **Коли використовувати**: Навантаження, чутливі до затримки, як-от чат у реальному часі, автодоповнення або інтерактивні помічники з програмування. **Псевдоніми**: `latency`, `fast` --- #### 4. `sla-aware` / `sla` — відповідність SLO щодо затримки/помилок/вартості Оцінює кожного кандидата за тим, наскільки добре він відповідає налаштованій політиці SLO: | Фактор | Вага | Формула | | ------------------- | ---- | ------------------------------------------------------ | | Оцінка затримки | 35% | `threshold / max(value, ε)` | | Оцінка помилок | 35% | `threshold / max(value, ε)` | | Оцінка стану | 15% | `1.0` (CLOSED) / `0.5` (HALF_OPEN) / `0.0` (OPEN) | | Оцінка вартості | 10% | `threshold / max(value, ε)` або обернено нормалізована | | Оцінка стабільності | 5% | обернено нормалізоване стандартне відхилення затримки | Коли `hardConstraints: true`, кандидати сортуються насамперед за **оцінкою порушення** (наскільки вони перевищують будь-який SLO), а потім за сукупною оцінкою. В іншому разі використовується лише сукупна оцінка. ```ts class SLAStrategyImpl implements RouterStrategy { readonly name = "sla-aware"; readonly description = "Вибирає провайдера, який із найбільшою ймовірністю задовольнить SLO щодо затримки, частоти помилок і вартості"; select(pool, context) { // ... оцінює кожного кандидата відповідно до політики: { targetP95Ms, maxErrorRate, maxCostPer1MTokens, hardConstraints } } } ``` **Поля SLA** (задаються в конфігурації комбінації): ```json { "strategy": "auto", "config": { "routerStrategy": "sla-aware", "slaTargetP95Ms": 1500, "slaMaxErrorRate": 0.05, "slaMaxCostPer1MTokens": 5, "slaHardConstraints": true } } ``` **Коли використовувати**: Виробничі навантаження зі строгими бюджетами затримки, частоти помилок або вартості. **Псевдоніми**: `sla-aware`, `sla` --- #### 5. `lkgp` — спочатку останній відомий справний провайдер Спочатку пробує **останнього відомого справного провайдера** (якщо його задано), а потім переходить до стратегії `rules`. Корисно для прив’язки сесії — той самий провайдер обробляє наступні запити в розмові. ```ts class LKGPStrategyImpl implements RouterStrategy { readonly name = "lkgp"; readonly description = "Спочатку пробує останнього відомого справного провайдера, а потім переходить до rules"; select(pool, context) { if (context.lkgpEnabled === false) { return getStrategy("rules").select(pool, context); } if (context.lastKnownGoodProvider) { const candidates = pool.filter( (c) => c.provider === context.lastKnownGoodProvider && c.circuitBreakerState !== "OPEN" ); if (candidates.length > 0) { return { provider: candidates[0].provider /* ... */ }; } } // Перехід до стратегії rules return getStrategy("rules").select(pool, context); } } ``` **Коли використовувати**: Багатоетапні розмови, у яких потрібно, щоб той самий провайдер обробляв наступні запити (наприклад, для кешування, безперервності контексту або узгодженості цін). **Псевдонім**: `lkgp` (без псевдоніма) --- #### 6. `nadir` — вибір моделі з урахуванням запиту через Nadir Кожна з наведених вище стратегій ранжує кандидатів за власною телеметрією; жодна з них не аналізує запит. `nadir` надсилає останню репліку користувача разом з ідентифікаторами моделей із пулу до API прийняття рішень [Nadir](https://getnadir.com) (`POST /v1/bucket`) і спрямовує запит до моделі, яку Nadir вибирає з цього переліку (`simple` → найдешевша придатна модель, `complex` → передова модель). З’єднання, яке обслуговує цю модель, усе одно вибирається за допомогою `rules`, тож квота, стан і вартість надалі визначають, який обліковий запис використовувати. ```json { "strategy": "auto", "config": { "routerStrategy": "nadir", "nadir": { "apiKey": "ndr_...", "baseUrl": "https://api.getnadir.com", "timeoutMs": 2000 } } } ``` `OMNIROUTE_NADIR_API_KEY` і `OMNIROUTE_NADIR_BASE_URL` є резервними змінними середовища для цих двох рядків. `baseUrl` потрібен лише для самостійно розгорнутого Nadir (кінцевий `/v1` допускається). Виклики без ключа потрапляють на анонімний рівень Nadir, де діють обмеження частоти запитів для кожної IP-адреси. Що надсилається назовні: текст останнього повідомлення користувача (перші 16 тисяч символів), ідентифікатори моделей-кандидатів і тег каналу `source: "omniroute"`. Системна підказка, історія, інструменти й заголовки не надсилаються. У разі збою система продовжує роботу: тайм-аут (типово 2000 мс), відповідь не з діапазону 2xx, недоступний вузол, некоректна відповідь або вибір поза межами пулу призводять до використання рішення `rules`, а до причини додається префікс `NadirStrategy: fallback (…)`. Після невдалого виклику стратегія не звертається до мережі протягом 30 с, тому збій спричиняє один тайм-аут на 30 с, а не один на кожен запит. Події маршрутизації повідомляють `strategy: "nadir"` лише тоді, коли вибір справді зробив Nadir. **Коли використовувати**: трафік із запитами різної складності в пулі, що охоплює кілька рівнів моделей (малу, середню та передову модель), коли потрібно скоротити витрати на постійне використання передової моделі. **Псевдонім**: `nadir` (без псевдоніма) --- ### Користувацькі стратегії маршрутизатора Ви можете зареєструвати власну реалізацію `RouterStrategy` через публічний API: ```ts import { registerStrategy, type RouterStrategy, } from "@omniroute/open-sse/services/autoCombo/routerStrategy"; class MyCustomStrategy implements RouterStrategy { readonly name = "my-custom"; readonly description = "Моя користувацька стратегія маршрутизації"; select(pool, context) { // Тут розміщується ваша логіка маршрутизації return { provider: pool[0].provider, model: pool[0].model, strategy: this.name, reason: "MyCustomStrategy: ...", candidatesConsidered: pool.length, finalScore: 1.0, }; } } registerStrategy("my-custom", new MyCustomStrategy()); ``` Після цього використовуйте її: ```json { "strategy": "auto", "config": { "routerStrategy": "my-custom" } } ``` --- ### Посібник із вибору стратегії маршрутизатора | Варіант використання | Стратегія | Причина | | ------------------------- | ----------- | ----------------------------------------- | | Збалансоване навантаження | `rules` | Типова — враховує всі фактори | | Мінімізація вартості | `cost` | Завжди вибирає найдешевший варіант | | Мінімізація затримки | `latency` | Вибирає найшвидшого надійного провайдера | | Строгі SLO | `sla-aware` | Фільтрує за порогами p95/помилок/вартості | | Багатоетапний чат | `lkgp` | Прив’язка сесії | | Запити різної складності | `nadir` | Вибирає рівень моделі для кожного запиту | Поля SLA: ```json { "strategy": "auto", "config": { "routerStrategy": "sla-aware", "slaTargetP95Ms": 1500, "slaMaxErrorRate": 0.05, "slaMaxCostPer1MTokens": 5, "slaHardConstraints": true } } ``` ## Відповідність завданням Понад 30 моделей оцінено за 6 типами завдань (`coding`, `review`, `planning`, `analysis`, `debugging`, `documentation`). Підтримуються шаблони з символами підстановки (наприклад, `*-coder` → висока оцінка для програмування). ## Огляд автоматичних варіантів Разом із базовим `auto` (за замовчуванням) і 6 значеннями `AutoVariant`, оголошеними в `autoPrefix.ts`, існує **7 доступних для виклику ідентифікаторів моделей**: `auto`, `auto/coding`, `auto/fast`, `auto/cheap`, `auto/offline`, `auto/smart`, `auto/lkgp` (`AutoVariant` містить 6 значень; сьомий варіант — «без варіанта», тобто базовий `auto`, — обробляється функцією `parseAutoPrefix()` як `variant: undefined`.) ## Як рівні вписуються в Auto-Combo 16-факторна функція оцінювання (`open-sse/services/autoCombo/scoring.ts`) розглядає належність до рівня як два сигнали: `tierPriority` (0.0476) і `tierAffinity` (0.0476). Повний набір `DEFAULT_WEIGHTS` наведено вище в канонічній [таблиці факторів оцінювання](#how-it-works-persisted-auto-combos), а перевизначення для окремих пакетів (ship-fast/cost-saver/quality-first/offline-friendly) — у таблиці «Профілі ваги для кожного пакета». Сам по собі рівень **не** змушує систему спочатку вибирати рівень 1: якщо затримка рівня 1 надто велика або співвідношення вартості та якості неоптимальне, перемагає рівень 2. Щоб примусово задати порядок рівнів, використовуйте стратегію комбінації `priority` і впорядкуйте постачальників за рівнями. Щоб надати значну перевагу рівню 1 (підписка), збільште вагу `tierPriority`: ```json { "strategy": "auto", "config": { "auto": { "weights": { "tierPriority": 0.3, "costInv": 0.05 } } } } ``` Визначення рівнів і класифікацію постачальників див. у файлі [`docs/guides/TIERS.md`](../guides/TIERS.md). ## Тестування та покриття ### Детермінована матриця рішень маршрутизації (`npm run test:combo:matrix`) `tests/integration/combo-matrix/*.test.ts` перевіряє **рішення** щодо маршрутизації для всіх 19 публічних стратегій наскрізно через реальний конвеєр комбінації з імітованим висхідним сервісом. Покриття включає: - Усі 19 стратегій `ROUTING_STRATEGY_VALUES` (ordered, weighted, cost, context, fusion, …). - Наскрізну перевірку `quota-share` (внутрішня стратегія): справедливість DRR + зниження пріоритету в разі насичення через реальну точку інтеграції `selectQuotaShareTarget` (`registerQuotaFetcher` / `setLKGP` / `__setHeadroomSaturationFetcherForTests`). - Перевірку універсальної передачі `context-relay` для кожної кількості цілей. Цей набір тестів запускається в CI (завдання `test:integration`) з `--test-concurrency=1` і `--test-force-exit`, тому він є детермінованим і не потребує справжніх облікових даних. ### Умовно активовані інтерактивні smoke-тести (НЕ в CI — реальні провайдери) | Команда | Що вона робить | | :------------------------------------- | :------------------------------------------------------------------------------------------------- | | `npm run test:combo:live` | Реальна маршрутизація усередині процесу з `RUN_COMBO_LIVE=1`; створює знімок активної БД OmniRoute | | `npm run test:combo:live:vps` | HTTP-виклики до активного сервера OmniRoute (установіть `COMBO_LIVE_BASE_URL`) | | `npm run test:combo:live:vps:failover` | Те саме, але з навмисно створеними сценаріями перемикання після відмови | Ці smoke-тести перевіряють реальний шлях передавання даних (комбінація → провайдер → завершення). Їх навмисно виключено з CI, оскільки вони потребують справжніх облікових даних і доступу до VPS. --- ## Файли | Файл | Призначення | | :-------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `open-sse/services/autoCombo/scoring.ts` | Функція оцінювання за 16 факторами, `DEFAULT_WEIGHTS`, нормалізація пулу | | `open-sse/services/autoCombo/taskFitness.ts` | Таблиця відповідності придатності моделі до завдання | | `open-sse/services/autoCombo/engine.ts` | Логіка вибору, бандитний алгоритм, обмеження бюджету | | `open-sse/services/autoCombo/selfHealing.ts` | Виключення, проби, режим інциденту | | `open-sse/services/autoCombo/modePacks.ts` | 6 профілів вагових коефіцієнтів (швидкий випуск, економія коштів, пріоритет якості, офлайн-режим, пріоритет надійності, хаос-режим) | | `open-sse/services/autoCombo/autoPrefix.ts` | Парсер префікса `auto/` + 6 варіантів | | `open-sse/services/autoCombo/virtualFactory.ts` | Створює в пам’яті `AutoComboConfig` з активних підключень | | `open-sse/services/autoCombo/providerRegistryAccessor.ts` | Тестовий механізм для імітації реєстру провайдерів | | `src/shared/constants/routingStrategies.ts` | `ROUTING_STRATEGY_VALUES` (20 стратегій) | | `src/sse/handlers/chat.ts` | Інтеграція: раннє завершення обробки для префікса `auto/` |