Статья

Поменял effort — куда делся кэш?

Effort выглядит настройкой ответа, но у ряда моделей попадает в начало входа. Разбираю два способа сменить его посреди разговора и проверить, сохранилось ли чтение кэша.

АвторСергей Нотевский

Опубликовано

Попросили агента думать поменьше. История та же, инструменты те же, новый вопрос короткий. А счётчик кэшированных входных токенов падает. Как так, если мы изменили только настройку ответа?

В посте в TelegramВнешняя ссылка, откроется в новой вкладке я сформулировал это с кликбейтом: не меняйте reasoning effort, пока не разобрались с кэшем. Теперь разберём без кликбейта, зато с устройством запросов. И сразу поправлю одну фразу из поста: новые настройки effort действуют до следующего изменения, а не один пользовательский ход. Так написано в актуальных документах OpenAIВнешняя ссылка, откроется в новой вкладке и AnthropicВнешняя ссылка, откроется в новой вкладке.

Настройка ответа оказывается частью входа

Префиксный кэш переиспользует уже обработанное начало запроса. Это не кэш готового ответа: модель всё равно обрабатывает новый хвост истории и генерирует продолжение. Главное условие для чтения старого префикса — совпадение начала того входа, который фактически видит модель. Одинаковая история в JSON этого ещё не гарантирует.

У Anthropic выбранный effort встраивается в представление промпта. Если между запросами поменять верхнеуровневый output_config.effort, прежние точки кэширования сообщений перестают совпадать; в зависимости от модели могут затронуться и инструменты с системными инструкциями. Это прямое правило в документации по thinking и кэшуВнешняя ссылка, откроется в новой вкладке и таблице инвалидацииВнешняя ссылка, откроется в новой вкладке.

На уровне идеи выходит так:

запрос 1: effort=high | инструменты | инструкция | история
запрос 2: effort=low  | инструменты | инструкция | история | новый вопрос
          ↑ изменилось начало модельного входа

Два JSON-запроса могут содержать одинаковую историю и всё же давать разные кэшируемые префиксы. Работает и обратное: у Anthropic явно переданное значение по умолчанию и пропущенное поле для кэша одно и то же. Поэтому правило «поменял поле — точно потерял весь кэш» слишком грубое. Важно, какое эффективное значение получил движок и где оно оказалось в собранном входе.

Переключение внутри истории

Новый способ: исходную настройку запроса не трогаем, а изменение дописываем после стабильного префикса. Старая часть разговора не переписывается; очередной ход использует другой effort.

Два пути сменить effort

Верхний уровень

Переписать исходную настройку

Меняется конфигурация всего запроса. Ранее сохранённое начало может перестать совпадать.

Внутри истории

Добавить изменение после префикса

Исходная конфигурация и ранние сообщения остаются прежними. Новый effort действует с последующего хода.

Схема показывает положение изменения, а не обещает cache hit: запись ещё должна сохраниться и быть найдена.

В Responses API для GPT-6 Astra это configuration_update перед следующим сообщением пользователя. Верхнеуровневое reasoning.effort оставляем тем же, что в начале разговора:

{
  "model": "gpt-6-astra",
  "previous_response_id": "resp_...",
  "reasoning": { "effort": "high" },
  "input": [
    { "type": "configuration_update", "reasoning": { "effort": "low" } },
    { "role": "user", "content": "Собери короткий итог." }
  ]
}

Это сокращённый пример для продолжения ответа, начатого с high. По документации OpenAIВнешняя ссылка, откроется в новой вкладке такой update поддерживается у Astra в стандартном одноагентном режиме. Он меняет именно effort и действует до следующего configuration_update. В режимах и обёртках, которые этот элемент не принимают, приём недоступен.

У Claude Fable 5.1 конструкция другая: в массив messages добавляется сообщение role: "system" с пустым content и output_config.effort. Например, после уже состоявшегося обмена:

{
  "role": "system",
  "content": [],
  "output_config": { "effort": "low" }
}

Его ставят перед следующим сообщением пользователя, не меняя верхнеуровневый effort. Для API нужен beta header mid-conversation-output-config-2026-07-01; возможность описана для Fable 5.1, Mythos 5.1 и Opus 5. Fable 5 такой per-message update не поддерживает. Настройка вступает в силу со следующего user turn и сохраняется, пока очередное сообщение не сменит её. Все эти границы перечислены в документации Anthropic по effortВнешняя ссылка, откроется в новой вкладке.

Есть одна ловушка: сам effort-only message не создаёт запись кэша. Нужен cache_control: автоматический или с явной точкой кэширования. Само изменение ставим после стабильной точки, иначе переиспользовать нечего. Это отдельно оговорено в документации mid-conversation system messagesВнешняя ссылка, откроется в новой вкладке.

А что в Codex и Claude Code?

API умеет принять новую конструкцию. Из этого ещё не следует, что агентский клиент отправит её при нажатии на переключатель effort. Между UI и API живёт сборщик запросов, который может продолжать менять верхнеуровневое поле.

В релизе Claude Code 2.1.260Внешняя ссылка, откроется в новой вкладке Anthropic прямо пишет, что переключение /effort на Fable 5.1 теперь сохраняет промпт-кэш. Это утверждение о конкретной версии клиента и модели. Оно не обещает такой же путь для всех моделей или провайдерских шлюзов.

В открытом коде CodexВнешняя ссылка, откроется в новой вкладке есть reasoning_effort_override: флаг для добавления доверенных изменений effort в историю. На коммите, который я смотрел, он выключен по умолчанию. Клиентский кодВнешняя ссылка, откроется в новой вкладке дополнительно проверяет поддержку обновлений моделью и OpenAI-провайдер. Так что «Codex поддерживает» верно с оговоркой: механизм в коде есть, но включается флагом и зависит от модели и пути вызова. Я бы проверял сформированный запрос и usage своей версии, а не делал вывод по одному пункту меню.

Как проверить, что выиграли именно на кэше

Сначала нужен достаточно длинный и повторяющийся префикс. У OpenAI для GPT-5.6 и новее минимальная кэшируемая длина — 1 024 токена; короткий тест может честно показать ноль при любом способе смены effort. У Anthropic тоже есть порог, зависящий от модели и платформы, а запись требует cache_control. Сверяйте порог с документацией OpenAIВнешняя ссылка, откроется в новой вкладке или AnthropicВнешняя ссылка, откроется в новой вкладке для выбранного пути.

Тест простой: одна длинная история и два продолжения — обычная смена верхнеуровневого effort и поддерживаемый update внутри истории. Модель, инструменты, системные инструкции, порядок сообщений и настройки кэша в обеих ветках одинаковые. Первый запрос должен успеть создать запись до повторного. Для каждого ответа собираем usage, время до первого токена и результат задачи. Несколько повторов полезнее одного удачного прогона.

Проверка на своём агенте

01 · Вход

Сравнить фактические запросы

Модель, ранние сообщения, tools, конфигурация и место изменения effort.

02 · Кэш

Считать reuse из usage

OpenAI: cached_tokens; Claude: cache_read_input_tokens и cache_creation_input_tokens.

03 · Результат

Сверить задержку и качество

TTFT, выходные токены и пригодность ответа на одинаковом сценарии.

Совпадение префикса отвечает на вопрос «можно ли читать кэш». Usage показывает, было ли чтение в этом запросе.

У OpenAI смотрите usage.input_tokens_details.cached_tokens; диагностика кэша умеет указать причину промаха, но фактический объём чтения всё равно берите из usage. У Anthropic отдельно учитываются cache_creation_input_tokens и cache_read_input_tokens. История в одной сессии сама по себе не гарантирует попадания: запись могла истечь, маршрут измениться, а инструменты — поменять начало. Подробнее — в документации OpenAI по кэшированиюВнешняя ссылка, откроется в новой вкладке и диагностике промаховВнешняя ссылка, откроется в новой вкладке, а также Anthropic по prompt cachingВнешняя ссылка, откроется в новой вкладке.

И только после этого имеет смысл говорить про экономию и TTFT. Меньший effort может сократить выход, но иногда меняет число шагов и качество решения. Cache read помогает с обработкой входа; он не ускоряет автоматически генерацию ответа или внешний инструмент. Если в сценарии всё время уходит на decode или tool calls, хороший hit rate не спасёт пользовательскую задержку.

Сам приём полезный: сложный шаг выполняем на высоком effort, рутинное продолжение — на низком, не переписывая прогретую историю. Но это свойство конкретного API и клиента, а не универсальная кнопка «экономить». Сначала проверьте путь запроса. Потом — реальные кэшированные токены. И уже затем считайте деньги.

Если непонятно, где у вашего агента меняется начало, можно начать с разбора префиксного кэша и проекта audit-prompt-caching. Отдельный вопрос — попадёт ли повтор на нужную реплику; его я разобрал в статье о маршрутизации.

Связанные материалы