Перейти к содержимому
22 нояб. 2024 г.·7 мин чтения

Как не переплачивать за длинный контекст: что резать и что помнить

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

Как не переплачивать за длинный контекст: что резать и что помнить

Почему длинный контекст быстро съедает бюджет

Каждый новый запрос к модели включает не только последний вопрос пользователя. Обычно приложение отправляет целый набор данных: системный промпт, часть истории чата, фрагменты из поиска или базы знаний, результаты инструментов и служебные поля. Модель читает это заново при каждом вызове, и вы платите за весь объем снова.

В типичном запросе есть несколько слоев: правила для модели, прошлые сообщения, вставки из документов, ответы инструментов, логи или JSON. Проблема в том, что у API нет собственной памяти между вызовами. Если диалог длится 15 ходов, первый вопрос не хранится на стороне модели. Приложение передает его снова и снова. Один и тот же абзац может попасть в контекст 10-20 раз за одну сессию.

Из-за этого даже небольшой хвост быстро превращается в заметный расход. Допустим, системный промпт занимает 700 токенов, история чата - еще 1500, а к новому вопросу вы добавили документ на 2000 токенов. Уже один ход получается дорогим. На следующем ходе почти тот же объем уходит снова, плюс свежий ответ модели.

Не вся история одинаково полезна. Если пользователь один раз сказал: "мне нужен ответ на казахском" или "у нас SAP и 1С", эти факты стоит сохранить. Они влияют на следующие ответы. А приветствия, благодарности, одинаковые уточнения и старые версии одного и того же резюме обычно не дают ничего, кроме лишних токенов.

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

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

Поэтому счет может расти даже при коротких вопросах. Сам вопрос занимает 20 слов, а все вокруг него - еще несколько тысяч токенов.

Что оставлять в истории чата

В истории чата нужен не весь разговор, а только то, без чего модель собьется в следующем ответе. Многие команды хранят слишком много: приветствия, повторы, длинные "ок, понял" и куски текста, которые уже не влияют на задачу.

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

Обычно стоит оставлять цель пользователя, текущий шаг задачи, ограничения по срокам, формату, языку и бюджету, точные данные вроде названий, версий, ID и дат, а также решения, которые уже приняли по ходу разговора. Иногда полезно сохранить 1-2 последние реплики, если из них понятно, почему следующий шаг вообще появился.

На практике этого хватает чаще, чем кажется. Если пользователь уже выбрал короткий отчет в таблице, дедлайн до пятницы и лимит в 50 000 тенге, именно эти три факта должны остаться рядом с запросом. Фразы вроде "добрый день", "спасибо" и "давайте еще раз" только раздувают историю.

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

Что хранить отдельно

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

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

Есть простой тест. Спросите про каждую строку истории: она нужна только сейчас, нужна почти всегда или уже не нужна. Первый тип оставляйте в чате. Второй переносите в память. Третий удаляйте.

Если команда отправляет LLM-запросы через шлюз вроде AI Router, лишние абзацы в каждом вызове быстро превращаются в заметный расход. Чистая история чата обычно дает не только снижение токенов, но и более точные ответы: когда в контексте меньше шума, модели проще держать мысль.

Как обрезать контекст без потери смысла

Сначала посчитайте, сколько токенов уходит в среднем запросе до любых правок. Иначе вы не поймете, что именно дало эффект. Возьмите 50-100 реальных диалогов и замерьте длину входа, длину ответа и стоимость одного хода.

После этого задайте жесткий потолок на историю. Не "примерно 20 последних сообщений", а конкретный бюджет: например, до 6-8 тысяч токенов на весь вход или до N последних реплик плюс системные инструкции. Жесткий лимит полезнее расплывчатых правил, потому что он не дает чату разрастаться незаметно.

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

Если приходится вырезать большой кусок, не удаляйте его молча. Замените его коротким резюме на 2-4 строки. Но в таком резюме пишите только факты, а не пересказ разговора. Например: "Пользователь выбрал тариф B, нужен отчет в CSV, даты фиксированы, формат менять нельзя".

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

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

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

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

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

