Files
cortiq-gateway/docs/ROUTING.ru.md
T
infosave2007 2ede0ee452 feat(routing): per-task rules on top of complexity tiers; hosted router is the default
The router has always returned two signals — task label and complexity tier —
but only the tier picked models. [routing.tasks] adds `label → ordered models`:
a matching rule's models lead the candidate chain and the tier chain follows as
fallback, so pinning translations to a local model never weakens failover.
Requests whose chain was headed by a task rule report
route_source "router+task-rule".

The Routing page grows a per-task editor built from the account's LIVE
taxonomy (GET /v1/taxonomies via the new /admin/api/routing/labels proxy;
built-in label set when the router is unreachable), with localized task names,
each task's share of 7-day traffic, "by complexity (no rule)" as the explicit
empty state, and custom labels for other taxonomies. Deleting a model scrubs
it from task rules like it always did from tiers.

Also fixes the default config pointing at a placeholder localhost:8080 router
with a nonexistent taxonomy — a fresh install now targets the hosted allaigate
router (IP endpoint, verify_tls off, data-assistant, 12 s oracle timeout) and
degrades gracefully until a key is set. Verified end-to-end against the live
router: translation → task-rule model, math → tier path.

16 new strings in all 7 languages.
2026-08-07 17:39:38 +03:00

