Перейти к содержанию
Публикация AiManual

Prompt caching в Amazon Bedrock: как снизить стоимость и задержку при работе с LLM

Разбираем prompt caching в Amazon Bedrock: как расставить cachePoint в Converse API, сколько стоит запись и чтение кэша, какие есть пороги токенов и TTL. Шесть

Коротко

Что будет в материале

  1. 01

    Что такое prompt caching в Amazon Bedrock и зачем он нужен

  2. 02

    Поддерживаемые модели и минимальные пороги токенов

  3. 03

    Как использовать cachePoint в Converse API: пошаговое руководство

  4. 04

    Шесть практических сценариев использования prompt caching

Amazon Bedrock кэширует повторяющуюся часть запроса и возвращает её из готового состояния. Если системный промпт, документ или список инструментов не меняется от запроса к запросу, платформа обрабатывает этот блок один раз, а дальше берёт из кэша: чтение тарифицируется примерно в 10 раз дешевле обычных входных токенов, то есть со скидкой до 90%, и время до первого токена падает, потому что префикс не пересчитывается заново.

Работает это на уровне префикса. Модель читает вход слева направо, и кэшируется всё, что стоит до маркера cachePoint. Ключ кэша - сам контент, поэтому попадание возможно только при полном совпадении префикса, с учётом порядка блоков и описаний инструментов.

Кэш живёт 5 минут по умолчанию, привязан к аккаунту AWS, региону и конкретной модели, а минимальный объём кэшируемого префикса измеряется тысячей с небольшим токенов. Ниже - как расставить cachePoint, сколько это стоит, где механизм молчит и как посчитать окупаемость на своём трафике.

Что такое prompt caching в Amazon Bedrock и зачем он нужен

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

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

Как работает cachePoint: краткий обзор

cachePoint - не отдельный вызов API, а блок внутри структуры запроса Converse API: {"cachePoint": {"type": "default"}}. Его можно поставить в трёх местах: в массиве system, в content сообщения и в списке tools. Всё, что расположено до маркера, образует кэшируемый префикс. Первый запрос с таким префиксом записывает кэш, следующие читают его, пока не истёк TTL.

Проверить попадание можно по ответу: поля usage.cacheWriteInputTokens и usage.cacheReadInputTokens показывают, сколько токенов записано и сколько прочитано из кэша. Если второе поле нулевое при повторном одинаковом запросе, значит префикс чем-то отличается или кэш уже остыл. Кэширование работает и в потоковом режиме ConverseStream, счётчики приходят в финальном событии метаданных.

Экономический эффект: сколько можно сэкономить

Тарифы устроены так: запись в кэш дороже обычного входного токена, чтение - в разы дешевле. В ценах Bedrock для моделей Anthropic Claude запись стоит 1,25 от цены входного токена при TTL 5 минут, чтение - 0,1 от неё. При цене входа $3 за миллион токенов чтение обойдётся в $0,3 за миллион.

Считаем на примере. Системный промпт на 3 000 токенов уходит в модель 1 000 раз в час. Без кэша это 3 миллиона входных токенов, около $9. С кэшем: одна запись на $0,011 и 999 чтений по 3 000 токенов на $0,90. Итого примерно $0,91 против $9, то есть разница почти в десять раз при неизменном промпте.

Точные тарифы зависят от модели и региона, их стоит сверять в актуальной таблице цен Bedrock. Пороги окупаемости и подробный расчёт на другом объёме контекста - в разделе про тарифы ниже.

Поддерживаемые модели и минимальные пороги токенов

Кэширование доступно не всем моделям каталога. На момент публикации cachePoint поддерживают линейка Anthropic Claude (3.5 Haiku, 3.5 Sonnet v2, 3.7 Sonnet, Sonnet 4, Opus 4 и более поздние версии) и семейство Amazon Nova (Micro, Lite, Pro). Как это выглядит на свежих версиях Claude и какие региональные ограничения есть у AWS-контуров, разобрано в материале про Claude Fable 5.1 в AWS и экономию на чтении кэша.

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

СемействоМинимальный кэшируемый префиксЧекпоинтов в запросе
Anthropic Claude 3.5 Haiku, 3.5 Sonnet v2, 3.7 Sonnet1 024 токенадо 4
Anthropic Claude Sonnet 4, Opus 4 и более поздние1 024 токенадо 4
Amazon Nova Micro, Lite, Pro1 024 токена и выше, зависит от версиименьше, чем у Claude

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