Когда сжимать текст, а когда хранить источник отдельно

Если задача простая, правило тоже должно быть простым: разделяйте данные на то, что уже устоялось, и на то, что модель должна видеть дословно. Обычно это экономит больше токенов, чем мелкая правка промпта.

Сжимать стоит там, где смысл важнее формулировки. Если в истории уже закрепились роль пользователя, статус задачи, принятые решения или краткий итог встречи, все это можно свернуть в 2-3 строки без потери качества. Модели не нужен весь путь разговора, если вывод уже ясен.

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

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

Но есть вещи, которые нельзя пересказывать свободно. Цены, лимиты, коды товаров, номера договоров, текст правил, юридические условия и точные инструкции лучше передавать как есть. Если модель перепутает одно слово в тарифе или одно число в коде, ошибка будет уже не стилистической, а рабочей.

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

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

Проверка здесь простая. Если текст можно заменить точной записью в памяти или коротким summary без риска исказить смысл, сжимайте. Если важна каждая формулировка, храните источник отдельно и подавайте только нужный кусок.

Как выбрать память для разных задач

Подключите один API-шлюз
Смените адрес API и продолжайте работать со своими SDK, кодом и промптами.

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

Для разового запроса память почти не нужна. Если человек просит пересказать письмо, исправить текст или вытащить факты из одного документа, модели хватает самого текста и короткой инструкции. Старая история чата в этом случае только раздувает вход.

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

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

Представьте внутреннего помощника в банке. Один аналитик всегда просит ответ на русском, в виде короткой таблицы, с суммами в тенге. Эти настройки можно сохранить в профиль. А детали текущей заявки клиента, спорный лимит и последнее исправление отчета - это рабочая память, и она нужна только пока идет этот кейс.

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

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

Пример на одном диалоге

Клиент банка часто описывает один сбой долго и по кругу. Для живого чата это нормально: человек переживает, вспоминает детали не сразу, повторяет попытки и отвечает на уточнения оператора. Для модели такая история дорогая, хотя полезных фактов там немного.

Допустим, за первые восемь сообщений клиент несколько раз объяснил одну проблему: 14 мая утром он пытался сделать перевод с кредитной карты через чат в мобильном приложении, но видел одну и ту же ошибку. Он добавил, что перезапускал приложение, менял сеть и пробовал снова, но результат не менялся. Остальное - повторы, эмоции, извинения и фразы вроде "я уже писал об этом выше".

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

Вместо восьми сообщений можно передать модели короткое резюме:

Клиент обратился в чат мобильного приложения.

Продукт: кредитная карта.

14 мая утром клиент пытался сделать перевод, но получил ошибку. Перезапуск приложения и повторные попытки не помогли.

Смысл почти не меняется. Модели по-прежнему ясно, что случилось, где это произошло и что уже пробовали сделать. Теряются в основном повторы и лишние слова, а не факты, нужные для ответа или маршрутизации запроса.

По деньгам разница обычно заметна. Полная история может занимать 1200-1800 токенов, если там много повторов и служебных реплик. Короткое резюме часто укладывается в 120-200 токенов. Ответ приходит быстрее просто потому, что модель читает меньше текста на каждом новом ходе.

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

Где токены теряются зря

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

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

Есть и более тихая ошибка: один и тот же факт живет сразу в трех местах - в системном промпте, в памяти диалога и в ответе инструмента. Модель читает дубликаты, а вы платите за них снова и снова. Со временем разные копии еще и расходятся, и тогда растут не только токены, но и число странных ответов.

Быстрые признаки перерасхода видны сразу. В запросах повторяется один и тот же справочный блок на 2-3 тысячи токенов. Инструмент возвращает полный JSON, хотя модели нужен один статус и одна сумма. Историю чата сжимают после каждого сообщения, даже когда разговор еще держится на одной детали. Пользователь меняет условие, а старое резюме продолжает жить в памяти и тянется дальше.

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

