Короткий промпт дороже длинного: разбор prefix cache в LLM-агентах | AiManual
AiManual Logo Ai / Manual.
12 Май 2026 Гайд

Почему короткий промпт может быть дороже длинного: разбор prefix cache в LLM-агентах

Разбираем парадокс: ужимая промпт для агентов, вы теряете преимущества prefix caching. Рассказываю, как логировать кэш, не стрелять себе в ногу и экономить до 9

Вы старательно вырезали каждую лишнюю запятую из system prompt, сократили инструкцию до двух абзацев, а счет за API все равно вырос вдвое. Поздравляю — вы только что наступили на грабли prefix cache. В мире LLM-агентов парадокс работает безотказно: чем короче промпт, тем выше цена за токен (если вы не знаете, как кэш устроен).

Агенты — это не однократный запрос. Это конвейер: один system prompt, десятки вызовов с разными user-запросами. И каждый вызов может либо выстрелить в кэш, либо пересчитать всё с нуля. Короткий промпт — почти всегда промах. Длинный, но стабильный — попадание в яблочко. Почему так вышло и как это пофиксить? Дальше без воды.

Как работает prefix cache (и почему вы о нем забываете)

Когда вы шлете запрос к LLM, провайдер (OpenAI, Anthropic, Google, либо ваш vLLM/SGLang) считает attention для всех токенов. Но если начало запроса повторяется от вызова к вызову, умные системы сохраняют промежуточные состояния (KV-кэш) именно для этих первых токенов. Это и есть prefix caching.

Anthropic, например, официально заявляет скидку до 90% на токены, попавшие в кэш. OpenAI делает то же самое — в API есть заголовок x-cache-status. У локальных решений вроде vLLM и SGLang с IndexCache выигрыш по времени — до 1.8x (про это я писал в статье про IndexCache). Но весь профит летит в трубу, если префикс каждый раз новый.

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

Короткий промпт = гарантированный miss

Допустим, вы написали агента для поиска документов. System prompt — 50 токенов: “Ты ассистент. Отвечай кратко.”. User-запрос каждый раз новый — “Найди контракт с Ивановым”, “Покажи счет за май”. Префикс из 50 токенов — мизер. Даже если он одинаковый, экономия смешная. Но чаще всего вы еще и меняете system prompt под каждую задачу: “сейчас ты юрист”, “сейчас ты бухгалтер”. Поздравляю, кэш сброшен.

Теперь посмотрим на альтернативу: длинный system prompt на 4000 токенов, который описывает все возможные сценарии, список инструментов (allowed_tools), правила форматирования, примеры. Он неизменен на протяжении всей сессии. User-запросы добавляются в конец. Первый вызов платит за все 4000 токенов system prompt + user часть. Второй вызов — кэш уже содержит эти 4000 токенов. Вы платите только за приращение (user input + новый вывод). Effective cost падает в разы. Подробный разбор формулы effective cost я приводил в статье Экономика AI.

“Укорачивание system prompt — это стрельба по ногам. Вы жертвуете стабильным префиксом ради иллюзии экономии.”

Что реально ломает prefix cache

Самые частые грабли в агентных циклах:

  • Динамический system prompt — добавление timestamp, номера итерации, случайных id. Даже один отличающийся токен в начале — и кэш мимо.
  • Изменение списка инструментов — если вы подмешиваете разные наборы функций в каждый вызов, префикс после system prompt меняется. Фиксированный allowed_tools — ваш лучший друг.
  • Перестановка сообщений — агенты часто пихают историю диалога между system и user. Это сдвигает префикс. Идеально: system -> (история) -> user. Если история стабильна по длине, то кэш работает для system и части истории.
  • Использование разных моделей или эндпоинтов — кэш привязан к конкретному инстансу. Если вы балансируете между разными vLLM-серверами, cache hit упадет.

Как получить максимальный cache hit: пошаговый гайд

1Выделите статический суперпрефикс

System prompt должен быть максимально длинным и неизменным. Вынесите туда все, что не зависит от запроса: описание личности, общие инструкции, список всех возможных инструментов (даже тех, что не понадобятся в данном вызове). Да, это увеличит первый запрос, но каждый следующий будет почти бесплатным. Сравните: заплатить $0.01 за первый и $0.001 за остальные 100 вызовов намного выгоднее, чем $0.003 каждый раз без кэша.

2Логируйте cache hit rate

Без цифр вы слепы. Каждый LLM-провайдер отдает метрики кэша. В OpenAI смотрите заголовок x-cache-status (значения HIT / MISS). Anthropic — x-request-id и отдельный биллинг с разбивкой cache_creation / cache_read. Для vLLM/SGLang — эндпоинт /metrics с гистограммой vllm:cache_hit_rate. Соберите это в логи агента и мониторьте в дашборде. Если cache hit rate ниже 80% — ищите, где меняется префикс.

3Используйте инфраструктуру с поддержкой prefix caching

Если вы селф-хостите модели, выбирайте движки с развитым кэшированием. vLLM и SGLang поддерживают автоматический prefix cache. А с IndexCache (описано в статье IndexCache для DeepSeek-V3.2) можно дополнительно кэшировать attention индексы, получая до 1.8x ускорения. Llama.cpp тоже умеет, но там надо явно включать --cache-type — без этого кэш не работает (читайте мою статью-расследование).

4Фиксируйте порядок сообщений

Структура запроса должна быть железобетонной: [system, user] или [system, history, user]. Никаких вставок дополнительных сообщений между system и user, если вы ждете cache hit. Если агент динамически добавляет контекст — делайте это строго после первого user-запроса. Префикс (system + начало) остается стабильным.

Пример: до и после (и как выглядит код)

Плохой подход (короткий динамичный system prompt):

system_prompt = "Ты помощник. Сегодня " + datetime.now().strftime("%d.%m.%Y")
response = client.messages.create(
    model="claude-sonnet-4-20250512",
    system=system_prompt,
    messages=[{"role":"user", "content":"Найди документ"}]
)

Каждый вызов — новый timestamp, cache miss, полная стоимость.

Хороший подход (длинный статический префикс):

SYSTEM_PROMPT = """Ты помощник с фиксированным набором инструментов:
- search_docs
- get_contract
- calculate_summary
Инструкции: ..."""  # 3000 токенов

response = client.messages.create(
    model="claude-sonnet-4-20250512",
    system=SYSTEM_PROMPT,
    messages=[{"role":"user", "content":"Найди документ"}]
)
# Проверяем заголовки: x-cache-status: HIT

Первый вызов создает кэш, все последующие — HIT. Экономия на каждом вызове — стоимость ~3000 токенов system prompt.

А что насчет allowed_tools?

Список инструментов — часть префикса. Если вы каждый раз передаете разный набор функций, префикс меняется. Выход: передавайте все доступные функции всегда, а логику выбора делайте на стороне агента (с помощью дополнительной LLM-проверки или триггеров). Да, это увеличит размер запроса, но кэш будет бить постоянно. Или используйте единый список functions с параметрами, которые модель может игнорировать.

Еще один трюк: если вы используете OpenAI, попробуйте задавать tool_choice как auto — это не меняет префикс. Ручное принуждение конкретного инструмента (tool_choice: {"type":"function","function":{"name":"search"}}) — меняет. Статья про локальные LLM и длинные инструкции хорошо иллюстрирует, как даже небольшое изменение ломает поведение.

Ошибки, которые я видел в продакшене

  • Миксование system prompt внутри агентского цикла. Одна команда писала в system: “Сейчас ты выполняешь шаг {i} из {n}”. При i=1 кэш создавался, при i=2 — уже miss. Просто вынесите номер шага в user-запрос или в отдельное сообщение.
  • Добавление случайных инструкций для каждого вызова. “Не используй эмодзи”, “Ответь на русском” — это должно быть в system, а не в user. Иначе каждый новый user-запрос начинает различаться с первого токена.
  • Игнорирование кэша при тестировании. Разработчики меряют latency на одном запросе и удивляются, что в проде оно выше. Cache прогревается только после повторных вызовов. Тестируйте с прогревом.

Совет: если у вас агент с большим количеством пользователей, рассмотрите per-user cache. Некоторые провайдеры (Anthropic, vLLM) позволяют привязать кэш к сессии или user_id. Тогда каждая сессия получает свой прогретый префикс.

Бонус: cache-aware prefill–decode disaggregation

Для тех, кто гонится за скоростью: техника разделения prefill и decode с учетом кэша может ускорить обработку длинного контекста на 40%. Суть в том, что prefill (вычисление attention для префикса) можно выполнить один раз, а decode (генерацию) — много раз на разных запросах. Детали — в статье Cache-aware prefill–decode disaggregation. Эта техника особенно полезна для локальных инстансов vLLM, когда у вас много параллельных запросов с общим префиксом.

Коротко: не бойтесь длинных промптов. Бойтесь нестабильных. А еще бойтесь отсутствия метрик. Настройте логирование cache hit rate, и вы увидите, как ваши $100 за токены превращаются в $10.

Подписаться на канал