Как использовать cachePoint в Converse API: пошаговое руководство

Структура запроса в Converse API одинакова для всех моделей: метод converse принимает modelId, system, messages, toolConfig и inferenceConfig. cachePoint добавляется как обычный блок в массивы system, content или tools. Порядок и расположение маркеров определяют, что попадёт в кэш.

import boto3

client = boto3.client("bedrock-runtime", region_name="us-east-1")

LONG_SYSTEM = open("system_prompt.txt", encoding="utf-8").read()
DOC = open("policy.txt", encoding="utf-8").read()

response = client.converse(
    modelId="anthropic.claude-3-7-sonnet-20250219-v1:0",
    system=[
        {"text": LONG_SYSTEM},
        {"cachePoint": {"type": "default"}},
    ],
    messages=[
        {
            "role": "user",
            "content": [
                {"text": DOC},
                {"cachePoint": {"type": "default"}},
                {"text": "Сколько дней отпуска положено сотруднику после трёх лет работы?"},
            ],
        }
    ],
    inferenceConfig={"maxTokens": 512, "temperature": 0},
)

print(response["usage"])

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

Кэширование системного промпта

Самый простой сценарий: фиксированная инструкция ассистента на 1 000 токенов и больше. Маркер ставится после текста:

system = [
    {"text": LONG_SYSTEM},
    {"cachePoint": {"type": "default"}},
]

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

Кэширование содержимого сообщений

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

messages = [
    {
        "role": "user",
        "content": [
            {"text": DOC},
            {"cachePoint": {"type": "default"}},
            {"text": USER_QUESTION},
        ],
    }
]

Если поставить cachePoint после вопроса, каждый новый вопрос будет ломать префикс и попаданий не случится. В агентных циклах, где один и тот же контекст пересчитывается десятки раз за сессию, эффект особенно заметен. Похожую задачу на локальном железе решают кэшем KV-состояний: пример разбора такого подхода для агентных пайплайнов есть в статье про форк llama.cpp с SSD-кэшированием KV для агентных пайплайнов.

Кэширование определений инструментов

Список инструментов у агента с десятком функций занимает тысячи токенов и уходит в модель при каждом вызове. Маркер ставится в массив tools после последнего описания:

tool_config = {
    "tools": [
        {"toolSpec": {...}},
        {"toolSpec": {...}},
        {"cachePoint": {"type": "default"}},
    ]
}

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

Шесть практических сценариев использования prompt caching

  1. Кэширование содержимого сообщений: документы и база знаний, которые повторяются в диалоге.
  2. Кэширование системных промптов: фиксированные инструкции агентов и правила.
  3. Кэширование определений инструментов: большие наборы функций при function calling.
  4. Смешанные TTL: короткий кэш для истории диалога, длинный для справочника.
  5. Изоляция арендаторов: общий префикс для всех клиентов, персональные данные после маркера.
  6. Интеграция с LangChain: cachePoint в сообщениях ChatBedrockConverse.

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

Смешанные TTL: как комбинировать разные времена жизни

Converse API принимает единственный тип маркера, cachePoint с type: default, и его TTL равен 5 минутам. Задать разные TTL для двух чекпоинтов в одном запросе нельзя. При этом каждое попадание в кэш продлевает жизнь блока, поэтому при потоке запросов раз в минуту кэш фактически не остывает.

Для части моделей Anthropic в Bedrock встречается вариант с часовым TTL: в низкоуровневом запросе вместо cachePoint указывают cache_control с полем ttl. Запись в такой кэш тарифицируется дороже, чтение остаётся около 0,1 от цены входа. Поддержку и точный формат проверяйте в документации для своей версии модели.

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

Изоляция арендаторов: как не смешивать кэш разных клиентов

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

Отсюда два практических правила. Первое: tenant-specific данные держите после cachePoint, в кэшируемой части оставляйте только то, что совпадает у всех. Второе: учёт расходов ведите на уровне аккаунта и приложения, потому что usage отдаёт счётчики чтения и записи по конкретному запросу, без разбивки по пользователям. Для распределения затрат подойдут application inference profiles с тегами, для жёсткой изоляции - отдельные аккаунты или регионы.

Интеграция с LangChain

В langchain-aws для ChatBedrockConverse можно передавать те же content blocks, что и в Converse API, включая cachePoint. Иллюстративный пример:

from langchain_aws import ChatBedrockConverse
from langchain_core.messages import SystemMessage, HumanMessage

llm = ChatBedrockConverse(
    model="anthropic.claude-3-7-sonnet-20250219-v1:0",
    region_name="us-east-1",
)

messages = [
    SystemMessage(content=[
        {"type": "text", "text": LONG_SYSTEM},
        {"cachePoint": {"type": "default"}},
    ]),
    HumanMessage(content=[
        {"type": "text", "text": DOC},
        {"cachePoint": {"type": "default"}},
        {"type": "text", "text": "Сделай выжимку по пункту 4.2"},
    ]),
]

answer = llm.invoke(messages)
print(answer.usage_metadata)

Ограничение по версии: поддержка cachePoint в langchain-aws появилась не сразу, в старых релизах блок может игнорироваться или вызывать ошибку валидации. Проверяйте версию библиотеки и формат блоков под неё. И помните, что цепочки LangChain не расставляют маркеры сами: если промпт собирается шаблоном, cachePoint добавляется вручную, иначе кэша не будет.

Тарифы на запись и чтение кэша: как считать выгоду

Арифметика простая. Запись дороже входа, чтение дешевле. Для Claude в Bedrock: запись 1,25 от цены входного токена при TTL 5 минут, чтение 0,1 от цены входа. Часовой кэш на запись стоит дороже, чтение тарифицируется так же. Формула экономии: объём префикса, умноженный на число повторных чтений по цене кэша, против того же объёма по цене обычного входа, минус одна запись.

Статья расходаРасчётСумма
Без кэша100 запросов × 10 000 токенов × $3 / 1M$3,00
Запись в кэш (один раз)10 000 токенов × $3,75 / 1M$0,0375
Чтение из кэша99 запросов × 10 000 токенов × $0,3 / 1M$0,297
Итого с кэшемзапись плюс чтение$0,33
Экономияпротив $3,00около 89%

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

Ограничения и подводные камни prompt caching

Список ограничений стоит держать перед глазами до того, как вы начнёте считать сэкономленные деньги.

  • Порог токенов: префикс короче минимума не кэшируется, cachePoint просто игнорируется без ошибки.
  • TTL: 5 минут по умолчанию, при истечении запись выполняется заново.
  • Неизменность префикса: подстановка текущей даты, имени пользователя, случайный порядок инструментов или динамический few-shot обнуляют попадания.
  • Область действия: кэш не переносится между регионами, аккаунтами и моделями, смена версии модели обнуляет его.
  • Выходные токены не кэшируются, экономия касается только входа.
  • Короткие дешёвые запросы: накладные расходы на запись могут перевесить выигрыш.

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

Различия между Converse API и InvokeModel API

Converse API даёт единый формат для всех моделей: блок cachePoint в system, content или tools. Смена модели не меняет код, поэтому для новых проектов это путь по умолчанию. InvokeModel API работает с телом запроса конкретной модели: у Claude это блоки cache_control с type: ephemeral в системной части, сообщениях и инструментах, у других семейств формат отличается, а часть моделей кэширование через этот API вообще не поддерживает.

Плюс InvokeModel в доступе к расширенным настройкам, например к более длинному TTL у части моделей. Минус в том, что код становится зависимым от модели: при переезде на другое семейство его придётся переписывать. Если переносимость важнее тонкой настройки, берите Converse, если нужен максимум контроля, читайте спецификацию конкретной модели.

Когда кэширование не сработает или будет невыгодным

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

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

Итог: стоит ли использовать prompt caching в вашем проекте

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

Начните с пилота на одном сценарии: замерьте cacheWriteInputTokens и cacheReadInputTokens, посчитайте фактическую стоимость и задержку до и после на реальном трафике, и только потом раскатывайте приём на остальные сервисы. Поддержка моделей и тарифы меняются, так что раз в квартал сверяйтесь с документацией Bedrock.

Для локального стека логика похожая, но реализация другая: там кэшируют KV-состояния и умеют держать их на диске между сессиями, как в разборе персистентного SSD-кэша KV для быстрого warm start. Облачный кэш живёт минуты и привязан к аккаунту, локальный переживает перезапуск процесса, но требует своего железа. Что дешевле в вашем случае, решает объём трафика и стоимость аренды GPU.

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