Перейти к содержимому
PD
AI-автоматизация 8 мин чтения

Роутер моделей вместо большего бюджета на LLM

Большинство AI-продуктов платят за frontier-модель там, где хватило бы регулярки. Разбираю роутер, который я ставлю перед каждым LLM-вызовом: три тира, дешёвый классификатор, кэш, эскалация вместо ретраев и учёт стоимости по фичам.

PD

Pavel Duglas

AI Automation & MVP Architect

Почти в каждом AI-продукте, который я разбираю, одна и та же болезнь: в коде захардкожено одно имя модели, и оно используется на всё - от «определи, спам это или нет» до «сделай трёхстраничную юридическую выжимку». Счёт растёт линейно с пользователями, маржа сжимается, а основатель идёт не в архитектуру, а в переписку с вендором за скидку. Лечится это скучно и надёжно: перед LLM-вызовами ставится роутер.

Я делал это для support-автоматизации в SaaS, для парсера документов и для Telegram-бота, который квалифицирует лиды. Во всех трёх случаях счёт упал на 60–80%, а качество не просело или даже выросло - потому что роутинг заставляет наконец сформулировать, что значит «достаточно хорошо» для каждой задачи.

Почему счёт на самом деле такой большой

Дело почти никогда не в объёме токенов. Дело в трёх вещах:

  1. Одна модель на всё. Вы выбрали самую умную на этапе прототипа, когда важна была правильность, а не копейки, и с тех пор не пересматривали.
  2. Один и тот же контекст на всё. В вызов, которому нужно 200 токенов, чтобы ответить «да/нет», уезжает system prompt на 6k токенов плюс вся история диалога.
  3. Слепые ретраи. Что-то упало - повторяем тем же дорогим вызовом. Иногда трижды. Иногда в цикле, за которым никто не смотрит.

Роутер бьёт по всем трём сразу, потому что решение «какая модель» всегда тянет за собой решение «какой контекст».

Шаг 1: инвентаризация вызовов, а не моделей

До того как писать код роутера, выпишите все места в продукте, где вызывается LLM. Для каждого - четыре поля:

  • Тип задачи - классификация, извлечение, переписывание, генерация, ризонинг, выбор инструмента.
  • Цена ошибки - что будет, если ответ неверный? Неправильный тег - дёшево. Неправильная сумма в счёте - дорого.
  • Бюджет по latency - ждёт ли этого человек в реальном времени?
  • Объём - вызовов в сутки.

Я делаю это в гугл-таблице примерно за час. Картина всегда одинаковая: 70–85% вызовов - это высокочастотные задачи с низкой ценой ошибки, и они же съедают 70–85% бюджета без всякой на то причины.

Эта таблица и есть ваша политика роутинга. Дальше - только реализация.

Шаг 2: три тира, а не двенадцать

Я использую ровно три уровня. Больше - и в системе уже никто не может держать логику в голове.

Tier 0 - без модели вообще

Самый дешёвый LLM-вызов - тот, которого не было. Регулярка, сравнение строк, справочная таблица, маленькая локальная embedding-модель, детерминированный парсер. В проекте с парсингом документов 40% «AI-извлечений» заменились регуляркой плюс нормализатором валют - потому что документы приходили из трёх известных шаблонов. Стоимость стала нулевой, latency - 2 мс, а точность выросла.

Всегда спрашивайте себя: это правда языковая задача - или я просто держал в руке LLM и всё стало похоже на текст?

Tier 1 - маленькая / open-weight рабочая лошадка

Классификация, тегирование, маршрутизация, короткое извлечение полей, определение интента, да/нет-гейты, саммари одного абзаца, переформатирование. Небольшие хостовые или self-hosted open-weight модели закрывают это без заметной разницы в качестве - но только на ограниченных задачах. Ключевое слово «ограниченных»: дайте жёсткую схему, enum допустимых значений и 3–5 few-shot примеров. Маленькая модель с хорошим промптом на таких задачах уверенно обгоняет большую с ленивым промптом.

Tier 2 - frontier-модель

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

Шаг 3: пишем роутер

