Контекстное окно для ИИ-агентов: как им управлять
В обычном чате контекст растёт линейно: вопрос, ответ, вопрос, ответ. У агента всё иначе — одна пользовательская задача разворачивается в десятки внутренних шагов: рассуждение, вызов инструмента, результат инструмента, снова рассуждение. Каждый такой шаг добавляет токены, и все они остаются в истории, потому что API стейтлесс — при каждом следующем запросе вы заново отправляете модели всю накопленную переписку. Разберём, почему контекст агента заполняется настолько быстрее, чем в диалоге, и какими способами это разгружают на практике.
Содержание
- Почему контекстное окно агента кончается быстрее, чем в чате
- Из чего складывается контекст агента
- Что происходит, когда контекст переполняется
- Обрезка истории: простой, но грубый способ
- Суммаризация и компакция промежуточных шагов
- Вынос результатов инструментов из контекста
- Честная цена управления контекстом
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Почему контекстное окно агента кончается быстрее, чем в чате
В диалоге с человеком одно сообщение — это одна реплика. В агентном цикле одна пользовательская задача может породить десять, двадцать, пятьдесят внутренних итераций «модель — инструмент — результат», прежде чем агент вообще выдаст финальный ответ. И каждая итерация тяжелее обычной реплики чата: помимо текста рассуждения в неё добавляется вызов инструмента (имя + аргументы) и его результат — а результат инструмента часто не короткая фраза, а сырой вывод: содержимое файла, ответ API в JSON, лог выполнения команды, страница поисковой выдачи.
Условный пример: агент читает файл конфигурации (~4000 токенов), делает grep по логам и получает вывод (~2000 токенов), запускает тесты и получает лог прогона (~3000 токенов) — три шага, и в контексте уже +9000 токенов, при том что сама задача ещё не решена. В обычном чате столько токенов набегает за долгий разговор на протяжении часов; агент может выжечь тот же объём за одну автономную задачу длиной в несколько минут. Это не гипотетическая цифра — конкретные значения зависят от инструментов и данных, с которыми работает именно ваш агент, но порядок величины стоит держать в голове при проектировании.
Разница в характере роста, а не в лимите модели: у агента и у чата может быть одно и то же окно в 200K или 1M токенов, но агент добирается до потолка кардинально быстрее, потому что генерирует историю сам, без участия человека, который естественным образом сдерживает темп переписки.
Из чего складывается контекст агента
Контекст, который отправляется в API на каждом шаге цикла, обычно состоит из:
- системного промпта — роль, инструкции, а также описания всех зарегистрированных инструментов вместе с их JSON-схемами параметров. Чем больше инструментов у агента, тем тяжелее уже сам системный промпт: агент с 15-20 тулами (типично для связки на LangChain или n8n) может нести несколько тысяч токенов служебного описания ещё до первого сообщения пользователя;
- истории сообщений user/assistant — постановка задачи и промежуточные ответы модели;
- блоков рассуждения (thinking/reasoning) — если модель поддерживает расширенное рассуждение, эти блоки тоже считаются токенами и накапливаются в истории;
- вызовов инструментов (tool_use) — имя инструмента и переданные аргументы;
- результатов инструментов (tool_result) — самое тяжёлое звено: сюда попадает всё, что вернул инструмент, без урезания, если вы не позаботились об этом сами.
Грубая прикидка бюджета: при окне модели в 200K токенов, системном промпте и наборе тулов на 5K, и среднем весе шага цикла в 1-3K токенов, агент упирается в потолок примерно за 60-90 шагов. Это именно ориентир для прикидки порядка величины — у вас цифры будут другими в зависимости от объёма ответов инструментов, и мерить их стоит на своих логах. Про то, как вообще считать токены и что стоит за строкой «контекст 128K» на конкретном железе, разбирали отдельно в статье про контекстное окно и память — там больше про инфраструктурную сторону вопроса, здесь — про поведение агента.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSЧто происходит, когда контекст переполняется
Три типичных сценария, и все три неприятные:
- API возвращает ошибку превышения длины контекста, и цикл агента просто падает на середине задачи.
- Наивная обрезка истории (например, фреймворк молча отбрасывает старые сообщения) ломает парность tool_use/tool_result: у результата инструмента должен быть парный вызов чуть выше по истории, и если обрезать неаккуратно, получится невалидная последовательность сообщений — новая ошибка API вместо решённой проблемы.
- Деградация без явной ошибки — самый коварный случай. Агент «забывает» исходную формулировку задачи, повторяет уже выполненные шаги, теряет план действий, зацикливается на одном и том же вызове инструмента. Внешне это выглядит как модель поглупела, а на деле — она просто не видит начала своей же работы.
Отсюда практический вывод: управление контекстом у агента — это не оптимизация «на будущее», а обязательная часть архитектуры, если задача рассчитана больше чем на десяток шагов цикла.
Обрезка истории: простой, но грубый способ
Самый очевидный приём — скользящее окно (sliding window): держать в контексте только последние N сообщений или последние M токенов, старое отбрасывать. Дёшево реализовать, не требует дополнительных вызовов модели, но с оговорками:
- обрезать можно только по границе сообщения, а не «внутри» пары tool_use/tool_result — иначе ошибка API из пункта выше гарантирована;
- исходную постановку задачи (первое сообщение пользователя) обычно закрепляют отдельно и не обрезают никогда — иначе агент буквально теряет цель;
- окно должно оставлять запас: если резать вплотную к лимиту, следующий же шаг с крупным результатом инструмента снова упрётся в потолок.
Псевдокод для ориентира:
def trim_history(messages, max_tokens, system_task):
kept = [system_task] # первая задача — не трогаем
running = count_tokens(system_task)
for msg in reversed(messages[1:]):
t = count_tokens(msg)
if running + t > max_tokens:
break
kept.insert(1, msg)
running += t
return kept
Проблема очевидна: всё, что не поместилось, теряется безвозвратно. Для коротких задач это приемлемо, для длинных агентных сессий — источник тех самых повторных ошибок, о которых ниже.
Суммаризация и компакция промежуточных шагов
Более аккуратный подход — не выбрасывать старую историю, а сжимать её в короткую сводку перед тем, как она выйдет за порог. Механика обычно такая: при приближении к пороговому объёму токенов (скажем, 70-80% от лимита) агент (или отдельный, часто более дешёвый вызов модели) получает задачу «сожми предыдущие N шагов в краткое резюме: что сделано, что решено, что осталось» — и это резюме подменяет собой развёрнутую историю в следующих запросах.
Такая функция под разными названиями встречается в современных API и агентных SDK как «компакция» (compaction) — автоматическая суммаризация истории по достижении порога токенов. Технические детали отличаются от провайдера к провайдеру и от версии к версии SDK — проверяйте синтаксис в актуальной документации своего стека, а не по общему описанию.
Цена суммаризации:
- это дополнительный вызов модели — деньги и задержка на каждом срабатывании;
- сжатая сводка неизбежно теряет детали: точные числа, формулировки ошибок, частные случаи. Модель, которая пишет резюме, сама решает, что важно, а что нет — и может ошибиться.
Вынос результатов инструментов из контекста
Третья стратегия бьёт не по истории диалога, а по самому тяжёлому компоненту — результатам инструментов. Идея простая: вместо того чтобы держать полный ответ инструмента (дамп файла, JSON от API, страницу логов) в контексте до конца сессии, в истории остаётся только компактная ссылка — короткое описание плюс идентификатор, а полное содержимое уходит во внешнее хранилище (БД, файл, Redis, векторный индекс). Если агенту снова понадобятся детали, для этого регистрируют отдельный инструмент — «получить полный результат по id».
Общая концепция для этого называется context editing (в некоторых API — конкретно «очистка старых результатов вызовов инструментов», clear tool uses): старые tool_result стираются из истории или сворачиваются в плейсхолдер, а сами данные не теряются, а остаются доступны по запросу через отдельный механизм. Точный синтаксис параметров у разных провайдеров разный и меняется между версиями — проверяйте по документации своего стека, а сам паттерн «храни полный результат снаружи, в контексте — только ссылку» универсален и легко реализуется руками в любом самописном цикле или поверх LangChain/n8n.
Практическая обвязка инструмента для такого паттерна:
def run_tool_and_offload(tool_name, args, store):
raw_result = call_real_tool(tool_name, args)
result_id = store.save(raw_result) # файл, Redis, БД — что угодно
summary = summarize_briefly(raw_result) # 1-2 предложения, не вызов LLM
return {
"summary": summary,
"result_id": result_id,
"note": "полный результат доступен через get_full_result(result_id)"
}
Такой подход не требует дорогого вызова модели на каждом шаге и не теряет данные насовсем — но добавляет инженерной сложности: нужно где-то держать хранилище, продумать инструмент чтения по id, и есть риск, что агент просто не сообразит, что старые данные стоит перечитать.
Честная цена управления контекстом
У всех трёх стратегий один и тот же корень проблемы: контекстное окно конечно, а значит любое управление им — это управление тем, что модель забудет, а не гарантия, что она не забудет ничего.
- Обрезка теряет детали безвозвратно. Если на пятом шаге агент принял решение на основе данных, которые к тридцатому шагу уже вышли за окно, он может повторить уже отвергнутый путь или переоткрыть уже найденную ошибку — просто потому что не видит, что это уже было.
- Суммаризация снижает риск полной потери, но сжатие само по себе искажает — точные значения, редкие граничные случаи, формулировки конкретных ошибок в резюме обычно не выживают. Агент может решить уже решённую подзадачу заново, потому что в сводке осталось «баг исправлен», а не то, в чём именно он был.
- Вынос во внешнее хранилище не искажает данные, но перекладывает риск на другую плоскость: агент должен сам понять, что стоит перечитать сохранённый результат, — если он этого не делает, для него данные всё равно как будто не существуют.
Практический вывод — не искать одну идеальную стратегию, а комбинировать: агрессивный вынос крупных результатов инструментов по умолчанию (это дешевле всего и не требует лишних вызовов модели), периодическая суммаризация на пороговых значениях для длинных сессий, и жёстко закреплённая изначальная постановка задачи, которая не обрезается никогда. И отдельно — вести полный необрезанный лог истории на своей стороне, вне контекста модели, просто для отладки: когда агент повёл себя странно, вы захотите посмотреть, что на самом деле произошло, а не только то, что осталось в урезанном контексте.
Взять модель с окном побольше — не архитектурное решение, а отсрочка проблемы: агент в цикле часами заполнит и миллион токенов, а стоимость и задержка растут вместе с объёмом истории на каждом запросе, потому что API стейтлесс. Экономику таких запросов удобнее прикидывать заранее, а не по факту счёта — подход к расчётам разобран в статье про стоимость LLM-запросов на своём сервере.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Влияет ли объём RAM на сервере на размер контекстного окна?
Само окно — свойство модели или API, а не сервера. Но если вы хостите оркестратор агента (свой Python-сервис, n8n, LangChain) на VPS, память процесса влияет на то, сколько истории, буферов и данных для offloading вы можете держать одновременно в оперативной памяти, не упираясь в своп.
Нужно ли всё это, если использовать модель с окном в 1M токенов?
Да. Автономный агент в цикле рано или поздно заполнит и такое окно, а стоимость и задержка растут с объёмом истории на каждом запросе — большое окно откладывает проблему, но не решает архитектурно.
Как понять, что агент уже упирается в лимит контекста?
Типичные симптомы: ошибки API про превышение длины, агент повторяет уже сделанные шаги, «забывает» исходную формулировку задачи, зацикливается на одном и том же вызове инструмента без прогресса.
Обрезка и суммаризация — это одно и то же?
Нет. Обрезка отбрасывает старые данные без остатка, суммаризация сжимает их в резюме ценой дополнительного вызова модели и потери деталей. Это разные компромиссы, и для длинных сессий их часто комбинируют.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.