Одна строка в системном промпте отключала кеш целиком. 1% против 98% на живых вызовах. anthropic.. anthropic. deepseek.. anthropic. deepseek. hit rate.. anthropic. deepseek. hit rate. llm.. anthropic. deepseek. hit rate. llm. Open source.. anthropic. deepseek. hit rate. llm. Open source. openai.. anthropic. deepseek. hit rate. llm. Open source. openai. prefix cache.. anthropic. deepseek. hit rate. llm. Open source. openai. prefix cache. prompt caching.. anthropic. deepseek. hit rate. llm. Open source. openai. prefix cache. prompt caching. python.. anthropic. deepseek. hit rate. llm. Open source. openai. prefix cache. prompt caching. python. искусственный интеллект.. anthropic. deepseek. hit rate. llm. Open source. openai. prefix cache. prompt caching. python. искусственный интеллект. Ненормальное программирование.. anthropic. deepseek. hit rate. llm. Open source. openai. prefix cache. prompt caching. python. искусственный интеллект. Ненормальное программирование. оптимизация затрат.

Токен из кеша дешевле обычного в разы. Цены на 27 августа 2026:

провайдер

обычный

из кеша

разница

DeepSeek v4-flash

$0.44 / 1M

$0.014 / 1M

31x

OpenAI gpt-5.6-sol

$4.00 / 1M

$0.40 / 1M

10x

Anthropic (Sonnet)

$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, которые провайдер и так возвращает. Никаких запросов к моделям, ничего не уходит с машины.

что умеет prefixcash

что умеет prefixcash

Формат входа — 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
diagnose: cacheable prefix 1 процент

diagnose: cacheable prefix 1 процент

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 — любое расхождение, не подошедшее ни под один шаблон. То есть виновник будет назван и позиция показана, даже если ваш случай никто не предусматривал.

Переносим время в хвост, больше ничего не трогаем:

diagnose после пересборки: cacheable prefix 98 процентов

diagnose после пересборки: cacheable prefix 98 процентов

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 вызовов, хит-рейт 98 процентов

97 877 вызовов, хит-рейт 98 процентов

97 877 вызовов, 32,5 млрд токенов, 98% из кеша. Claude Code собран правильно: статический префикс не двигается, диалог дописывается в хвост.

Две оговорки. base $ — контрфактика по прайсу pay-as-you-go, а я на подписке: это цена механизма, а не возврат на карту. И запись в кеш у Anthropic стоит дороже обычного токена (1.25x для 5-минутного TTL, 2x для часового), prefixcash этого пока не моделирует и завышает экономию примерно на 2%.


Бенчмарк

Пять сценариев, живые вызовы к DeepSeek, две сборки на каждый:

сценарий

что ломало

было

стало

время в префиксе

dynamic_iso_datetime

65.2%

97.8%

UUID в префиксе

dynamic_uuid

65.7%

98.3%

перестановка базы знаний

content_change

49.4%

98.7%

короткий промпт

0.0%

0.0%

промпт реального фреймворка

dynamic_iso_datetime

50.3%

99.0%

Четвёртая строка — отрицательный контроль. На коротком промпте кеш не включается, никакой фикс не даёт ничего, инструмент пишет NO GAIN. Если бы там стояло красивое число — не верьте остальным четырём.


MIT, Python 3.11+, 42 теста, CI на каждый push — github.com/Isk4R1oT/prefixcash

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

Автор: Isk4R1oT

Источник