*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. Задачи без правила маршрутизируются только по сложности. **Закрепление сложности за задачей.** Правило задачи принимает и полную форму с фиксированным уровнем — вердикт роутера для этой задачи заменяется до всех остальных шагов: ```toml [routing.tasks.legal] models = ["strong-model"] tier = "high" # юридические тексты — всегда high, что бы ни решил роутер ``` Ответы с закреплённым уровнем сообщают `route_source: "router+task-rule@high"`. На вкладке Роутинг это селектор «Сложность» у каждой задачи (Авто / low / medium / high). **Исключение — высокая сложность эскалирует.** Когда роутер оценил запрос как `high` (и цепочка `high` настроена), цепочка уровня идёт первой, а правило задачи становится резервом. Иначе правило вида `code → дешёвая-модель` приковало бы к дешёвой модели даже самые сложные запросы, и система уровней не имела бы права голоса. Для low/medium правило задачи ведёт, как и раньше. Список меток приходит с роутера (`GET /v1/taxonomies`) — вкладка Роутинг строит редактор по живой таксономии аккаунта и показывает долю каждой задачи в трафике. Когда цепочку возглавило правило задачи, ответ сообщает `route_source: "router+task-rule"`.