Токен из кеша дешевле обычного в разы. Цены на 27 августа 2026:
провайдер |
обычный |
из кеша |
разница |
|---|---|---|---|
$0.44 / 1M |
$0.014 / 1M |
31x |
|
$4.00 / 1M |
$0.40 / 1M |
10x |
|
$3.00 / 1M |
$0.30 / 1M |
10x |
У DeepSeek и OpenAI кеш работает автоматически. У Anthropic его надо явно попросить:
client.messages.create( model="claude-sonnet-5", cache_control={"type": "ephemeral"}, # без этого кеша нет вовсе system=POLICY, messages=messages, )
Но там, где кеш включён, ломается он одинаково. Одной строкой.
Механизм
Кеш — префиксное дерево по токенам. Совпали первые N токенов с предыдущим запросом — эти N пришли из кеша. На первом расхождении кеш кончается, и весь остаток промпта идёт по полной цене.
system = f"Current time: {datetime.now()}.\n{POLICY}\n{KNOWLEDGE_BASE}"
Время меняется на каждом вызове → префикс расходится на четвёртом слове → весь промпт, все две тысячи токенов политик и базы знаний, каждый раз идёт по полной цене. Хотя не менялся ни разу.
Этот баг видно глазами, и чинится он без всяких инструментов: убрать динамику из начала. Дальше — про те, которые глазами не видно.
Замер
Support-агент, системный блок ~1200 токенов (важно: DeepSeek включает кеш примерно от 1024), четыре вопроса подряд. Две сессии с одинаковым содержимым и разной сборкой:
live-broken— время перед статикойlive-fixed— время после статики
Живой DeepSeek, восемь вызовов, сырой вывод:
live-broken prompt= 1177 cache_hit= 0 live-broken prompt= 1180 cache_hit= 0 live-broken prompt= 1179 cache_hit= 0 live-broken prompt= 1178 cache_hit= 0 live-fixed prompt= 1180 cache_hit= 0 live-fixed prompt= 1183 cache_hit= 1152 live-fixed prompt= 1182 cache_hit= 1152 live-fixed prompt= 1181 cache_hit= 1152
Ноль попаданий против 1152 из ~1180. Это намеренно худший случай: время первой строкой, кеш не включается вообще. На реальных промптах динамика сидит глубже — в бенчмарке ниже бейзлайн 49–66%, а не 0%.
Агент с промптом 1200 токенов и 10 000 вызовов в день:
провайдер |
без кеша |
с кешем 98% |
разница |
|---|---|---|---|
DeepSeek v4-flash |
$158 / мес |
$8 / мес |
−$150 |
OpenAI gpt-5.6-sol |
$1 440 / мес |
$173 / мес |
−$1 267 |
Anthropic (Sonnet) |
$1 080 / мес |
$130 / мес |
−$950 |
Баги, которые не видно глазами
Если у вас datetime.now() в первой строке — вы уже всё поняли и инструмент вам не нужен. Он нужен для остального. Все четыре случая ниже — общий паттерн для любого провайдера с префиксным кешем, пример с Anthropic просто самый наглядный:
Схемы инструментов. У Anthropic кеш покрывает tools, system, messages именно в этом порядке — схемы лежат в самом начале префикса, до системного промпта. Собираете их из словаря с плавающим порядком ключей — ломаете кеш в первой позиции, и в коде промпта этого не видно вообще.
База знаний из set(). Содержимое то же, порядок между процессами другой.
Префикс совпал, а кеша нет. Отдельный класс: сборка правильная, cache_read_tokens == 0. Значит TTL истёк или upstream не кеширует — чинится маршрутизацией, а не промптом.
Шаблон, который рендерится по-разному под нагрузкой. Условный блок, попадающий в промпт раз в сто вызовов.
Общее у всех четырёх: промпт большой, изменение маленькое, глазами не найти.
Инструмент
prefixcash читает поля usage, которые провайдер и так возвращает. Никаких запросов к моделям, ничего не уходит с машины.

Формат входа — JSONL, по строке на вызов. Нужны usage (как отдал провайдер) и messages:
resp = client.chat.completions.create(model=MODEL, messages=messages) log.write(json.dumps({ "model": resp.model, "session_id": session_id, "usage": resp.usage.model_dump(), # сырой usage, парсится как есть "messages": messages, }) + "\n")
Пять строк поверх любого OpenAI-совместимого SDK. С LiteLLM — три: litellm.callbacks = [PrefixCashCallback(file="metrics.jsonl")].
prefixcash diagnose --file calls.jsonl --session live-broken

cacheable prefix 1%, общий префикс — 8 слов, виновник — dynamic_time.
Зелёное на карте приходит из кеша. Красное 09:00:11 рвёт префикс. Оранжевое — всё, что после разрыва. Текст там совпадает дословно между вызовами, но кеш его не отдаст: он смотрит на префикс, а префикс сломан восемью словами раньше. Цена ошибки — не одно слово, а весь хвост за ним.
Что он умеет распознавать: iso_datetime, datetime, date, time, uuid, hex_token, number, email, url_query, placeholder ({var}). Плюс два, которые ловят то, чего в списке нет: high_entropy — сгенерированные строки по энтропии Шеннона, и content_change — любое расхождение, не подошедшее ни под один шаблон. То есть виновник будет назван и позиция показана, даже если ваш случай никто не предусматривал.
Переносим время в хвост, больше ничего не трогаем:

cacheable prefix 98%, общий префикс — 730 слов вместо восьми.
Проверьте на своих данных
Если вы пользуетесь Claude Code — лог уже на диске, интеграция не нужна, это буквально две минуты:
pip install prefixcash python -m examples.import_claude_code --out cc.jsonl prefixcash report --file cc.jsonl
Адаптер переносит только поля usage, тексты промптов не читаются — это видно в examples/import_claude_code.py, там 70 строк.
Если нет — честно, это не две минуты: пять строк логирования выше, потом дождаться трафика. Сколько ждать: diagnose сравнивает соседние вызовы, поэтому минимум два вызова в одной сессии — уже будет вердикт. Не нужны ни тысячи вызовов, ни день накопления; хватит одного живого диалога из двух реплик.
Сетевых запросов инструмент не делает вообще: считает по полям usage, которые уже лежат в вашем логе. Если ставите на боевые данные — смотреть надо src/prefixcash/integrations/importers.py, это единственное место, где читается ваш файл.
Вот что получилось у меня:

97 877 вызовов, 32,5 млрд токенов, 98% из кеша. Claude Code собран правильно: статический префикс не двигается, диалог дописывается в хвост.
Две оговорки. base $ — контрфактика по прайсу pay-as-you-go, а я на подписке: это цена механизма, а не возврат на карту. И запись в кеш у Anthropic стоит дороже обычного токена (1.25x для 5-минутного TTL, 2x для часового), prefixcash этого пока не моделирует и завышает экономию примерно на 2%.
Бенчмарк
Пять сценариев, живые вызовы к DeepSeek, две сборки на каждый:
сценарий |
что ломало |
было |
стало |
|---|---|---|---|
время в префиксе |
|
65.2% |
97.8% |
UUID в префиксе |
|
65.7% |
98.3% |
перестановка базы знаний |
|
49.4% |
98.7% |
короткий промпт |
— |
0.0% |
0.0% |
промпт реального фреймворка |
|
50.3% |
99.0% |
Четвёртая строка — отрицательный контроль. На коротком промпте кеш не включается, никакой фикс не даёт ничего, инструмент пишет NO GAIN. Если бы там стояло красивое число — не верьте остальным четырём.
MIT, Python 3.11+, 42 теста, CI на каждый push — github.com/Isk4R1oT/prefixcash
Проверьте одну вещь, это десять секунд: что стоит первой строкой вашего системного промпта. Если оно меняется от вызова к вызову — вы платите за весь промпт полную цену, каждый раз.
kif1r4ek
Статья очень крутая, только вопрос, есть ли практический способ закешировать статическую часть до этой динамической вставки, сохранив её в начале?
Isk4R1oT Автор
Спасибо! Короткий ответ: нет. Кеш матчится строго слева направо от нулевого токена — «пропустить» динамическую вставку и продолжить совпадение он не умеет. Всё до точки расхождения и есть весь кешируемый префикс. cache_control у Anthropic не помогает: он помечает, где префикс заканчивается, а не начинается. Что работает, если переставить нельзя:- Огрубить. Нужен день, а не секунда — 2026-08-28 вместо 2026-08-28 14:03:11. Префикс живёт сутки вместо одного вызова.- Убрать из текста. session_id и подобное нужны телеметрии, а не модели — им место в метаданных запроса.- Отдельным сообщением. Если значение нужно модели — не в system, а в первое user-сообщение.И стоит проверить саму посылку: «должно быть в начале» почти всегда оказывается привычкой. Модель видит весь контекст, и «сейчас 14:03» работает из хвоста не хуже.