/ Дневник курса / Урок 22

Как удешевить вызовы модели и чем за это платят

Слои экономии на вызовах модели — кэш промпта, кэш по смыслу, каскад моделей, лимиты бюджета. В учебном расчёте бот дешевеет со $120 до $36 в сутки.

  • finops
  • caching
  • cost-optimization
  • cascade-routing
  • llm-ops

Банковский чат-бот принимает 8 000 обращений в сутки и обходится в $3 600 в месяц. Расчёт здесь и дальше учебный, а профиль узнаваемый: на треть трафика приходятся «спасибо», «понял» и «здравствуйте».

Дальше разберём этот счёт по слоям: какие запросы можно вообще не отправлять, за что платит отправленный и по каким разрезам видно, какая часть продукта съедает деньги. Каждый приём экономии при этом сам чего-то стоит — повышенной ставки за первую запись в кэш, отдельного вычисления на каждом входящем запросе, второго вызова модели там, где дешёвая не справилась. Считать выигрыш без этой второй половины — верный способ отчитаться о снижении расходов, которого не было.

Дневник курса, урок 22. Пригодится разобранное раньше: экономика одного запроса (урок 21) — стоит ли этот запрос своих денег; каскад и бюджеты на рассуждение (урок 2) — тот же приём с другой стороны; метрики и контроль расходов (урок 7) — чем меряют то, что здесь режут. Пост читается отдельно: все термины вводятся заново.

Содержание
  1. Куда уходят деньги и в каком порядке включают слои
  2. Вызовы, которых не должно было быть
  3. Вход растёт сам: история, найденные документы и потолок ответа
  4. Кэш префикса: что кэшируется и чем это ломают
  5. Прайс как рычаг: множители перемножаются, а политики вендоров расходятся
  6. Кэш по смыслу: экономия ценой чужого ответа
  7. Каскад: младшая модель по умолчанию
  8. Почему экономию каскада считают в свою пользу
  9. Разрезать счёт: по каким осям и почему разметку ставят заранее
  10. Аномалии расходов и лимиты, которые срабатывают до следующего вызова
  11. Сборка: что осталось от $120 в сутки
  12. Итог
  13. FAQ
  14. Источники

1. Куда уходят деньги и в каком порядке включают слои

Бот отвечает на вопросы клиентов банка, и каждое обращение уходит прямиком во флагманскую модель. На каждом ходе в запрос уезжает вся переписка целиком, а средний диалог у него — пятнадцать ходов. Кэша нет, лимитов нет, вызовы не размечены метаданными, поэтому разложить расходы по фичам нечем. Выходит $120 в сутки, $3 600 в месяц, то есть полтора цента за обращение в самом простом чат-сервисе.

С чего тут начинать? С разбора трафика по типам. На вежливость («спасибо», «понял», «здравствуйте») у бота приходится 30% обращений, повторяющиеся вопросы по тарифам и условиям дают ещё 40%, а сложные персонализированные запросы по счетам клиента закрывают оставшиеся 30%. Дальше под каждый тип подбирается свой слой.

Резать расход мы можем в трёх разных местах. Убрать вызов целиком, урезать число токенов в вызове, удешевить сам токен. Токен — кусочек текста; на такие кусочки модель режет и присланный ей запрос, и собственный ответ, а счёт провайдер выставляет по их числу: отдельно за вход, отдельно за выход, по разным ставкам. Рычаги независимы и друг друга не заменяют. Порядок, в котором их включают, диктует цена самого слоя. Первые стоят ноль, последние оплачиваются лишними вызовами.

Вопрос «стоит ли этот запрос своих денег» решается до всего этого: там считают себестоимость одного обращения и ценность действия, которое агент выполнил вместо человека (разобрано в соседнем посте). У бота решение уже принято, продукт в проде, и задача перед нами стоит другая — срезать расход на потоке, не уронив качество.

Три вещи со схемы ниже стоит назвать сразу. Модельный шлюз — сервис, через который приложение ходит к провайдеру. Это единственное место, где видны все вызовы разом, поэтому на нём и схлопывают дубли, и метят запросы, и режут бюджет. Кэш префикса переиспользует уже посчитанное начало промпта, инструкции и примеры, одинаковые от запроса к запросу, и берёт за повторное чтение десятую долю обычной цены. Семантический кэш (semantic cache) хранит готовые ответы и достаёт их не по точному тексту вопроса, а по его смыслу: «какие ставки по вкладам?» и «расскажи про процент по вкладу» приведут к одной записи.

Ввод пользователя
   │
   ├─ пауза перед отправкой (интерфейс)  вызова нет, цена 0
   ├─ правила и регулярные выражения     вызова нет, цена 0
   ├─ схлопывание дублей на шлюзе        вызовов меньше, цена 0
   ├─ семантический кэш                  вектор на каждый запрос
   ├─ кэш префикса промпта               запись дороже обычного входа
   ├─ каскад: младшая → старшая модель   при эскалации 2 вызова
   │
   ▼
вызов модели
Рисунок — собственная цена каждого слоя. Первые три обходятся в ноль токенов, семантический кэш требует векторизации каждого входящего запроса, кэш префикса оплачивается записью (у Anthropic на 21 августа 2026 года это 1,25–2× от базовой цены входа), а каскад при эскалации платит за два вызова вместо одного.

Типичная ошибка проектирования — поставить семантический кэш первым. Он выглядит самым умным слоем, однако каждому входящему запросу нужен эмбеддинг: числовой вектор смысла, который считает отдельная модель-энкодер, и считает не бесплатно. У бота это 8 000 векторизаций в сутки, из которых 2 400 приходятся на «спасибо». То же «спасибо» отсекается регулярным выражением за микросекунды и ноль. Дорогой слой не может стоять раньше дешёвого, иначе до дешёвого деньги уже потрачены.

2. Вызовы, которых не должно было быть

Дешевле всего обходится вызов, которого не было. У бота таких обращений 2 400 в сутки, те самые 30% вежливости, и убираются они приёмами, которые не трогают качество ответов вообще.

Пауза перед отправкой. У чата с отправкой по Enter этого слоя нет, ведь пользователь сам решает, когда запрос уходит. Приём нужен там, где вызов случается на каждое нажатие клавиши, например в поле поиска с подсказками от модели. Слово из десяти символов даёт десять запросов при отправке по каждому нажатию и один при отправке после того, как пользователь допечатал; пауза в 400 мс срезает, по ходовой оценке, около 90% промежуточных вызовов подсказки. Опубликованного замера под этой цифрой нет, а платит за паузу пользователь: подсказка приходит на те же 400 мс позже, и другой цены у слоя не бывает.

Детерминированная предварительная проверка ставит жёсткие правила, которые отсеивают запрос до обращения к модели. Пустой ввод, случайный набор символов, запрос заведомо не из домена, известные атакующие паттерны закрываются именно так. У бота сюда уходят все 2 400 вежливых обращений. Они получают шаблонный ответ, и модель о них не узнаёт. Здесь же экономия сходится с безопасностью, потому что тот же слой ловит часть попыток внедрения инструкций в промпт раньше, чем они дойдут до модели.

Схлопывание одновременных дублей. Паттерн SingleFlight («один в полёте») перенесён из обычного бэкенда на модельный шлюз. Если такой же запрос уже считается, второй не отправляется, а подписывается на чужой результат. У бота этот слой оживает в конкретный момент. ЦБ меняет ключевую ставку, и сотни клиентов в одну минуту спрашивают одно и то же.

async def coalesced_call(key: str, fn):
    if key in inflight:              # такой запрос уже считается
        return await inflight[key]   # ждём чужой результат, не вызывая модель
    task = asyncio.create_task(fn())
    inflight[key] = task
    try:
        return await task
    finally:
        del inflight[key]

Разве эту работу не должен делать кэш? Кэш работает с уже завершёнными запросами, а схлопывание — с одновременными. Запрос, для которого нужный кусок уже посчитан и берётся готовым, называют попаданием в кэш (cache hit). На всплеске трафика попадать некуда: первый ответ ещё не готов, класть в кэш нечего. Anthropic пишет об этом в документации прямым текстом: запись появляется только после того, как началась первая генерация. Значит, чтобы в кэш попадали и параллельные запросы, надо дождаться первого ответа и лишь потом отправлять остальные.

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

Посчитаем по прайсу Anthropic на 21 августа 2026 года, где запись стоит 1,25 базовой ставки входа. Пять одновременных одинаковых запросов дают пять записей в кэш, итого 6,25 ставки. Те же пять запросов, пущенные с ожиданием первого ответа, дают одну запись и четыре чтения по 0,1×, то есть 1,65 ставки. Разница почти четырёхкратная, и создаётся она тем, отправлены ли запросы веером или лесенкой. Ни модель, ни промпт на неё не влияют.

3. Вход растёт сам: история, найденные документы и потолок ответа

У запроса, который всё-таки дошёл до API, две части с разными ставками. Входная дешевле, но растёт сама, без единой правки в коде; выходная стоит впятеро-вшестеро дороже и не кэшируется в принципе.

Входную часть редко проектируют, она накапливается. Около 30% её объёма занимают определения инструментов и системный промпт, ещё около 20% добавляют примеры в промпте (few-shot), около 10% приходится на историю диалога, а всё остальное добирают куски документов, подложенные поиском. Неизменна из этого одна статья расхода. Набор примеров живёт в репозитории и сам не меняется, а история и найденные документы растут от трафика; системный промпт растёт от правок, которые дописывают мимо релизного цикла.

История растёт хуже всего. Если на каждом ходе диалога в запрос уезжает вся переписка целиком, стоимость хода растёт с номером хода, а стоимость сессии растёт квадратично, ведь пятнадцатый ход оплачивает четырнадцать предыдущих.

Именно так устроен бот. Средний диалог у него пятнадцать ходов, и за сессию история оплачивается 120 раз вместо пятнадцати. Восьмикратно оплачивается именно история. Скользящее окно (в запрос идут только последние N ходов) или сжатие давних ходов в короткую сводку выпрямляет эту кривую в горизонталь, и потолок стоимости хода перестаёт зависеть от того, как долго пользователь разговаривает.

Платят за это забывчивостью. За краем окна модель не помнит начала разговора, клиент повторяет уже сказанное, и часть сэкономленного возвращается лишними ходами. Жёсткий max_tokens работает так же грубо: обрывает ответ на полуслове, и пользователь просит договорить, то есть тоже платит вторым обращением.

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

Правая сторона запроса, выход, дороже левой. У gpt-5.6-terra это $12 против $2 за 1 млн, у Claude Sonnet 4 — $15 против $3, у Gemini 3.7 Flash — $3.75 против $0.75. Все три пары на 21 августа 2026 года укладываются в одно отношение, впятеро-вшестеро, и задаёт его физика генерации, а не ценовая политика вендора. Три прайса на одну дату остаются снимком, и как поведёт себя разрыв через год, из них не видно. Расхожая оценка «втрое-вчетверо» ниже, чем любая из этих трёх пар.

Разницу создаёт железо. Обработка входа параллельна, генерация выхода последовательна и упирается в пропускную способность памяти (подробнее в соседнем посте). Поэтому max_tokens стоит читать как статью бюджета: он задаёт потолок самой дорогой части счёта. Правило при этом условное. Сокращение ответа обгоняет правку входного промпта там, где ответы многословны, и проверяется это одной цифрой, долей выходных токенов в объекте usage, который приходит с каждым ответом API.

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

4. Кэш префикса: что кэшируется и чем это ломают

Шапка промпта (инструкции, определения инструментов, примеры в промпте) в каждом запросе одна и та же, а оплачивается в каждом. Платить за неё один раз позволяет кэш префикса; у вендоров этот механизм называется кэшированием промпта (prompt caching). Он переиспользует уже посчитанное представление статической части, и на 21 августа 2026 года чтение стоит десятую долю базовой ставки входа у Anthropic, OpenAI и Google.

У бота эта шапка большая. Правила банка, описания инструментов доступа к счетам, эталонные диалоги дают порядка трёх тысяч токенов, одинаковых в каждом из 2 400 сложных обращений в сутки. Без кэша это 7,2 млн входных токенов в сутки на одну только шапку. С работающим кэшем за них платится как за 0,72 млн.

Сервер провайдера хэширует статическую часть промпта и, встретив знакомый хэш, не считает её заново. Ключевое слово здесь именно «префикс»: кэш смотрит на начало строки, а совпадения в произвольном месте ему безразличны. Честнее маркетинга границу очерчивает документация vLLM: механизм сокращает только обработку входа, то есть фазу предзаполнения, а на скорость генерации новых токенов не влияет вообще. Кэш работает слева от запроса и ничем не помогает справа.

[СТАТИЧЕСКИЙ ПРЕФИКС — кэшируется]
   системные инструкции
   определения инструментов
   примеры в промпте            3000+ токенов, чтение 0,1× входа
─────────────── граница кэша ───────────────
[ДИНАМИЧЕСКИЙ СУФФИКС — полная ставка]
   найденные документы
   сообщение пользователя
   текущее время, идентификатор сессии
Рисунок — анатомия кэшируемого промпта. Всё выше границы читается по 0,1 от базовой ставки входа, всё ниже оплачивается полностью, а перенос любой динамической строки выше границы обнуляет попадание целиком, а не частично.

Что же ломает попадание? Обычно временная метка или идентификатор сессии в первой строке системного промпта, антипаттерн дорогой и совершенно тихий. Хэш префикса меняется на каждом запросе, попаданий нет никогда, расход растёт без единой ошибки в логах.

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

messages = [{
    "role": "system",
    "content": [
        {"type": "text", "text": SYSTEM_RULES + TOOL_DEFS + GOLDEN_EXAMPLES,
         "cache_control": {"type": "ephemeral"}},   # всё выше кэшируется
        {"type": "text", "text": f"Текущее время: {now()}"},   # ниже границы
    ],
}]

Эксплуатационных деталей у кэша префикса три, и про все три обычно узнают уже в проде.

Порог длины. Короткий префикс не кэшируется вообще, даже с проставленной границей. У Anthropic минимум зависит от модели: 512 токенов для Claude Opus 5, Fable 5 и Mythos 5, 2048 для Mythos Preview и Opus 4.7, 4096 для Opus 4.6 и Haiku 4.5. У OpenAI кэш включается автоматически от 1024 токенов, но там же оговорено: минимальная длина кэшируемого префикса зависит от модели и лежит в диапазоне от 1024 до 2048. Промпты чуть выше тысячи кэшируются непоследовательно.

Гранулярность блока. Потокенного учёта нет — попадание засчитывается кусками, и у OpenAI на 21 августа 2026 года шаг составляет 128 токенов. Известное правило «выравнивать префикс по границе 16 токенов» относится к vLLM, у которого блок именно такой; на API OpenAI его переносить бессмысленно, там шаг в восемь раз крупнее.

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

Долю попаданий держат на дашборде рядом с задержкой, это такая же эксплуатационная метрика. Для аккуратно спроектированного агента ориентир берут в районе 80–90%; планка ходовая, а не измеренная норма, и калибруется она на своём трафике. Просадку разбирают как инцидент. Доля попаданий падает раньше, чем вырастут расходы, и почти всегда указывает на конкретную поломку. Кто-то дописал динамику в начало, поменял системный промпт или переставил блоки местами.

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

5. Прайс как рычаг: множители перемножаются, а политики вендоров расходятся

Десятая доля ставки не единственный множитель к цене входного токена. Anthropic документирует это прямо: множители перемножаются с прочими модификаторами цены, включая скидку за пакетную обработку. Скидки, стало быть, не выбирают по одной. Их набирают цепочкой, и наценки нанизываются туда же.

базовая ставка входа                         1,00×
  запись префикса в кэш, 5 минут             1,25×
  запись префикса в кэш, 1 час               2,00×
  чтение из кэша                             0,10×
  пакетный режим                             0,50×
  пакетный режим + чтение из кэша            0,05×  ← 5% от базовой
  длинный контекст (OpenAI)                  2,00×
  приоритетный класс (Gemini)                1,80×
Рисунок — множители к базовой ставке входа по документации вендоров на 21 августа 2026 года. Внутри одного вендора они перемножаются, поэтому асинхронный пакет с попаданием в кэш обходится у Anthropic в 5% от полной ставки. С наценками ровно то же самое. У Gemini приоритетный класс идёт по 1,8, у OpenAI длинный контекст по 2,0, но между собой эти две наценки не складываются, потому что взяты из разных прайсов.

Пакетный режим меняет синхронность на половину цены. Запросы уходят пачкой, ответы забираются позже. У Anthropic в пачку влезает 100 000 запросов или 256 МБ, большинство закрывается в течение часа, невыполненное истекает через 24 часа, результаты лежат 29 дней.

Годится всё, где никто не ждёт у экрана, от переиндексации и дозаполнения исторических данных до офлайновых прогонов оценки качества и массовой классификации. У бота под это подпадают ночной пересчёт справочника по тарифам и регулярный прогон оценки качества на зафиксированном списке вопросов, и половина цены достаётся им просто за то, что ответ нужен к утру.

Одна оговорка в документации легко теряется: запросы в пачке выполняются асинхронно и параллельно, поэтому попадания в кэш обеспечиваются лишь по мере возможности. Порядок выполнения внутри пачки не гарантирован — отсюда и рекомендация ставить для пакетов часовой срок жизни кэша, пятиминутного по умолчанию не хватает.

Класс обслуживания работает отдельным рычагом, ортогональным выбору модели. У Gemini одна и та же модель продаётся в четырёх режимах. Стандартный идёт по прайсу, пакетный и гибкий дают скидку 50%, приоритетный стоит в 1,8 раза дороже. Решение «что перевести в пакет, а что в приоритет» принимается по тому, ждёт ли человек ответа, и не требует ни смены модели, ни правки промптов.

Ставка чтения из кэша совпадает у Anthropic, OpenAI и Google и составляет десятую часть входа. На этом сходство заканчивается, и дальнейшие различия ломают приёмы, которые кажутся универсальными.

Anthropic OpenAI Google Gemini
Включение явная граница cache_control, до 4 на запрос автоматически от 1024 токенов неявно (без гарантии) либо явным объектом caches.create
Минимальный префикс 512 / 2048 / 4096 по моделям 1024–2048 по моделям 2048–4096 по моделям
Запись ×1,25 (5 мин) / ×2 (1 час) ×1,25 на GPT-5.6+, раньше без доплаты отдельной строки в прайсе нет, объект создаётся через caches.create
Чтение ×0,1 ×0,1 ×0,1
Хранение бесплатно бесплатно $0.50 за 1 млн токенов в час
Попадания и лимит токенов в минуту не списываются списываются

Расхождение в последней строке, про лимит токенов в минуту, обходится дороже остальных, и обе стороны стоит привести дословно. Пересказ своими словами сгладил бы главное: два вендора пишут про одну и ту же вещь прямо противоположное.

Anthropic: «cache hits are not deducted against your rate limit». Попадания не списываются с лимита, и при их доле в 80% лимит, скажем, в 2 млн входных токенов в минуту превращается примерно в 10 млн эффективных, потому что в лимит идут только промахи, а их пятая часть. Кэш работает сразу и скидкой к цене, и множителем к пропускной способности.

OpenAI: «Cached input tokens still count toward tokens-per-minute rate limits. Prompt caching does not change rate-limit calculations». Кэшированные токены считаются в лимит наравне с обычными, и на пропускную способность кэш не влияет вообще. Архитектурное решение «упрёмся в лимит — поднимем долю попаданий» верно ровно для одного из двух провайдеров, а при переезде между ними ломается молча. Для бота это разница между «переживём утренний час пик на текущем лимите» и «упрёмся в него ровно тогда, когда пришли клиенты».

Второе расхождение — плата за хранение у Gemini. Там два механизма с разными обещаниями. Неявный кэш включён по умолчанию, и экономии он не гарантирует — так прямо и написано в документации. Явный требует создать объект кэша руками и гарантию даёт, но оплачивается она хранением по $0.50 за 1 млн токенов в час.

Одно чтение миллиона закэшированных токенов экономит $0.75 − $0.075 = $0.675, так что час хранения отбивается первым же обращением. Сутки простоя стоят $12 и требуют примерно восемнадцати обращений, чтобы выйти в ноль. Вопрос от этого смещается с «кэшировать или нет» на «сколько раз мы обратимся к объекту, пока он жив». В пакетном тарифе токены дешевеют вдвое, а хранение остаётся прежним, поэтому доля хранения в общей сумме удваивается.

Есть и третье расхождение, менее заметное. Кэш-политика бывает связана с политикой хранения данных. У OpenAI модели gpt-5.5 и gpt-5.5-pro поддерживают только суточное удержание кэша, а при включённом режиме нулевого хранения данных настройка откатывается к кратковременному кэшу в памяти.

Плюс попадание зависит от маршрутизации: запрос уезжает на конкретную машину по ключу prompt_cache_key, а хэш начала промпта работает лишь вторичным ключом. Совпадения текста мало — запрос ещё должен попасть туда, где лежит запись.

И последнее, что стоит вычитывать из прайса. Расхожее «цены на токены только падают» неверно: в прайс-листе Gemini записано плановое удвоение, $0.75 за 1 млн входа до 31 декабря 2026 года и $1.50 начиная с 1 января 2027-го. Касается оно всех строк, включая хранение кэша, которое вырастет с $0.50 до $1.00 в час. У OpenAI длинный контекст тарифицируется вдвое дороже короткого ($4.00 за 1 млн входа против $2.00), потому что цену задаёт режим обработки, и тот же объём в коротком режиме стоит вдвое дешевле. Расчёт экономии, построенный на допущении о монотонном удешевлении, живёт до ближайшего обновления прайса.

6. Кэш по смыслу: экономия ценой чужого ответа

Множители из прайса отвечают на вопрос, как дешевле оплатить вызов. Самый крупный кусок трафика бота вызова не требует вовсе. На вопросы про тарифы приходится 3 200 обращений в сутки, те самые 40%, и спрашивают в них одно и то же снова и снова разными словами. Здесь и работает семантический кэш: ключом ему служит вектор запроса, а не его текст, поэтому десяток формулировок одного вопроса сходится в одну запись, и модель об этих обращениях не узнаёт.

Обычный кэш по хэшу строки для пользовательских запросов почти бесполезен, ведь одно и то же формулируют бесконечным числом способов, и совпадение символ в символ случается редко. На машинном трафике всё наоборот. Фоновые задания, повторные попытки, дозаполнение данных шлют байт в байт одинаковые промпты, и точный кэш по хэшу там остаётся первым и самым дешёвым слоем. Дорогие слои имеет смысл ставить уже за ним.

Для живых пользователей замена ключа на вектор превращает «редко» в «часто». Энкодер кодирует запрос в вектор, а индекс приближённого поиска HNSW (многослойный граф, по которому ближайшая запись находится за логарифмическое число сравнений вместо перебора всех) ищет ближайшего соседа в векторной базе. Близость двух векторов меряют косинусным сходством, и если оно выше порога, бот отдаёт готовый ответ, не обращаясь к модели.

def get(self, query: str, threshold: float = 0.96) -> str | None:
    vector = encoder.encode(query)
    hit, similarity = self.index.search(vector, namespace=self.role_ns)
    if similarity < threshold:
        return None                      # промах: идём в модель
    return hit.response

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

Поэтому планку ставят высоко, 0,96 для чувствительных доменов вроде банковского и 0,92–0,94 в учебных реализациях на менее рискованных. Обе цифры остаются рабочей эвристикой, константой их считать нельзя. Публичной методики, которая связала бы конкретный порог с долей попаданий и долей ложных срабатываний на произвольном корпусе, нет, и калибровать его приходится на своих размеченных парах «тот же вопрос / другой вопрос».

Одну пару при этом не разведёт никакой порог. «Как открыть счёт?» и «Как закрыть счёт?» дают высокое косинусное сходство при противоположном смысле. Эмбеддинг ловит тему; намерение он не различает. Лечится это ключом. Либо в ключ добавляют само действие, «открыть» против «закрыть». Либо перед кэшем ставят классификацию намерения, дешёвый предварительный шаг, который определяет, что у бота просят сделать, и подмешивает ответ в ключ.

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

Второй риск, утечка, случается, когда ответ с персональными данными пользователя A уходит пользователю B. Для банковского бота это худший из возможных инцидентов, и решение здесь архитектурное. Права доступа включают в ключ, то есть держат отдельные пространства имён по ролям.

А вот утечка, от которой пространства имён в собственном коде не спасают. Работа «Auditing Prompt Caching in Language Model APIs» (Стэнфорд, ICML 2025) показывает, что кэш работает каналом утечки по времени ответа: закэшированный промпт обрабатывается заметно быстрее незакэшированного, и разница измерима снаружи. Если кэш общий для пользователей, атакующий отправляет предполагаемый промпт и по скорости ответа узнаёт, спрашивал ли его кто-то до него.

Авторы обнаружили глобальное разделение кэша между пользователями у семи провайдеров, включая OpenAI. Побочный результат того же замера показателен не меньше. По таймингам удалось выяснить внутреннее устройство эмбеддинг-модели OpenAI. Она построена на декодере, то есть на той же половине архитектуры, на которой работают генеративные модели, а публично это не раскрывалось. Через кэш утекают и пользовательские данные, и детали архитектуры. Отсюда практика. Изоляцию кэша смотрят в документации конкретного провайдера, предполагать её нельзя. Anthropic, например, декларирует изоляцию между организациями явно.

7. Каскад: младшая модель по умолчанию

Промах семантического кэша ещё не повод звать флагмана. В каскаде весь трафик сначала идёт в дешёвую модель, а на дорогую эскалируется только то, что не прошло проверку уверенности: по готовому черновику она решает, отдавать его пользователю или платить флагману за второй заход. Ступени каскада принято называть тирами, младший закрывает 70–80% ответов, старший разбирает остаток.

Отличие от привычного запасного варианта принципиальное. Там переключение происходит на сбое, когда провайдер отдал ошибку или сломался формат. Здесь переключает сомнение в качестве при полностью здоровой системе.

У бота под каскад уходит тот же массив тарифных вопросов. Часть из 3 200 закрылась семантическим кэшем, остальное отправляется в младшую модель, и до флагмана из этой доли доезжает примерно каждый четвёртый запрос.

                 Запрос
                    │
                    ▼
      Тир 1: дешёвая, быстрая модель
                    │
                    ▼
                 черновик
                    │
                    ▼
          проверка уверенности
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
 прошёл (70–80%)       не прошёл (20–30%)
        │                       │
        │                       ▼
        │                Тир 2: флагман
        │                       │
        └───────────┬───────────┘
                    ▼
              пользователю
Рисунок — каскад из двух тиров. Проверка пропускает 70–80% ответов младшей модели напрямую пользователю и отправляет оставшиеся 20–30% на повторное решение флагманом, а экономика держится на том, что цена вызова между тирами различается на порядки.

Второй вызов не единственная плата за каскад. Запрос, не прошедший проверку, ждёт две генерации подряд, и удвоенное ожидание достаётся ровно тем клиентам, у которых вопрос и без того был трудным.

Работает это потому, что качество на большинстве задач упирается в плато. Ориентир, который называют практики, — порядка 80% задач классификации и извлечения сущностей выходят на плато уже на младших моделях. Кто и на каком корпусе это мерил, практики не уточняют, но деньги за верхний тир в любом случае платятся за проценты качества, нужные меньшинству запросов.

Первоисточником самой идеи была FrugalGPT (Chen, Zaharia, Zou, Стэнфорд, 2023): авторы заявили, что каскад повторяет качество лучшей одиночной модели при снижении затрат до 98%. Читать эту цифру нужно осторожно — это верхняя граница на конкретном датасете, ценах и моделях 2023 года, типовым результатом она не была никогда. Полезнее в той работе другое. Каскад назван там лишь одним из трёх классов приёмов, наряду с адаптацией промпта и аппроксимацией модели, а мотивация построена на разбросе прайса: цены разных моделей отличались на два порядка.

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

Тип Механика Когда срабатывает
Эвристики и правила Длина ответа меньше N, стоп-слова («не могу ответить»), сбой разбора JSON после генерации, цена ~0
Логарифмы вероятностей Средняя уверенность модели в выбранных токенах ниже порога, например avg(logprobs) < -0.45 после генерации, цена ~0, но провайдер должен их отдавать
Отдельная модель-верификатор Дешёвая модель читает черновик и отвечает на один вопрос: «этот текст отвечает на заданный?» после генерации, один дешёвый вызов
Обратная связь пользователя Дизлайк, нажатая кнопка «повторить», тот же вопрос, заданный другими словами после доставки ответа, инфраструктура ~0
Энтропия ветвления рассуждения Разброс вариантов продолжения на первых шагах рассуждения, посчитанный локально до генерации

Первые четыре роднит то, что они судят уже потраченные деньги. Черновик сгенерирован, токены оплачены, проверка лишь решает, платить ли второй раз.

Обратная связь пользователя в этом ряду самая точная и самая поздняя. Судит тот, кто действительно читал ответ, правда, уже после того, как плохой вариант ему показали. Поэтому её ставят дополнением к автоматическим проверкам; заменить их она не может.

Последний тип работает иначе. В работе «Not All Tokens Are Equal» предложен сигнал, который считают до генерации, — энтропия ветвления цепочки рассуждений (CoT Branching Entropy). Цепочка рассуждений здесь означает пошаговый разбор задачи, который модель пишет перед самим ответом, а энтропия ветвления показывает, насколько сильно расходятся возможные продолжения на первых её шагах. Высокий разброс говорит, что очевидного пути решения у задачи нет и дешёвая модель на ней завязнет. Это препринт 2026 года, независимого воспроизведения пока нет, а дальше на нём стоят ещё несколько чисел поста.

Качество сигнала авторы оценивают в AUROC 0,887. Метрика показывает, насколько уверенно признак разделяет два класса. Значение 0,5 равносильно монетке, 1,0 означает идеальное разделение, а 0,887 читается как «уверенно, но не безошибочно».

Порядок проверок важнее их набора. Сначала бесплатные, потом платные. Вызывать верификатор до того, как отработали правила, — значит платить за то, что и так видно по длине ответа.

def answer(query: str) -> str:
    draft = call_tier1(query)          # дешёвый вызов, $0.0001

    if parse_failed(draft) or len(draft) < MIN_LEN or has_refusal_phrase(draft):
        return call_tier2(query)       # эскалация по эвристике, $0.005
    if draft.avg_logprob < -0.45:
        return call_tier2(query)       # эскалация по неуверенности

    return draft                       # тут заканчиваются 70–80% трафика

Порог -0.45 годится как отправная точка, но калибруют его на своём трафике. У части провайдеров логарифмы вероятностей вообще недоступны, и второй тип проверки отпадает целиком.

Альтернативой каскаду служит маршрутизация до вызова, когда модель выбирается по самому запросу, ещё до того, как появился черновик. RouteLLM (LMSYS / UC Berkeley, 2024) обучает роутер на данных о человеческих предпочтениях и решает заранее, кому отдать запрос. По замерам авторов такой роутер снижает затраты — в отдельных случаях более чем вдвое — и качество при этом не проседает.

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

Привязку задач к тирам держат в конфиге, иначе смена модели превращается в релиз приложения.

task_routing:
  intent_classification: "tier_1_haiku"
  rag_answer:            "tier_2_sonnet"
  contract_analysis:     "tier_3_opus"

Родственный приём делит роли внутри одной задачи и называется разделением архитектора и исполнителя (Architect/Editor split). Дорогая модель проектирует короткий план в структурированном виде, дешёвая печатает по нему основной объём. Экономию на генерации кода оценивают в 50–80%. Оценка гуляет по разборам без методики, зато механика за ней понятная: дорогие токены уходят на десяток строк плана, а сотни строк реализации оплачиваются по младшему тарифу.

8. Почему экономию каскада считают в свою пользу

Проценты экономии выше посчитаны по цене вызова, и в этом ошибка. Каскад, посчитанный так, может занижать реальную стоимость сценария больше чем вдвое на трудных задачах, а в пике замера доходит и до 4,25 раза. Сам эффект авторы работы 2026 года называют инфляцией токенов, отношением настоящей стоимости всего сценария к стоимости одного вызова.

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

У бота под это описание подходит ровно оставшаяся треть трафика, 2 400 персонализированных запросов по счетам клиентов в сутки. Там надо сходить в несколько внутренних систем, сверить остатки, разобрать выписку. Соблазн отдать их младшему тиру велик, ведь цена по прайсу падает на порядок, и именно на них инфляция токенов съедает выигрыш.

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

Авторы замерили обратное. Проваленная цепочка рассуждений, переданная в GPT-4o, роняет её точность на величину до 34,8 процентного пункта. Испорченный черновик хуже чистого листа, потому что модель верхнего тира читает чужую цепочку как контекст, а не как гипотезу. Ошибочная промежуточная посылка фиксирует направление решения, и усилия уходят на починку чужого пути вместо поиска своего.

Приём называется эскалацией с чистого листа. Черновик выбрасывается, а старший тир получает исходную задачу и решает её заново. Собранный по этим двум принципам маршрутизатор в замерах авторов даёт на школьных арифметических задачах GSM8K под фиксированным бюджетом 94,7% против 91,0% у FrugalGPT при 31% меньшем расходе токенов.

Своя ловушка ждёт тех, у кого проверку делает отдельная модель-верификатор. В разборе инцидента с бесконечным циклом двух агентов этот режим отказа назван услужливым верификатором (в оригинале sycophant verifier). Модель, обученная быть полезной, без жёсткого рубрикатора приёмки почти всегда находит, что ещё улучшить, и потому не одобряет результат почти никогда. На месте проверки уверенности такой верификатор эскалирует всё подряд, и каскад превращается во флагман плюс лишний вызов сверху. Симметричная беда случается при слишком мягкой проверке, когда плохие ответы уходят пользователю и экономия оплачена качеством.

Из-за этого экономию каскада нельзя принимать по отчёту о снижении цены вызова. Проверяем мы её стоимостью исхода, суммой затрат на закрытую задачу, а не на обращение к API. Дешёвая модель, отвечающая так, что пользователь переспрашивает четыре раза, обходится дороже одного точного вызова, и метрика на уровне вызова этого не покажет (подробный разбор в соседнем посте). Стоимость поэтому проверяют тестами качества, а без них получается деградация с отчётом об экономии.

9. Разрезать счёт: по каким осям и почему разметку ставят заранее

Стоимость исхода считается по одному сценарию, а продукт состоит не из одного. У бота их три, у сервиса покрупнее — три десятка, и счёт от провайдера всё равно приходит одной суммой в конце месяца. Дальше все делают одно и то же. Сумма выросла, значит, режем везде понемногу.

Так резать бессмысленно, потому что дорожают отдельные куски, а не всё сразу. Учебный счёт на $10 000 раскладывается по фичам и контурам в одну картинку, по которой половину решений принимают сразу. Из итоговой суммы её не собрать.

$10 000 за месяц — одна строка в счёте провайдера
   │
   ├── фича A ................. 60%   $6 000
   ├── фича B ................. 25%   $2 500
   ├── dev и stage ............ 10%   $1 000
   └── разовые скрипты ........  5%     $500
Рисунок — учебное разложение счёта: две фичи забирают 60% и 25%, отладочные и тестовые контуры 10%, разовые скрипты аналитиков оставшиеся 5%.

Разрезов, по которым раскладывают счёт, четыре, и выстраиваются они лестницей от самого грубого к самому дорогому в получении.

дороже в поддержке
  ▲
  │  клиент    Enterprise · Pro · Free   кто съедает маржу
  │  фича      Summarize · Deep Search   что окупается
  │  команда   Core · Support · Parser   чей бюджет вырос
  │  среда     prod · stage · CI         сколько уходит мимо клиентов
  └─ ставится первым
Рисунок — лестница разрезов затрат. Нижняя ступень, среда исполнения, отделяет прод от отладочных контуров, на которые в учебном разложении уходит 10% счёта; выше идут команда и фича, а верхняя ступень, разрез по клиенту, показывает, какой тариф забирает маржу.

Нижнюю ступень ставят за полчаса, и окупается она часто сразу: расход отладочных контуров и прогонов в CI обычно никто не считал, а идёт он по тому же прайсу, что и клиентский. Следующие две требуют договориться об именах фич и сервисов внутри команды, и это уже не полчаса. Верхняя дороже всех в поддержке и нужна только там, где тарифы разные, а нагрузка на них — нет.

Ради верхней ступени всё и городят. Стоимость фичи сама по себе ничего не решает; решает она в паре с тем, что фича приносит:

Фича Расход на модели Что приносит Разница
Summarize $4 500 $6 200 +$1 700
Deep Search $8 000 $2 100 −$5 900
Intent-Classification $1 200 $2 000 +$800

Числа здесь учебные, а картина типовая. Без разреза по фичам убыточный Deep Search растворяется в общем счёте и живёт годами: сервис в целом прибыльный, поэтому вопросов никто не задаёт. В уроке 21 та же разность считалась на один запрос и отвечала на вопрос, стоит ли вообще делать эту фичу. Здесь она же считается по фиче за месяц и отвечает на другой: какую из уже сделанных пора чинить или выключать.

Собирают разрез на модельном шлюзе. Приложение проставляет метаданные заголовками, шлюз пишет их рядом с объектом usage из ответа API, и всё это асинхронно уезжает в аналитическую базу. Токены при этом считают именно по usage, а не по длине отправленного текста, потому что на токены текст режет провайдер, и собственная оценка со счётом не сойдётся.

response = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    extra_headers={"X-Feature-ID": "rag_answer",   # какая фича
                   "X-Team-ID": "support-bot",     # чей бюджет
                   "X-User-Tier": "pro"},          # какой тариф
)

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

Способ Что даёт Чем платят
Прокси перед провайдером (Helicone, Portkey) Заводится за день, кэш и лимиты уже внутри Лишний сетевой переход и зависимость от чужого сервиса
Трассировка из кода (Langfuse, LangSmith) Видно всё дерево вызовов внутри одного обращения Нужно встраивать в приложение
Свой шлюз с собственной базой Полный контроль, никакой привязки к вендору Его придётся написать и поддерживать

У правила, которое из этого следует, есть короткое имя: размечай раньше, чем строишь (tag before you build). Метаданные закладывают в шлюз с первого дня, потому что задним числом к потраченным деньгам разметку не приписать. В логе провайдера остаются сумма, модель и время; чья это была фича и чей тариф, знало только приложение — и знало ровно в момент вызова.

Нашему боту разрез по фичам нужен меньше, чем кажется, и вот почему. Фича у него одна, зато разбор трафика на вежливость, тарифные вопросы и персональные запросы из первого раздела — это тот же разрез, только по типу обращения. Именно он позволил подобрать свой слой под каждый тип, и без него подбирать было бы нечего. У продукта из тридцати сценариев такой разбор руками не сделать: там он и превращается в feature_id на шлюзе.

Ошибаются здесь одинаково — заводят дашборд, на который приятно смотреть. Панель «расход по дням» красивая и совершенно бесполезная, потому что не подсказывает ни одного действия. Каждая панель обязана вести к конкретному инженерному шагу, а если не ведёт, её место не на дашборде.

10. Аномалии расходов и лимиты, которые срабатывают до следующего вызова

Счёт умеет уезжать не потому, что запросы дорогие, а потому, что их стало на порядок больше, чем предполагала архитектура. Внеплановый расход — отдельный класс проблем, и в опубликованном разборе такой сюжет обошёлся примерно в $47 000 при полностью работающем мониторинге.

Наблюдаемость и принудительное ограничение (enforcement) — разные контуры, и их регулярно путают. Наблюдаемость показывает, что происходит внутри системы: дашборды, алерты, трассировка вызовов. Ограничение стоит на пути запроса и останавливает трату до того, как она произошла. Алерт сообщает о том, что уже случилось, и наличие первого контура ничего не говорит про наличие второго.

Сначала про наблюдаемость. Расходы растут несколькими разными способами, и путать их нельзя, потому что у каждой формы своя причина и своя реакция.

Форма Как выглядит Что за ней стоит
Скачок Резкий пик за минуты Рекурсивный баг ретраев, внедрение инструкции с выводом бесконечного текста, отказ в обслуживании через токены
Дрейф Плавный подъём за недели Накопление истории в контексте, незаметно растущий системный промпт, рост длины диалогов
Сезонная волна Регулярная волна Всплеск в будни днём, падение ночью и в выходные — норма, а не авария

Статический порог в долларах ловит только первую форму, ругается на третью и слеп ко второй, а дрейф съедает бюджет незаметнее всех. Работающая формулировка сравнивает расход с ним же неделю назад в тот же час, а не с константой, и сезонность вычитается автоматически.

Бот в исходном состоянии живёт как раз с дрейфом. Средняя длина диалога ползёт вверх вместе с доверием к сервису, каждый ход тащит на вход всю историю, и расход растёт без единого релиза. На дашборде это неотличимо от органического роста продукта.

# расход токенов за час превысил аналогичный час прошлой недели вдвое
(sum(rate(ai_tokens_total[1h])) by (feature_id))
  > 2.0 * (sum(rate(ai_tokens_total[1h] offset 7d)) by (feature_id))

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

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

На рваном профиле всё наоборот. Внеплановая разовая выгрузка сама по себе даёт всплеск выше 2× при полностью здоровой системе, и это уже ложные срабатывания, а после десятка таких дежурный перестаёт открывать алерт. Ровный трафик просит порога ниже двойки или окна пошире, рваному нужен множитель больше и подтверждение на нескольких соседних окнах.

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

Со вторым контуром, с ограничением на пути запроса, дело обстоит хуже. В том самом разборе инцидента два агента на протоколе межагентного взаимодействия крутились в цикле 264 часа — одиннадцать суток. Верификатор ни разу не сформулировал измеримого критерия приёмки, поэтому анализатор бесконечно переделывал работу. Остановил цикл порог на биллинговом дашборде.

Разбор сводит случившееся в одну фразу: «The team had observability. They did not have enforcement». Наблюдаемость у команды была, ограничения не было. Отдельным пунктом там же стоит «Dashboards Are Not Brakes»: дашборд ничего не тормозит, а проверка бюджета обязана стоять внутри исполнения агента, а не в биллинге, который узнаёт о тратах часами позже. Сумма и компания в этом разборе независимо не подтверждаются, зато механика воспроизводима и без точной цифры.

Тормоз складывается из трёх проверок, и каждая стоит на пути запроса. Терминирующее условие, которое вычисляется кодом без участия модели («верификатор одобрил три раунда подряд» или «прошло пять итераций»). Бюджет на агента и на весь конвейер, сверяемый перед следующим вызовом. И проверка на топтание на месте, когда соседние итерации сравнивают по смыслу и почти совпавшие считают сигналом, что агент никуда не движется.

def guarded_call(ctx, fn):
    if ctx.spent + ctx.estimate(fn) > ctx.budget:   # проверка ДО вызова
        return degrade(ctx)                         # понижаем класс, не падаем
    if ctx.iteration >= ctx.max_iterations:
        return ctx.partial_result("iteration_limit")
    if ctx.no_progress_rounds >= 2:                 # итерации почти совпали
        return ctx.partial_result("no_progress")

    result = fn()
    ctx.spent += result.usage.cost                  # только официальный usage
    return result

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

И предупреждение, ради которого имеет смысл открыть документацию своего шлюза. LiteLLM позволяет задать max_budget с циклом сброса budget_duration, мягкий порог soft_budget для предупреждения, лимиты токенов и запросов в минуту на ключ и на команду.

Однако там же написано, что бюджеты требуют базы данных, а без неё глобальный лимит litellm_settings.max_budget отказывает в разрешающую сторону. Суммарный расход подгружается только при наличии клиента БД, сравнивать не с чем, и проверка молча пропускается. Ограничитель, который отваливается тихо и разрешает всё, равносилен отсутствующему. Проверяется это тестом: поднять расход выше лимита на стенде и убедиться, что следующий вызов не состоялся.

11. Сборка: что осталось от $120 в сутки

Вместе слои срезают расходы бота примерно на 70%, без единой замены модели на более умную. Вежливость закрылась правилами и шаблонами, тарифные вопросы ушли в семантический кэш и младшую модель каскада, а персонализированные запросы по счетам клиентов остались за флагманом, но уже с работающим кэшем префикса.

Слой Что закрывает Стоимость
Правила и шаблоны 30% вежливости $0
Семантический кэш + младшая модель 40% типовых вопросов $4/день
Флагман с кэшем префикса 30% сложных $32/день

Оба ненулевых числа выводятся из раскладки первого раздела. Четыре доллара — это остаток тарифных вопросов, который не закрылся кэшем по смыслу и ушёл в младшую модель каскада; тридцать два — те же 2 400 сложных обращений по флагманской ставке за вычетом того, что вернул кэш префикса. Строки складываются в $0 + $4 + $32 = $36 в сутки против исходных $120. Если принять эти $120 за 100 пунктов индекса удельной стоимости, после всех слоёв остаётся 30.

Индекс 30 требует оговорки. В таблицу входит только оплата вызовов модели, а собственная цена слоёв (векторизация каждого входящего запроса для семантического кэша, работа шлюза, хранение векторной базы) считается отдельной строкой инфраструктуры и добавляет несколько долларов в сутки. Реалистичный ориентир поэтому ближе к 30–35 пунктам индекса, чем к ровным тридцати.

Показательно, что первый слой вообще не обращается к модели. Разбор трафика по типам к устройству языковых моделей отношения не имеет, а без него не подобрать ни одного слоя: именно он показал, что треть обращений закрывается шаблоном. Цифры кейса зависят от профиля. Раскладка 30/40/30 у другого продукта будет другой, и переносить проценты экономии без собственного замера нельзя. Переносится метод: сначала разложить трафик по типам, потом подбирать слой под каждый тип.

Без замера всё перечисленное остаётся гипотезой. Каждый слой из этого поста меняет ответы. Семантический кэш отдаёт похожий ответ вместо точного, каскад отвечает младшей моделью, сжатие истории убирает часть контекста.

Проверяем мы это прогоном на золотом наборе (golden dataset), то есть на зафиксированной полусотне запросов с заранее известными правильными ответами, в режиме «до и после». В отчёте четыре числа: стоимость, точность на этом наборе, задержка по 95-му перцентилю и доля попаданий в кэш. Задержку смотрят по перцентилю, а не по среднему, потому что среднее прячет как раз те запросы, на которых каскад сходил в модель дважды, а жалуются пользователи именно на них.

Итог

  • Экономия складывается из независимых рычагов — убрать вызов, сократить токены, удешевить токен, — и слои подключают в порядке возрастания их собственной цены. Семантический кэш перед регулярным выражением стоит дороже, чем экономит.
  • Скидки перемножаются внутри одного вендора. У Anthropic пакетный режим (0,5×) с попаданием в кэш (0,1×) даёт 5% от базовой ставки входа. Наценки нанизываются по тому же правилу, у OpenAI длинный контекст вдвое, у Gemini приоритетный класс в 1,8 раза, но эти две наценки в один счёт не сложатся: они из разных прайсов.
  • Кэш-политики Anthropic, OpenAI и Google совпадают только в ставке чтения (0,1×). У Anthropic попадания не списываются с лимита токенов в минуту, у OpenAI списываются; Gemini берёт за хранение полдоллара за 1 млн токенов в час, и сутки простоя объекта окупаются примерно восемнадцатью обращениями к нему.
  • Цены ходят в обе стороны: в прайсе Gemini записано удвоение с 1 января 2027 года, включая хранение кэша. Выход при этом на всех трёх прайсах одной даты стоит впятеро-вшестеро дороже входа, потому что разрыв задаёт последовательная генерация, а не прайс-политика; расхожие «втрое-вчетверо» его занижают.
  • Каскад, посчитанный по цене одного вызова, врёт: инфляция токенов доходит до 4,25×, потому что дешёвая модель на сложной задаче входит в циклы исправлений. При эскалации черновик надо выбрасывать — передача проваленной цепочки рассуждений роняет точность верхней модели на величину до 34,8 процентного пункта.
  • Наблюдаемость не равна принудительному ограничению. Биллинговый дашборд — асинхронный сигнал, а лимит обязан проверяться перед следующим вызовом; и его надо проверять тестом, потому что бюджет в шлюзе умеет отказывать в разрешающую сторону.
  • Резать имеет смысл то, что видно по отдельности. Разрезы среда → команда → фича → клиент ставятся заранее и метаданными на шлюзе, потому что задним числом к потраченным деньгам разметку не приписать, а без разреза по фичам убыточная фича годами живёт внутри прибыльного сервиса.
  • На разобранном боте всё вместе дало $36 в сутки вместо $120, и слой, который к модели не обращается вовсе, закрыл треть обращений, не стоив при этом ничего. Общее у всех приёмов поста одно: сначала разложить поток на куски, у которых разная цена и разная ценность, и только потом что-то с ними делать. Разбор трафика по типам, разрезы счёта по фичам, тиры моделей и слои кэша — это один и тот же ход на разных уровнях.

FAQ

Насколько дешевле выходит кэширование промптов?

Чтение из кэша стоит десятую часть базовой ставки входных токенов у Anthropic, OpenAI и Google (цены на 21 августа 2026 года). Запись обходится дороже обычного входа, у Anthropic это ×1,25 при пятиминутном сроке жизни и ×2 при часовом, у OpenAI ×1,25 на моделях GPT-5.6 и новее. Экономия реальна только на стабильном префиксе, ведь кэш работает по началу промпта, и одна динамическая строка сверху обнуляет попадание целиком. Есть и порог длины. Префикс короче 512–4096 токенов (порог зависит от модели) не кэшируется вообще.

Можно ли складывать скидку за пакетную обработку со скидкой за кэш?

Да, у Anthropic это документировано прямо. Множители перемножаются с прочими модификаторами цены, включая пакетную скидку. Пакетный режим даёт 50% от стандартной цены, чтение из кэша 10%, вместе получается 5% от базовой ставки входа. Есть оговорка из той же документации, которую легко пропустить. Внутри пачки запросы выполняются асинхронно и параллельно, поэтому попадания в кэш обеспечиваются по мере возможности, и для пакетов рекомендуется часовой срок жизни кэша вместо пятиминутного.

Какой порог косинусного сходства ставить в семантическом кэше?

Рабочие ориентиры такие. 0,96 для чувствительных доменов вроде банковского и 0,92–0,94 для менее рискованных. Публичной методики, связывающей конкретный порог с долей попаданий и ложных срабатываний на произвольном корпусе, нет, поэтому порог калибруют на собственных размеченных парах. Ошибки несимметричны, промах стоит одного лишнего вызова, а ложное попадание отдаёт пользователю чужой ответ. Отдельного внимания требуют близкие по теме антонимы («Как открыть счёт?» и «Как закрыть счёт?»), которые не разводятся никаким порогом, и тут нужна либо классификация намерения, либо включение действия в ключ кэша.

Правда ли, что каскад моделей снижает затраты на 65–98%?

Верхнюю границу 98% дала работа FrugalGPT 2023 года на своём датасете и на ценах того времени. Цифра 65% ходит по разборам без опубликованной методики, проверить её не на чем. Типовым результатом ни ту, ни другую считать нельзя. Работа 2026 года по инфляции токенов показывает, что расчёт по цене одного вызова может занижать реальную стоимость сценария больше чем вдвое на трудных задачах и до 4,25 раза в пике. Дешёвая модель входит в циклы исправлений, и каждый виток заново оплачивает весь накопленный контекст. Считать экономию каскада нужно по стоимости закрытой задачи и подтверждать прогоном оценки качества, иначе это деградация с отчётом об экономии.

Можно ли передавать неудачный ответ дешёвой модели дорогой при эскалации?

Не стоит. Замер в работе «Not All Tokens Are Equal» (arXiv:2608.13571) показывает, что передача проваленной цепочки рассуждений модели верхнего тира снижает её точность на величину до 34,8 процентного пункта. Верхний тир воспринимает чужой черновик как заданный контекст, и ошибочная промежуточная посылка задаёт направление решения. Правильная схема — эскалация с чистого листа, когда черновик выбрасывается, а верхний тир получает исходную задачу.

По каким разрезам раскладывают счёт за модели?

Разрезов четыре, и ставят их снизу вверх. Среда исполнения отделяет прод от stage и CI, то есть показывает, сколько уходит мимо клиентов. Команда или сервис отвечает на вопрос, чей бюджет вырос. Идентификатор фичи даёт самое ценное — расход конкретной функции продукта, который дальше сравнивают с тем, что она приносит. Клиент или тариф нужен там, где тарифы разные, а нагрузка на них — нет. Технически всё это проставляется метаданными в заголовках запроса на модельном шлюзе и складывается рядом с официальным объектом usage из ответа API. Заводить разметку надо до прода: задним числом к уже потраченным деньгам её не приписать.

Почему бюджетного лимита в шлюзе бывает недостаточно?

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

Источники

  • Anthropic — Prompt caching · Anthropic — Batch processing — множители 1,25×/2×/0,1×, перемножение скидок с пакетным режимом, пороги минимального префикса по моделям, отсчёт срока жизни от начала запроса, вывод попаданий из-под лимита токенов в минуту, аргумент за схлопывание параллельных запросов.
  • OpenAI — Prompt caching · OpenAI — Pricing — гранулярность попаданий в 128 токенов, учёт кэшированных токенов в лимите TPM, явная граница кэша как обход проблемы с временной меткой, двойной тариф на длинный контекст.
  • Google — Gemini context caching · Google — Gemini API pricing — неявный кэш без гарантии экономии против явного с гарантией, плата за хранение $0.50 за 1 млн токенов в час, четыре класса обслуживания, плановое удвоение цен с 1 января 2027 года.
  • FrugalGPT — arXiv:2305.05176 — первоисточник каскада, три класса приёмов снижения затрат, оценка «до 98%» с привязкой к датасету и ценам 2023 года.
  • RouteLLM — arXiv:2406.18665 — маршрутизация до вызова на данных о предпочтениях, снижение затрат более чем вдвое, переносимость роутера при смене моделей в тирах.
  • Not All Tokens Are Equal: Inflation-Aware Routing — arXiv:2608.13571 — инфляция токенов до 4,25×, занижение стоимости у расчётов по одному вызову, энтропия ветвления рассуждения как досрочный сигнал сложности, падение точности на величину до 34,8 процентного пункта при передаче проваленного черновика.
  • Auditing Prompt Caching in Language Model APIs — arXiv:2502.07776 — кэш как канал утечки по времени ответа, глобальное разделение кэша между пользователями у семи провайдеров.
  • vLLM — Automatic Prefix Caching — граница применимости кэша префикса: ускоряет обработку входа и никак не влияет на генерацию выхода.
  • LiteLLM — Budgets, Rate Limits — конфигурация трёх уровней лимитов и предупреждение об отказе глобального бюджета в разрешающую сторону без базы данных.
  • Разбор инцидента: бесконечный цикл двух агентов на $47 000 — различие наблюдаемости и принудительного ограничения, услужливый верификатор, требования к терминирующему условию.

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