Еще один частый пожиратель бюджета - длинные ответы инструментов и служебные хвосты. Логи, сырые таблицы, поля вроде internal_notes, debug, trace, полные куски HTML или JSON редко нужны модели целиком. Передавайте только то, на чем строится следующий шаг.

Резюме диалога тоже надо обновлять, а не складывать как архив. Если пользователь сначала просил отчет за квартал, а потом переключился на отчет за месяц, старое резюме нужно переписать. Иначе в памяти остаются две версии задачи, и модель начинает путаться.

Хорошее рабочее правило простое: каждый факт хранится в одном основном месте. Справочник лежит отдельно и подтягивается по мере надобности. Память держит устойчивые предпочтения и решения. Ответ инструмента содержит только нужный результат.

Быстрая проверка перед запуском

Проверьте сжатие без догадок
Сравните полную историю и короткое резюме через один OpenAI-совместимый API.

Перед релизом полезно пройтись по контексту так же строго, как по логике кода. Если блок текста не меняет ответ модели, убирайте его. Лишние 500-1000 токенов в одном запросе кажутся мелочью, но в длинных диалогах быстро превращаются в заметные деньги.

Для задачи вроде "как не переплачивать за длинный контекст" этого часто уже достаточно: не пытайтесь сохранить всю историю, пока не доказали, что она нужна. Модель не получает бонус за объем. Ей обычно проще работать с коротким и точным вводом.

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

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

Перед запуском достаточно проверить четыре пункта:

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

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

Если вы уже считаете трафик через единый шлюз, полезно смотреть токены по маршрутам и типам задач отдельно. В AI Router это особенно удобно, потому что можно сравнивать несколько провайдеров и моделей через один OpenAI-совместимый эндпоинт и быстрее увидеть, где расход раздувает именно лишняя память диалога, а не выбор модели.

С чего начать дальше

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

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

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

Первый прогон можно сделать очень просто:

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

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

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

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

Часто задаваемые вопросы

С чего начать, если расходы на длинный контекст уже растут?

Начните с замера. Возьмите 50–100 реальных диалогов и посмотрите, сколько токенов уходит на вход, ответ и весь ход целиком. Потом уберите один блок за раз: старые реплики, длинные вставки из документов, полный вывод инструментов. Так вы быстро увидите, что дает расход без пользы.

Что можно смело вырезать из истории чата?

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

Какие данные стоит оставлять в контексте?

Оставляйте то, без чего модель ошибется в следующем шаге. Это цель пользователя, текущий статус задачи, язык ответа, формат, сроки, лимиты, точные названия, версии, даты, ID и уже принятые решения. Иногда хватает еще 1–2 последних реплик, если они объясняют, почему появился новый вопрос.

Когда лучше заменить старые сообщения кратким резюме?

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

Что нельзя сжимать своими словами?

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

Где хранить постоянные настройки пользователя?

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

Какой лимит на историю чата лучше задать?

Ставьте не число сообщений, а бюджет в токенах на весь вход. Для многих сценариев хватает жесткого потолка, например 6–8 тысяч токенов вместе с системным промптом и рабочей историей. Такой лимит проще контролировать, и чат не разрастается незаметно.

Почему даже короткий вопрос может стоить дорого?

Потому что сам вопрос часто занимает мало места, а система добавляет вокруг него много текста. Дорогими становятся системный промпт, куски истории, фрагменты документов, JSON, логи и ответы инструментов. В итоге пользователь пишет одно предложение, а модель читает несколько тысяч токенов.

Как понять, что я урезал контекст слишком сильно?

Смотрите на качество следующего шага, а не только на цену одного запроса. Если модель теряет дату, лимит, исключение, запрет или начинает просить лишние уточнения, вы урезали слишком сильно. В таком случае верните 2–3 факта или оставьте последний блок сообщений без сжатия.

Нужно ли отправлять модели полный ответ инструмента?

Нет, полный вывод нужен редко. Если инструмент вернул большой JSON, таблицу, лог или HTML, вытащите только поля, на которых строится следующий ответ. Так модель читает меньше мусора, отвечает быстрее и реже путается в деталях.