Контекст горит раньше, чем вы думаете
Вы запускаете своего агента на Qwen 2.5 14B. Он бодро начинает задачу, первые 5-10 шагов выглядят осмысленно. А потом — словно сносит крышу. Агент забывает первоначальную цель, начинает повторяться или генерировать откровенный бред. Вы проверяете логи: контекстное окно заполнено лишь на 70%. Что происходит?
Деградация контекста в моделях с длинным контекстом — известный, но редко обсуждаемый в лоб факт. Модель теряет связность не когда окно переполнено, а гораздо раньше. Для Qwen 2.5 точка слома — примерно 60-70% от заявленного лимита в 32K токенов.
В теории все рапортуют о миллионах токенов. На практике ваш агент начнет "глючить" уже после 20-22 тысяч. И если вы не управляете контекстом вручную, то вся затея с автономными агентами рассыпается как карточный домик.
Виноват не размер окна, а механизм внимания
Забудьте про красивые цифры в 128K или 1M токенов. Проблема в архитектуре трансформера. Механизм внимания софтмакс плохо масштабируется на сверхдлинные последовательности. Самые ранние токены в контексте получают экспоненциально малый вес, просто "растворяются" в шуме.
Производители моделей борются с этим через улучшенные позиционные эмбидинги (RoPE, YaRN). Но для Qwen 2.5, особенно в локальном развертывании, это лишь отодвигает проблему, а не решает её. Особенно если вы используете 4-битную или 8-битную квантизацию — она дополнительно "съедает" стабильность длинного контекста.
Агрессивная обрезка — единственное работающее решение
Ожидание, что модель сама справится с памятью, — путь в никуда. Встроенные системы "памяти" в большинстве локальных LLM — это просто обёртки над векторной БД, которые работают медленно и прерывают поток рассуждений. Они не подходят для агентов, где каждый миллисекунд на счету.
Решение грубое, но эффективное: резать контекст заранее, не дожидаясь симптомов.
1Определите критический порог для своей задачи
Для начала проведите стресс-тест. Запустите агента в цикле и отслеживайте, после какого количества токенов качество ответов начинает падать. Не доверяйте общим цифрам. Для кодинга сломаться может на 18K, для анализа документов — на 24K. Запишите это число. Это ваш личный лимит, который всегда будет меньше заявленного.
Наша практика с Qwen 2.5 14B (загруженной через llama.cpp с контекстом 32K) показывает: для задач планирования и выполнения (agentic workflow) безопасный лимит — 19 200 токенов. Как только история диалога приближается к этой отметке, пора действовать.
2Спроектируйте стратегию обрезки
Резать всё подряд нельзя. Нужна эвристика, что важно, а что можно выкинуть. Вот иерархия ценности частей контекста в агентском цикле:
- Системный промпт и первоначальная цель. Это святая святых. Удалять нельзя никогда.
- Результаты последних 3-5 действий (tool calls). Без них агент потеряет нить текущей операции.
- Критические промежуточные выводы, которые модель сделала сама в процессе рассуждения.
- Ранняя история действий. Её можно сжать или выбросить, если она не ссылается на текущий шаг.
- Подробные raw-ответы от инструментов (API, поиска). Часто их можно суммировать в одно предложение.
Создайте функцию, которая проходит по истории диалога и применяет эти правила, когда срабатывает триггер по длине.
def aggressive_trim(context_messages, current_token_count, threshold=19000):
"""Агрессивно обрезает контекст, сохраняя самое важное."""
if current_token_count < threshold:
return context_messages
# 1. Всегда сохраняем системный промпт (первое сообщение)
system_message = context_messages[0]
# 2. Сохраняем последние N сообщений (действия и их результаты)
recent_messages = context_messages[-10:] # Эмпирическое значение
# 3. Пытаемся найти и сохранить ключевые выводы агента (сообщения, где есть "Итак:", "Вывод:" и т.д.)
key_insights = [msg for msg in context_messages[1:-10] if is_key_insight(msg)]
# 4. Собираем новый контекст
new_context = [system_message] + key_insights + recent_messages
# 5. Логируем факт обрезки
logger.warning(f"Контекст обрезан с {current_token_count} токенов до ~{estimate_tokens(new_context)}")
return new_context3Интегрируйте обрезку в цикл агента
Теперь встройте эту логику в каждый шаг вашего агентского пайплайна. Схема выглядит так:
# Псевдокод основного цикла
context = [system_prompt]
while task_not_complete:
# 1. Проверяем длину текущего контекста
token_count = count_tokens(context)
# 2. Агрессивно обрезаем, если приближаемся к лимиту
context = aggressive_trim(context, token_count, threshold=19200)
# 3. Генерируем следующий шаг (действие или ответ) с обновлённым контекстом
next_action = qwen.generate(context)
# 4. Получаем результат действия (от инструмента, пользователя и т.д.)
result = execute_action(next_action)
# 5. Добавляем и действие, и результат в историю
context.append({"role": "assistant", "content": next_action})
context.append({"role": "user", "content": result})
# ... цикл повторяетсяКлючевой момент — обрезка происходит ДО того, как качество упадет, а не после. Это превентивная мера.
Где спрятаны грабли: нюансы, которые всё сломают
Вы можете скопировать код выше и всё равно наступить на эти грабли.
| Ошибка | Последствие | Как избежать |
|---|---|---|
| Обрезка без учёта структуры сообщений | Модель получает битые JSON или непарные теги, генерация ломается. | Всегда обрезайте по целым сообщениям, а не по произвольным токенам. Проверяйте, что последнее сообщение не обрезано посередине. |
| Игнорирование KV-cache инвалидации | После обрезки модель может ссылаться на "призрачные" токены из старого кэша, выдавая абсурд. | При сильной обрезке (удалении более 20% контекста) сбрасывайте KV-cache полностью. В llama.cpp это llama_kv_cache_clear. В других бэкендах ищите аналоги. |
| Слишком частая обрезка | Агент тратит всё время на "перезагрузку" контекста, теряет связность. | Настройте порог обрезки так, чтобы она срабатывала не чаще, чем каждые 5-7 шагов агента. См. статью "Контекстный инжиниринг". |
Самый болезненный нюанс — работа с инструментами (tools). Если агент вызвал инструмент, а вы обрезали контекст до того, как получили результат, агент "забудет", что он вообще что-то вызывал. Поэтому в стратегии обрезки (шаг 2) мы жёстко сохраняем последние сообщения, куда как раз и попадают вызовы и результаты инструментов.
FAQ: вопросы, которые вы хотели задать, но боялись
Почему бы просто не использовать модель с контекстом 128K?
Потому что проблема не в числе, а в архитектуре. Деградация контекста есть у всех моделей. Кроме того, 128K-модель требует космических ресурсов VRAM для KV-cache. На 16 ГБ карте вы её просто не запустите с таким контекстом. Подробнее в разборе "Агенты на 16 ГБ VRAM".
А встроенные функции памяти в LM Studio или Ollama не решат проблему?
Нет. Часто это просто отдельный чат-интерфейс или медленная RAG-система. Они не интегрированы напрямую в поток генерации агента. Вашему агенту в середине цикла не скажут: "Подожди, я сейчас поищу что-то в векторах". Управление должно быть детерминированным и моментальным. На эту тему есть целая статья — "AI Memory - это обман?".
Можно ли обойтись без обрезки, используя суммирование (summarization)?
Можно, но это дорого и медленно. Каждый раз запускать ещё одну LLM для суммирования истории — это удваивает latency и нагрузку. Для продакшн-агента, который должен работать в реальном времени, агрессивная обрезка по чётким правилам почти всегда выигрывает у суммирования по соотношению цена/качество. Хотя для архивных задач, где важны все детали, суммирование — вариант. Про его плюсы и минусы мы писали здесь.
Это решение работает только для Qwen 2.5?
Принцип агрессивной превентивной обрезки работает для любой LLM, которая демонстрирует деградацию контекста. Для Qwen 2.5 мы эмпирически вывели порог в ~60% от заявленного окна. Для другой модели, например, новой версии от Mistral (партнёрская ссылка на сервис аренды GPU, где её можно протестировать), порог может быть другим. Суть — найти свой порог и резать, не дожидаясь слома.
Что будет дальше? Память как отдельная служба
Ручное управление контекстом — это костыль. Но костыль, который сегодня позволяет вашим агентам работать. Будущее — за выделенными Memory OS, как в Amazon Bedrock AgentCore, где память управляется отдельным модулем. Но пока такие системы сложны и дороги для локального развертывания.
До тех пор возьмите за правило: доверяйте заявленным цифрам контекста только на 50-60%. И режьте без сомнений. Ваш агент этого не почувствует, но будет работать в разы стабильнее. Иногда чтобы помнить главное, нужно безжалостно забывать второстепенное.