182 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
*Read this in [English](ROUTING.md).*
# Маршрутизация и стоимость
Как Cortiq Gateway превращает решение роутера (`task_label` + `complexity.tier`) в
выбор конкретной модели из вашего пула — и как сделать это **cost-aware**:
дешёвое локально, дорогое в облаке.
---
## 1. Базовая модель: полоса сложности → пул моделей
cortiq-router возвращает полосу сложности (`low` / `medium` / `high`, настраивается
в роутере). Таблица `[routing]` сопоставляет полосе **упорядоченный список** моделей —
это одновременно приоритет и цепочка fallback.
```toml
[routing]
low = ["local-qwen"] # простое → только локально (бесплатно)
medium = ["gpt-4o-mini", "local-qwen"] # средне → дешёвый хост, откат на локальное
high = ["claude-opus", "gpt-4o-mini"] # сложно → дорогая модель, откат на средн.
default = "gpt-4o-mini" # если роутер недоступен
```
Алгоритм выбора:
```
1. tier := router.complexity.tier (или default-полоса, если роутер недоступен)
2. candidates := routing[tier] (упорядоченный список model_id)
3. для каждого model_id по порядку:
если модель healthy И поддерживает нужные возможности (tools/vision):
выбрать её → выход
4. если все недоступны: следующая полоса вниз/вверх по политике, либо ошибка 503
```
---
## 2. Универсальный пул: локальные дешёвые + хостовые дорогие
Главный сценарий для разработчика: смешать **бесплатную локальную** модель и
**платный облачный** API, и пусть шлюз сам решает, что куда.
```toml
# Локальная модель на vLLM / llama.cpp / Ollama (OpenAI-совместимый endpoint)
[[models]]
id = "local-qwen"
provider = "openai"
base_url = "http://localhost:8000/v1"
model = "qwen2.5-7b-instruct"
cost_tier = "cheap"
price_in = 0.0 # локально → 0
price_out = 0.0
caps = ["tools"] # что умеет (для проверки на этапе выбора)
# Дешёвый облачный
[[models]]
id = "gpt-4o-mini"
provider = "openai"
base_url = "https://api.openai.com/v1"
model = "gpt-4o-mini"
api_key_env = "OPENAI_API_KEY"
cost_tier = "mid"
price_in = 0.15 # $/1M входных токенов
price_out = 0.60
# Дорогой облачный для сложного
[[models]]
id = "claude-opus"
provider = "anthropic"
base_url = "https://api.anthropic.com"
model = "claude-opus-4"
api_key_env = "ANTHROPIC_API_KEY"
cost_tier = "expensive"
price_in = 15.0
price_out = 75.0
caps = ["tools", "vision"]
```
Поскольку `local-qwen` и `gpt-4o-mini` оба `provider = "openai"`, шлюзу не важно,
где модель физически — **локальные серверы уже говорят на OpenAI API**. Это и делает
прокси универсальным.
---
## 3. Cost-aware режим (опционально)
Вместо жёсткой таблицы можно включить выбор по **бюджету/цене** (v0.2):
```toml
[routing.policy]
mode = "cost_aware" # fixed_table | cost_aware
# при cost_aware шлюз для каждой полосы берёт самую дешёвую модель,
# удовлетворяющую минимальному «классу качества» полосы:
min_class = { low = "cheap", medium = "mid", high = "expensive" }
# верхняя граница цены за запрос (страховка от дорогих вызовов)
max_cost_usd_per_request = 0.50
```
Логика: среди моделей не ниже `min_class[tier]` и не дороже лимита выбирается самая
дешёвая доступная. Это даёт «минимально достаточную» модель под сложность задачи.
---
## 4. Pinning и passthrough
- **Passthrough.** `model = "local-qwen"` (реальный id) — без обращения к роутеру,
прямой вызов. Полезно, когда клиент сам знает, что хочет.
- **Pinning профиля.** `model = "cortiq-auto:cost-saver"` — роутинг с конкретным
policy-профилем роутера (реже эскалирует → дешевле).
- **Pinning версии роутера** (v0.4). Чтобы поведение не «плыло» из-за самообучения
роутера, можно зафиксировать `model_version` — агент получает воспроизводимый роутинг.
---
## 5. Извлечение текста для маршрутизации (мультитёрн)
Агентский запрос — это не одна строка, а system + история + tools. Что слать роутеру
на классификацию, настраивается:
```toml
[route]
text_strategy = "last_user" # last_user | concat_all | last_user_plus_system
max_chars = 4000 # усечение длинного контекста (стоимость/DoS)
cache_ttl = "60s" # кэш решения по хэшу текста
profile = "balanced" # дефолтный policy-профиль
```
| Стратегия | Что классифицируем | Когда |
|---|---|---|
| `last_user` | последнее сообщение пользователя | по умолчанию; дёшево и точно для большинства агентов |
| `last_user_plus_system` | system + последнее user | когда роль задаётся системным промптом |
| `concat_all` | весь диалог (усечённый) | мультитёрн со смыслом, размазанным по истории |
---
## 6. Failover и circuit breaker
- **Failover.** При 5xx/таймауте/`overloaded` от провайдера — следующая модель списка.
- **Circuit breaker.** После `breaker.threshold` ошибок подряд модель помечается
«открытой» на `breaker.cooldown`, запросы её минуют. Полупроброс — один пробный
запрос после кулдауна.
- **Router down.** Берётся `routing.default`, запрос не падает (мягкая деградация).
```toml
[breaker]
threshold = 5
cooldown = "30s"
```
---
## 7. Петля обратной связи (опционально, v0.4)
Если включено, шлюз после успешного выполнения может отправить в роутер
`/v1/feedback` сигнал (например, по результату валидации ответа агентом) — роутер
дообучит маску задачи. Так маршрутизация со временем точнее ложится на ваш пул.
```toml
[feedback]
enabled = false
# источник истины задаёт интегратор (например, заголовок X-Cortiq-Correct-Label)
```
## Распределение по задачам
Роутер классифицирует каждый запрос дважды: **тип задачи** (что это за работа)
и **уровень сложности** (насколько она трудна). Уровни выбирают, *сколько
модели* нужно; правила по задачам — *какая* модель лучше в этом деле:
```toml
[routing.tasks]
translation = ["local-qwen"] # переводы дёшевы — оставляем локально
code = ["claude-sonnet", "gpt"] # код — сильнейшему кодеру
```
Модели подходящего правила идут **первыми** в цепочке кандидатов; цепочка по
сложности следует за ними как резерв — правило по задаче не ослабляет failover.
Задачи без правила маршрутизируются только по сложности. Список меток приходит
с роутера (`GET /v1/taxonomies`) — вкладка Роутинг строит редактор по живой
таксономии аккаунта и показывает долю каждой задачи в трафике. Когда цепочку
возглавило правило задачи, ответ сообщает `route_source: "router+task-rule"`.