Files
cortiq-gateway/docs/ROUTING.ru.md
T
infosave2007 b4d36334bf
Docker / publish (push) Canceled after 0s
Release / crates-io (push) Canceled after 0s
Release / release (macos-14, aarch64-apple-darwin) (push) Canceled after 0s
Release / release (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Canceled after 0s
Release / release (windows-latest, x86_64-pc-windows-msvc) (push) Canceled after 0s
chore: release 0.2.39
2026-08-07 22:25:52 +03:00

188 lines
9.8 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.
Задачи без правила маршрутизируются только по сложности.
**Исключение — высокая сложность эскалирует.** Когда роутер оценил запрос как
`high` (и цепочка `high` настроена), цепочка уровня идёт первой, а правило
задачи становится резервом. Иначе правило вида `code → дешёвая-модель`
приковало бы к дешёвой модели даже самые сложные запросы, и система уровней
не имела бы права голоса. Для low/medium правило задачи ведёт, как и раньше. Список меток приходит
с роутера (`GET /v1/taxonomies`) — вкладка Роутинг строит редактор по живой
таксономии аккаунта и показывает долю каждой задачи в трафике. Когда цепочку
возглавило правило задачи, ответ сообщает `route_source: "router+task-rule"`.