Роутер - это функция, а не фреймворк. Примерно такой формы:

def route(task: str, payload: dict) -> ModelChoice:
    policy = POLICIES[task]            # из вашей таблицы

    if policy.deterministic_handler and policy.deterministic_handler.can_handle(payload):
        return ModelChoice(tier=0)

    tokens = estimate_tokens(payload)
    complexity = policy.complexity_signal(payload)   # дешёвая эвристика

    if complexity == "low" and tokens < policy.small_model_limit:
        return ModelChoice(tier=1, model=policy.small_model)

    return ModelChoice(tier=2, model=policy.big_model)

Обратите внимание, чего здесь нет: LLM, которая решает, какую LLM позвать. Это ловушка, в которую регулярно попадают - «роутер-агент», который тратит frontier-вызов на решение, делать ли frontier-вызов. Используйте модель как классификатор только тогда, когда эвристики реально не справляются, и тогда - Tier 1, жёсткий лимит токенов и enum на выходе.

Бесплатные и хорошие сигналы сложности:

  • длина и структура входа (парсится ли как известный шаблон);
  • количество сущностей/вопросов в запросе;
  • нужны ли инструменты (tool use);
  • тариф пользователя (платным можно отдавать Tier 2 в спорных случаях);
  • номер попытки (см. ниже).

Шаг 4: эскалация вместо ретраев

Слепые ретраи - это способ утроить счёт во время инцидента. Замените их эскалацией:

  1. Идёт вызов Tier 1.
  2. Ответ проходит валидатор - проверка JSON-схемы, проверка enum, бизнес-правило (сумма равна сумме позиций?) или поле confidence, которое модель обязана вернуть.
  3. Если валидация не прошла - один раз эскалируем в Tier 2, добавив в промпт причину провала.
  4. Если и Tier 2 не прошёл - громко падаем в очередь на человека. Никаких циклов.

Это самый сильный по отдаче паттерн во всей статье. Он означает, что дешёвому тиру достаточно быть правым в большинстве случаев, потому что ошибки перехватываются и апгрейдятся, а не уезжают клиенту. На практике «маленькая модель с точностью 92% + валидатор + одна эскалация» выигрывает у «дорогой модели с 97%» и по деньгам, и по итоговой точности - потому что 3% ошибок дорогой модели уходят наружу непроверенными.

Жёстко ограничивайте суммарный расход на запрос. Я привязываю бюджет по токенам/деньгам к каждой входящей задаче, и роутер просто отказывается эскалировать за его пределы. Один сорвавшийся agent-loop стоит дороже, чем неделя инженерного времени на такую защиту.

Шаг 5: кэш всерьёз

Три слоя, в порядке пользы:

  • Exact-match кэш. Хэш от (task, model, prompt, params) → ответ. Тривиально корректно и неожиданно эффективно. У support-ботов и генераторов описаний товаров hit rate бывает огромным.
  • Prefix / prompt caching. Большинство провайдеров берут меньше за повторяющийся префикс. Перестройте промпт так, чтобы стабильная часть (инструкции, схема, примеры) шла первой, а переменная - в конце. Рефакторинг на 30 минут с реальной экономией.
  • Семантический кэш. Эмбеддим запрос, ищем близкий дубликат выше порога похожести, переиспользуем ответ. Мощно и опасно: только там, где слегка неточный ответ допустим, и никогда - где в ответе есть персональные данные конкретного пользователя. Порог ставьте высокий, каждый хит логируйте, чтобы потом можно было провести аудит.

Шаг 6: eval-гейт, иначе вы гадаете

Нельзя ответственно понизить модель без тестового набора. Он не обязан быть красивым. Для каждой задачи собираем 50–200 реальных входов с ожидаемыми выходами. Для классификации - точное совпадение. Для извлечения - совпадение по полям. Для генерации - рубрика, которую оценивает ваша Tier 2 модель, плюс 20 примеров, проверенных руками, чтобы проверить самого оценщика.

Запускаем это как CI-джобу. Правила, которые я ввожу:

  • изменение политики роутинга - это изменение кода, и в PR должен быть diff по eval;
  • у каждой задачи есть минимальный порог точности, ниже - билд красный;
  • отчёт по eval печатает стоимость прогона, чтобы точность и деньги были в одной таблице.

