Роутинг между моделями по задаче и цене

Не каждый запрос нуждается в самой дорогой модели. Разбираем, как настроить роутинг между Claude, GPT и GLM по задаче и бюджету.

4 мин · · Обновлено · архитектура · оптимизация

Частая ошибка в интеграциях с несколькими провайдерами моделей — жёстко прописать одну модель на все случаи, обычно самую мощную и дорогую. Это работает, но переплата не всегда оправдана: классификация текста, извлечение сущностей из JSON или переформулировка одного предложения не требуют того же уровня рассуждений, что архитектурное решение или сложный рефакторинг. Роутинг по задаче решает это без потери качества там, где оно нужно.

Зачем вообще несколько моделей

Через один ключ формата fetch_... доступны модели разных семейств: Claude (claude-opus-5-5 и claude-sonnet-5 — по $0.30/$0.30 за 1M токенов), GPT-5.6 (gpt-5.6-sol — $0.30/$0.30, gpt-5.6-luna — $0.10/$0.10) и GLM (glm-5.3 — $0.10/$0.10, glm-5.3-flash — $0.04/$0.04). У Claude и GPT контекст 1 000 000 токенов, у GLM — 128K и без vision; в остальном разница между ними — в цене за токен и характере ответов. Актуальный список — GET https://api.zukko.pro/v1/models.

Это значит, что с точки зрения кода переключение между ними — это просто смена значения model в теле запроса, протокол и формат ответа не меняются в рамках одного API (Anthropic Messages или OpenAI Chat Completions).

Простая стратегия: дешёвая модель по умолчанию, дорогая — по требованию

Базовый паттерн — маршрутизатор, который смотрит на тип задачи и подставляет модель:

bash
import requests

ROUTES = {
    "classify": "glm-5.3-flash",
    "extract": "glm-5.3",
    "rewrite": "gpt-5.6-sol",
    "reasoning": "claude-opus-5-5",
    "code_review": "claude-sonnet-5",
}

def call_model(task_type, messages, max_tokens=1024):
    model = ROUTES.get(task_type, "claude-sonnet-5")
    resp = requests.post(
        "https://api.zukko.pro/v1/chat/completions",
        headers={"Authorization": "Bearer fetch_ваш_ключ"},
        json={"model": model, "messages": messages, "max_tokens": max_tokens},
    )
    resp.raise_for_status()
    return resp.json()

Классификация тональности или разметка тегов идёт на glm-5.3-flash — самую дешёвую модель в каталоге, а запрос на архитектурное решение — на claude-opus-5-5. Цена за миллион токенов у них — $0.04 против $0.30, и при большом потоке классификационных запросов это ощутимо на счёте за месяц.

Роутинг по длине контекста

Не всегда нужна разница в «уме» модели — иногда важнее просто наличие большого контекстного окна. У Claude и GPT контекст 1 000 000 токенов, а у GLM — 128K и без vision, поэтому критерий длины входа в роутере нужен: длинные документы и запросы с изображениями отправляйте на Claude или GPT, а короткие текстовые задачи можно отдавать дешёвым GLM.

Fallback при отказе модели

Модель может быть временно недоступна (503 provider_unavailable) или выключена (503 model_disabled). Роутер должен уметь откатываться на резервную модель того же уровня:

bash
FALLBACK = {
    "claude-opus-5-5": "claude-sonnet-5",
    "gpt-5.6-sol": "claude-sonnet-5",
}

def call_with_fallback(model, messages, max_tokens=1024):
    for attempt_model in [model, FALLBACK.get(model)]:
        if not attempt_model:
            break
        resp = requests.post(
            "https://api.zukko.pro/v1/chat/completions",
            headers={"Authorization": "Bearer fetch_ваш_ключ"},
            json={"model": attempt_model, "messages": messages, "max_tokens": max_tokens},
        )
        if resp.status_code == 503:
            continue
        resp.raise_for_status()
        return resp.json()
    raise RuntimeError("Все модели в цепочке недоступны")

Если в ответе код model_disabled, модель выключена, а не временно недоступна — это стоит логировать отдельно и обновлять ROUTES по актуальному GET /v1/models, а не хардкодить старый id навсегда.

Роутинг по цене через X-Zukko-Cost-Micros

Каждый обычный (нестриминговый) ответ содержит заголовок X-Zukko-Cost-Micros — фактическую стоимость запроса в микродолларах (1 000 000 = $1); у потоковых ответов (stream: true) его нет. Это полезно не для выбора модели заранее, а для последующей калибровки роутера: если накопленная статистика по extract показывает, что glm-5.3-flash даёт приемлемое качество дешевле glm-5.3, порог можно сдвинуть без изменения бизнес-логики, только конфиг ROUTES.

bash
cost_micros = int(resp.headers.get("X-Zukko-Cost-Micros", 0))
balance_micros = int(resp.headers.get("X-Fetch-Balance-Cents", 0))  # несмотря на название — микродоллары: 1_000_000 = $1

Собранные по задачам метрики стоимости за неделю-две обычно быстро показывают, где переплата, а где, наоборот, дешёвая модель ошибается чаще, чем оправдывает экономию.

Экономия через кэш вместо роутинга

Отдельный случай — не разные задачи, а один и тот же запрос повторно. Если выставлен заголовок X-Zukko-Cache: true (temperature: 0 тоже включает кэш, но только у моделей, которые принимают этот параметр — модели Claude могут отклонить temperature, отличную от 1, ошибкой 400), запросы одного пользователя с полностью совпадающим телом кэшируются (для чата — на срок до 7 суток), и повторный вызов возвращается мгновенно и бесплатно — в ответе будет X-Zukko-Cache: hit. Для роутера это значит, что перед выбором дорогой модели для повторяющегося запроса (например, классификации одного и того же текста) может быть выгоднее сначала включить кэш заголовком X-Zukko-Cache: true — экономия придёт не от смены модели, а от кэша.

Idempotency-Key при повторных попытках

Если роутер делает повторную попытку после сетевой ошибки (не 503, а обрыв соединения), важно не задвоить списание. Заголовок Idempotency-Key с одинаковым значением в течение 24 часов гарантирует, что повторный запрос с тем же телом вернёт тот же ответ без повторного списания. Ключ задаётся на каждый логический запрос — уникальный для него и тот же при всех его повторах; не генерируйте его заново при ретрае и не кладите в общие заголовки клиента:

bash
curl https://api.zukko.pro/v1/chat/completions \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Idempotency-Key: retry-task-4821" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"..."}]}'

Это особенно важно для роутера с автоматическими ретраями — без идемпотентного ключа сбойный, но фактически выполнившийся запрос может списаться дважды.

Итог

Роутинг по задаче и цене — это не сложная инфраструктура, а таблица соответствий плюс fallback-цепочка и мониторинг через X-Zukko-Cost-Micros. Каталог моделей и их актуальные цены можно получить без ключа через GET /v1/models/info и держать ROUTES синхронным с реальными id, не полагаясь на память о том, какие модели ещё существуют.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.