Узнайте, как роутер с дешёвой моделью и шлюзом проверки сократил расходы на 71%. Практический гайд с кодом и подводными камнями.
30 июля OpenAI урезала цену на GPT-5.6 Luna до $0.20 за миллион входных токенов и $1.20 за миллион выходных — это на 80% дешевле прежних $1 и $6. Azure повторила это 1 августа. Большинство команд просто продолжили отправлять все запросы на сильную модель, потому что «просто использовать дешёвую» звучит как решение, которое обернётся тикетом в поддержке через три недели. Но когда вы видите счёт рядом с распределением трафика, становится очевидно: подавляющее большинство запросов — «озаглавь тред», «суммаризируй дифф», «извлеки поля из формы», «назови файл». Мы платили цены frontier-модели за генерацию заголовков из трёх слов.
В этой статье я покажу, как мы перестроили роутинг: около сорока строк кода на Go, которые переместили 81% запросов с дорогой модели и сократили счёт на 71%. А также разберу четыре вещи, которые сломались по пути — они и есть самое интересное.
Очевидное решение, которое приходит в голову первым — классификатор перед роутером: посмотреть на входящий запрос, решить, лёгкий он или сложный, и отправить на соответствующую модель. Мы так и сделали, и это оказалось хуже, чем кажется, по трём причинам.
Правило: не классифицируйте промпт. Запустите дешёвую модель и оцените результат.
Работающая схема до смешного проста:
Жизнеспособность этой схемы обеспечивает арифметика, которая нас удивила. При нашем среднем запросе — примерно 3000 входных и 700 выходных токенов — вызов Luna стоит около $0.0014, а вызов сильной модели — около $0.0145. Разница в десять раз.
Запрос, который эскалируется, обходится в 0.0014 + 0.0145 = $0.0159 вместо $0.0145. Это примерно на 10% дороже. Посчитаем точку безубыточности для частоты эскалации:
дешёвая + (p × сильная) < сильная
p < (сильная − дешёвая) / сильная
p < (0.0145 − 0.0014) / 0.0145
p < 0.90Вам пришлось бы эскалировать девять раз из десяти, чтобы дешёвый подход начал стоить дороже. Мы эскалируем в 15% случаев. Стоимость — не ограничение. Если вы спорите о двойной оплате, вы спорите не о том ресурсе. Ограничение — задержка. К этому вернёмся.
type Result struct {
Text string
Model string
Tokens Usage
}
// Gate возвращает причину, почему ответу нельзя доверять, или "" — если можно.
type Gate func(req Request, out string, fin FinishReason) string
func (r *Router) Do(ctx context.Context, req Request) (Result, error) {
// Принудительно сильная модель для определённых типов запросов
if req.ForceStrong || r.alwaysStrong[req.Kind] {
return r.call(ctx, r.strong, req)
}
// Пробуем дешёвую модель
cheap, err := r.call(ctx, r.cheap, req)
if err != nil {
// Ошибка доступности, не качества. Продолжаем, не падаем.
return r.call(ctx, r.strong, req)
}
// Прогоняем через все шлюзы
for _, gate := range r.gates {
reason := gate(req, cheap.Text, cheap.Finish)
if reason == "" {
continue
}
r.metrics.Escalate(req.Kind, reason)
// Эскалация: вызываем сильную модель с исходным запросом
strong, err := r.call(ctx, r.strong, req)
if err != nil {
// У нас уже есть ответ — отдаём дешёвый, деградируем, но не падаем
return cheap, nil
}
return strong, nil
}
r.metrics.Accept(req.Kind)
return cheap, nil
}Две строки здесь критически важны, и обе неочевидны. Первая — return r.call(ctx, r.strong, req) при ошибке дешёвого вызова: 429 или таймаут — это проблема доступности, и она не должна превращаться в ошибку для пользователя, когда у вас есть второй провайдер. Вторая — return cheap, nil при ошибке сильного вызова: у вас уже есть ответ, пусть и худший, но он лучше, чем бесконечный спиннер.
Комментарий про эскалацию — тот, который все понимают неправильно, и это четвёртая поломка ниже.
Шлюзы — это и есть продукт. Роутер — просто сантехника.
var defaultGates = []Gate{
SchemaInvalid, // должен быть JSON по схеме, но не является
ToolArgsMissing, // вызвал инструмент, но не передал обязательный аргумент
Truncated, // finish_reason != stop
EmptyOrHedged, // короче 24 символов или совпадает с набором отговорок
}Они упорядочены по стоимости проверки, и каждый из них — структурный. Ни один не заставляет модель оценивать другую модель.
SchemaInvalid делает больше всего работы. Всё, что имеет заданную форму вывода — извлечение полей, классификация, структурированные сводки — проверяется по схеме, которая у вас уже есть. Если не парсится или не соответствует — эскалация. Этот шлюз ловит около 60% наших эскалаций.
ToolArgsMissing — та же идея для вызова функций. Дешёвая модель выбирает правильный инструмент гораздо надёжнее, чем заполняет аргументы, а отсутствие обязательного аргумента — бесплатный точный сигнал.
Truncated — сравнение одного поля, и его постоянно пропускают. finish_reason со значением length означает, что предложение обрывается на полуслове.
EmptyOrHedged — самый слабый, и я хочу подчеркнуть, насколько слабый, потому что его все хотят сделать первым. Мы начали с интуитивной версии: просили дешёвую модель сообщать, когда она не уверена, и эскалировали на этом. Срабатывало в 0.4% ответов. Наша измеренная ошибка на том же трафике была около 15%. Самооценка модели — не сигнал, а украшение.
Что реально работает в этой роли — маленький скучный список: пусто, короче 24 символов, или точное совпадение с набором отговорок, который вы собрали, прочитав двести реальных отказов («недостаточно информации», «как ИИ», «уточните вопрос»). Не уверенность, а отказ.
Правило: проверяйте структуру, которую можно проверить, а не мнение модели о себе.
Это было немедленно, и это настоящая цена схемы.
Все-сильные Сначала-дешёвая + шлюз
p50 2.9s 1.4s
p95 7.8s 9.6s
p99 11.2s 16.4sLuna быстрая, поэтому принятые 85% стали намного быстрее. Эскалированные 15% платят за оба вызова последовательно и попадают в хвост. Если у вас есть SLO по задержке, этот хвост — где схема выживает или умирает. Помогли две вещи: запускать шлюзы на потоковой голове ответа, где это возможно, и установить жёсткий бюджет эскалации по времени — если дешёвый вызов уже сжёг 4 секунды, верните его и залогируйте промах, а не запускайте второй вызов, который не можете себе позволить.
Вся архитектура предполагает, что вы смотрите на вывод перед решением. Стриминг предполагает, что вы уже приняли решение. Умного исправления нет, только выбор:
Большая часть денег всё равно была в первом бакете: структурированная фоновая работа — это большой объём, и никто не смотрит на мигающий курсор.
Вывод беглый, хорошо отформатирован, использует ваши заголовки, попадает в тон, правильной длины. Просто неверный. Поэтому шлюзы вида «выглядит ли это хорошим ответом», включая LLM-как-судья на горячем пути, у нас работали плохо. Беглость — именно та ось, где разрыв в цене сократился больше всего. Суждение, многошаговые рассуждения и знание того, чего модель не знает, — там разрыв не сократился вовсе. Структурные шлюзы работают, потому что у них нет мнения. Валидный JSON — это валидный JSON.
Наша первая версия передавала неудачную попытку как контекст — «вот черновик, улучши его». Казалось, это очевидно эффективнее. Это сильно якорит: сильная модель наследует рамку дешёвой, сохраняет её структуру и исправляет формулировки, а не рассуждения. На эскалациях, которые мы проверяли вручную, путь «улучшить черновик» был хуже чистого запуска примерно в трети случаев — и хуже именно так, как важно: повторял ошибку, вызвавшую эскалацию, полируя прозу вокруг неё.
Эскалация — это не повторная попытка. Это свежая попытка лучшего исполнителя. Отправляйте исходный запрос.
На 1000 запросов при нашем распределении:
Все-сильные Сначала-дешёвая + шлюз
Запросов на дешёвой 0 950
Эскалировано — 143
Запросов к сильной 1000 193
Стоимость $14.50 $4.16Минус 71%. 81% запросов полностью обслуживаются моделью, которая стоит в десять раз дешевле, а 5% принудительно идут на сильную модель и вообще не попадают в роутер. Принудительный список короткий, и это политическое решение, а не измерение: всё, что пользователь отправляет клиенту, всё, что пишет в продакшен, и всё, что связано с генерацией кода в билдере. Это никогда не касается дешёвой модели, что бы ни сказал шлюз.
Result.Model с первого дня, если есть шанс, что вы в этой категории — ретрофит мучителен.Вам не нужна вся система, чтобы получить большую часть выгоды. Вот план:
Result.Model в логах и kill switch в конфиге.Главный аргумент для дешёвых моделей в 2026 году не в том, что они стали достаточно хороши, чтобы заменить frontier. А в том, что они стали настолько дёшевы, что проверка их качества теперь бесплатна.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →