Ревью подорожало: куда на самом деле уходят токены
К каждому вызову модели движок добавляет свой текст до вашего кода: пресет, правила команды, гайдлайн, контракт. На нашем учебном репозитории это около 12 000 символов, и они одни и те же для правки в одну строку и для MR на сотню файлов. Дифф — переменная часть: на коммите с купоном в нём 2 847 символов. Это первое, что стоит знать о счёте: размер постоянной части задаёт ваш конфиг, а не ваш код.
Второе — что одно и то же ревью, прогнанное дважды в течение часа, стоит разных денег: первый прогон платит за запись кэша, повтор — за чтение.
Третье — что две настройки меняют счёт в разы: одна умножает его, нигде об этом не сказав, другая делит, и это не та, на которую обычно думают. Страница про все три, с числами из наших собственных прогонов.
Почти все числа на этой странице сняты на нашем публичном учебном репозитории: его можно склонировать и прогнать те же ревью у себя. На вашем проекте числа будут другими, но счёт устроен так же — из тех же частей, с тем же кэшем и теми же настройками. И смотреть на него нужно в том же месте: в блоке диагностики прогона, о нём ниже. Цены — по прайс-листам вендоров на 23 сентября 2026 года.
Что уходит в модель и сколько это весит
Замерено на учебном репозитории: пресет nestjs, шесть правил команды и свой review_prompt, допущений в dont_flag нет, полные файлы и окружение проекта включены. В сумме 12 023 символа:
| Часть входа | Размер, символов | Как оплачивается |
|---|---|---|
| инструкции пресета | 3 527 | каждый вызов, кэшируется |
| дефолтный гайдлайн | 3 056 | каждый вызов, кэшируется |
| контракт ревью | 2 838 | каждый вызов, кэшируется |
правила команды + dont_flag | 1 500 | каждый вызов, кэшируется |
| служебные строки: заголовки блоков, разделители, строка про язык, инструкция про id правила | 442 | каждый вызов, кэшируется |
собственный review_prompt команды | 429 | каждый вызов, кэшируется |
| окружение проекта: версии из манифестов | 231 | каждый вызов, кэшируется |
| директива о языке вывода (для русского пустая, для других языков 211) | 0 | каждый вызов, кэшируется |
| сам дифф | 4 158 | каждый вызов, не кэшируется никогда |
Всё, что выше диффа, между прогонами не меняется, поэтому кэшируется. Дифф не кэшируется и не может: он каждый раз другой. В токенах, которыми считает счёт, ту же картину показывает блок диагностики прогона на коммите с купоном: из 14 309 токенов входа 8 394 — постоянная часть (в строке вызова это «кэш: 8,4K»), остальные 5 915 — дифф, полные файлы и служебные строки. И постоянная часть растёт вместе с конфигом: у команды с двумя десятками правил вроде наших один блок правил весит больше пресета.
И платится это на роль, а не на прогон. Весь этот системный блок уходит генератору, потом каждому судье, потом арбитру — и каждый заново вчитывается в ваши правила и ваш пресет. Схема из генератора, двух судей и арбитра оплачивает этот текст трижды, а когда судьи расходятся и зовут арбитра — четырежды. Это и есть арифметика за фразой «разросшийся свод правил оплачивается на каждом вызове каждого прогона» из рецепта про правила. Вход самого судьи ещё тяжелее: он получает весь системный промпт генератора, плюс собственный контракт, плюс файлы целиком.
Почему одно и то же ревью стоит разных денег
Постоянная часть входа в каждом прогоне одна и та же, и именно её можно закэшировать: записать один раз, а на следующих вызовах читать за малую долю цены. У Anthropic запись стоит вдвое против ставки входа, чтение — десятую часть: в двадцать раз дешевле за тот же самый текст. Дифф не кэшируется никогда, он каждый раз другой. На коммите с купоном один и тот же прогон стоил 0,12 $ с записью кэша и 0,09 $ с чтением, и вся разница была в этом блоке. Вендоры работают с кэшем по-разному: Anthropic кэширует то, что пометит запрос, и считает запись и чтение отдельно; OpenAI-совместимые провайдеры кэшируют сами, по совпадающему началу запроса; у посредников вроде OpenRouter свои правила, и для той же модели кэш может не включиться вовсе. Проверить просто: во втором прогоне подряд в блоке диагностики у вызова должно стоять «кэш: …» размером с системный блок. Нет числа — нет и экономии.
Любая правка любого куска системного промпта из таблицы выше даёт новый ключ кэша — включая исправление одного слова в одном правиле. Поправили правило посреди дня — и первый прогон после этого снова платит за запись. Сам TTL в конфигах не живёт: ни в конфиге репозитория, ни в домашнем. Это переменная окружения LLM_CACHE_TTL, у бота в его env, у терминального reviewgate — в вашей оболочке; по умолчанию час, альтернативы — 5m и off. Если прогоны идут реже, чем раз в час, часовой кэш только вредит: каждый прогон платит за запись вдвое. Тогда либо 5m — запись 1,25×, окупается, когда следующий прогон приходит в пределах пяти минут, — либо off, по обычной ставке и без надбавки.
Две настройки, которые меняют счёт в разы
full_file_context — самый дорогой тумблер, который у вас есть. Выключен по умолчанию, и не зря. Включённый, он отдаёт генератору все изменённые файлы целиком — не только те, где нашлись замечания, — плюс их шаблоны, стили и определения того, что они импортируют. У этого свой бюджет в 200 000 символов, и он не заменяет бюджет диффа в 400 000: они складываются.
Наш первый прогон хука на учебном репозитории — ровно пример того, как это выглядит, когда идёт не так: дифф в 37 строк по четырём файлам стоил 0,33 $, потому что pnpm-lock.yaml уехал в контекст целиком — 143 962 байта из 146 179, то есть 98,5% контекста файлов, который получила модель. С lock-файлом в ignore тот же коммит обошёлся в 0,01 $; разбор этого случая — в лесенке хуков. Поэтому порядок важен: кладите lock-файлы, снапшоты, генерируемый код и фикстуры в ignore до того, как включите полный контекст. Иначе вы платите за чтение файлов, в которых нет ни одной строки, написанной человеком.
Из настроек файл до вызова модели убирают только две: список ignore и маркер reviewgate-ignore-file с причиной внутри самого файла. Всё остальное — dont_flag, min_severity, правила — это текст в промпте или обработка ответа, и дешевле прогон от них не становится. Сам движок вдобавок отбрасывает удалённые файлы, пустые диффы и бинарники, но это не ваши рычаги.
Включите две настройки, выключенные по умолчанию
# .reviewgate/config.yml
diagnostics: true # the 🔬 block: every call, its tokens, its duration
cost:
show: true # the price line under the summary
currency: "$"
models: # a price for EVERY model of the run, or no amount is shown
# $ per 1M tokens, vendors' price lists as of 2026-09-23
claude-sonnet-5: { input: 2, output: 10 }
claude-opus-5: { input: 5, output: 25 }
deepseek-v4-flash: { input: 0.30, output: 1.20 } # peak rate; off-peak is half
ignore: # one of the two levers that remove a file BEFORE
- "pnpm-lock.yaml" # the model is called; the other is the in-file marker
- "**/*.generated.ts"
- "**/__snapshots__/**"diagnostics добавляет в отчёт блок, где виден каждый вызов модели: роль, модель, токены входа и выхода и сколько входа пришло из кэша, сколько заняло, сколько замечаний выдал генератор, сколько судья снял или понизил и почему. Без него прогон — чёрный ящик с числом в конце.
cost.show добавляет строку стоимости — и ему нужно ещё одно: цены для каждой модели прогона. Пропустите одну — и денег вы не увидите: в терминале строки не будет, в сводке MR она останется без суммы, с именем модели, для которой нет цены. Это не гипотеза: три наших замерочных прогона в терминале не напечатали цену, потому что DeepSeek не было в прайсе, и отчёт был прав, промолчав, вместо того чтобы показать число, считающее часть прогона.
Два случая, когда прогон вдруг дорожает
Ответ упёрся в потолок вывода. Модель оборвали на полуслове, прогон повторил вызов на пониженном усилии, и оба вызова в вашем счёте — один из них впустую. Отчёт говорит об этом прямо. На нашем собственном репозитории, на ветке из трёх страниц рецептов, это стоило 0,94 $ вместо 0,52 $: тогда потолок по умолчанию был 16 000 токенов, сейчас 64 000, но своей рукой его всё ещё можно опустить. У OpenAI-совместимых провайдеров повтора нет: обрезанный ответ — сразу ошибка прогона.
Reasoning-модель оказалась многословной. Один и тот же вход на DeepSeek v4-flash дал 22,7K, 6,8K и 19,4K токенов выхода за три прогона — разброс втрое, по причинам на стороне модели, а не вашей. Два из этих трёх не влезли бы в прежний потолок 16 000 токенов. Если в вашей схеме есть reasoning-модель, её потолку нужен запас, которого не нужно обычной.
Если нужен жёсткий предел, есть потолок трат в токенах на прогон, выключенный по умолчанию: у бота это переменная LLM_BUDGET_TOKENS, у терминального reviewgate — ключ budget_tokens в блоке llm домашнего конфига. Он не обрывает вас молча, а пишет строки над списком замечаний, как остальные пометки. На 80% — обычная строка: «расход бэкенда anthropic приближается к лимиту прогона: 41 000 из 50 000 токенов». На 100% — с ⚠️: «лимит прогона для бэкенда anthropic превышен: 52 000 токенов при лимите 50 000 — проверьте раскладку ролей и размер диффа». На 300% прогон останавливается на ближайшей контрольной точке: «🛑 ревью остановлено по бюджету … Не выполнено: судейство. Опубликовано то, что успело собраться».
Дальше
- Кто ищет, а кто судит— во что обходится и что добавляет каждая роль
- Правила, которые линтер проверить не может— блок, который оплачивается на каждом вызове
- Справочник конфига— cost, diagnostics, ignore