Это превращает «мне кажется, дешёвая модель хуже» в число. В половине случаев число говорит, что дешёвая модель нормальна, и спор на этом заканчивается.

Шаг 7: считать деньги по фичам, а не по месяцам

Логируйте каждый вызов: имя задачи, тир, модель, input-токены, output-токены, посчитанная стоимость, флаг cache hit, результат валидации, флаг эскалации, id пользователя/тенанта. Одна широкая таблица, один дашборд.

Что вы должны узнавать за пять секунд:

  • стоимость фичи в день;
  • стоимость на активного пользователя и на платящего пользователя;
  • rate эскалаций по задачам (растёт - значит поменялся промпт или входные данные);
  • топ-10 самых дорогих отдельных запросов за вчера.

Последний пункт вылавливает патологию: пользователя, который вставил PDF на 200 страниц; цикл, отработавший 47 раз; тенанта, чьё потребление в одиночку убивает вашу юнит-экономику. В том support-SaaS три аккаунта из 400 давали 31% всех AI-расходов. Это не проблема модели - это проблема тарифной сетки, и увидеть её можно только при логировании по тенантам.

Порядок миграции, при котором никто не паникует

Не надо переписывать всё сразу. Последовательность:

  1. Добавьте логирование и атрибуцию стоимости. Больше ничего не меняйте. Подождите неделю.
  2. Соберите eval-набор для двух самых дорогих задач.
  3. Переведите эти две задачи на Tier 1 с валидатором и одной эскалацией.
  4. Включите exact-match кэш и перестройте промпты под prefix caching.
  5. Замените очевидных кандидатов Tier 0 на детерминированный код.
  6. Только после этого думайте про self-hosting.

Self-hosting open-weight моделей стоит последним намеренно. У него лучший заголовок по экономии и худшие скрытые издержки: GPU, ops, свои евалы, дежурства. Идти туда стоит, когда объём стабилен и предсказуем, - а не потому что вас впечатлил чей-то бенчмарк.

Что вы на самом деле покупаете

Помимо счёта - независимость от вендора. Когда есть роутер, eval-харнесс и политики по задачам, замена модели превращается в правку конфига и прогон CI, а не в двухнедельную панику. Новые модели выходят каждые несколько недель. Выигрывают команды, которые могут протестировать и внедрить за один вечер; команды с захардкоженным именем модели в сорока файлах просто смотрят со стороны.

Соберите роутер до того, как он вам понадобится. Это день работы, и он делает дешевле каждое следующее решение по AI в вашем продукте.

Вопросы и ответы

С чего начать, если продукт уже в проде и трогать его страшно?

Начните с наблюдаемости, а не с оптимизации. Оберните все LLM-вызовы одной функцией, которая логирует имя задачи, модель, токены, стоимость и результат, и оставьте это работать неделю. После этого у вас будет отчёт, из которого видно две-три задачи, съедающие большую часть бюджета. Роутинг вводите только для них - так изменение остаётся маленьким и обратимым, а эффект уже заметный.

Не сломается ли качество, если перевести часть задач на маленькую модель?

Сломается, если делать это на глаз. Поэтому в схеме два предохранителя: eval-набор из 50–200 реальных примеров с порогом точности в CI и валидатор с одной эскалацией в Tier 2 на рантайме. Валидатор проверяет схему, enum и бизнес-правила; если ответ не проходит, запрос автоматически идёт в дорогую модель. Итоговая точность обычно оказывается выше, чем при работе только на frontier-модели без проверок.

Стоит ли использовать LLM в качестве самого роутера?

Как правило, нет. Дешёвых сигналов хватает почти всегда: длина и структура входа, число сущностей, нужны ли инструменты, тариф пользователя, номер попытки. Если эвристики действительно не различают случаи, ставьте классификатором Tier 1 модель с жёстким лимитом токенов и enum на выходе - но никогда не тратьте frontier-вызов на решение о том, делать ли frontier-вызов.

Похожие статьи