Суммаризация, память, навыки, middleware для управления контекстным окном.
У каждой модели фиксированный размер контекстного окна. По ходу работы агента оно заполняется, и этим процессом нужно управлять осознанно.
Интуиция подсказывает, что окно заполняет переписка с пользователем. На практике основной объём дают результаты инструментов: выдача поиска, содержимое страниц, ответы API, трассировки ошибок. Одно неудачное обращение к инструменту, вернувшее сырой JSON на несколько десятков килобайт, может занять больше места, чем весь предыдущий диалог.
Второе следствие важнее первого: в цикле агента вся история отправляется модели заново на каждом шаге. Значит, разросшийся контекст грозит не только упором в лимит: стоимость и задержка растут с каждым шагом.
Симптомы переполнения узнаваемы: агент забывает первоначальную задачу, повторяет уже сделанные вызовы инструментов, начинает отвечать общими словами. При жёстком превышении лимита провайдер просто возвращает ошибку.
Уменьшить нагрузку на окно можно на четырёх уровнях, и они не заменяют друг друга.
Первый уровень — это дисциплина инструментов. Прежде чем подключать суммаризацию, убедитесь, что инструменты возвращают сводки, а не дампы. Это самая дешёвая мера и самая часто пропускаемая.
Второй уровень — это сжатие истории: старые сообщения заменяются кратким пересказом.
Третий уровень — это очистка результатов инструментов: устаревшие ответы вычищаются, свежие остаются.
Четвёртый уровень — это вынос знаний наружу: постоянные инструкции и справочные материалы держат в файлах и хранилище, а в контекст подтягивают по необходимости.
SummarizationMiddleware следит за размером контекста и, когда порог достигнут, заменяет старую часть переписки кратким изложением, сохраняя свежие сообщения нетронутыми:
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[search, fetch_page],
middleware=[
SummarizationMiddleware(
model="openai:gpt-4o-mini",
trigger=("fraction", 0.8),
keep=("messages", 20),
)
],
)Параметр trigger задаёт условие срабатывания: доля заполненного окна, абсолютное число токенов или количество сообщений. keep описывает, что остаётся нетронутым после сжатия. Модель для суммаризации указывают отдельно, и брать дорогую здесь незачем: пересказ хорошо делает и дешёвая.
У суммаризации есть цена, о которой стоит знать заранее. Это сжатие с потерями и оно текстовое: изображения и другие мультимодальные вложения из старых сообщений после сжатия остаются только в виде текстового описания. Кроме того, каждое срабатывание — это дополнительный вызов модели.
Часто история диалога сама по себе компактна, а раздувают её именно ответы инструментов. Тогда вместо пересказа всего подряд разумнее вычистить старые результаты:
from langchain.agents.middleware import ContextEditingMiddleware, ClearToolUsesEdit
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[search, fetch_page],
middleware=[
ContextEditingMiddleware(
edits=[ClearToolUsesEdit(trigger=100_000, keep=3, clear_tool_inputs=False)]
)
],
)Стратегия ClearToolUsesEdit очищает старые результаты инструментов, оставляя несколько последних. Порог trigger задаётся в токенах, keep в количестве сохраняемых результатов, а clear_tool_inputs определяет, вычищать ли заодно аргументы вызовов.
Ключевой момент, ради которого стоит брать готовую мидлварь вместо самописной обрезки: пара из вызова инструмента и его результата не должна разрываться. Если вручную выкинуть ToolMessage, оставив tool_call, провайдер вернёт ошибку о несогласованной истории.
Суммаризация и очистка сочетаются: очистка убирает объёмный мусор, суммаризация сжимает то, что осталось.
Часть контекста нужна агенту всегда: правила проекта, соглашения, описание предметной области. Держать их в системном промпте неудобно, потому что промпт живёт в коде и меняется вместе с деплоем. Deep Agents предлагают выносить это в файлы AGENTS.md:
from deepagents import MemoryMiddleware
middleware = MemoryMiddleware(sources=["./AGENTS.md"])Содержимое загружается при старте агента и попадает в системный промпт. Правки в файле подхватываются без изменения кода, а сам файл живёт в репозитории и проходит ревью.
Постоянно загружаемая память подходит для коротких правил. Инструкции на несколько страниц по каждой из десяти предметных областей туда класть нельзя: окно кончится ещё до первого вопроса.
Навыки решают это ступенчатой загрузкой. При старте агент видит только имена и описания доступных навыков, а полное содержимое подтягивается тогда, когда задача действительно относится к этой области:
from deepagents.middleware.skills import SkillsMiddleware
middleware = SkillsMiddleware(backend=backend, sources=["./skills/"])Каждый навык — это папка с файлом SKILL.md, где в заголовке указаны имя и описание. Разница с памятью проста: память загружается всегда, навык только когда понадобился.
Самый радикальный способ не засорять окно — это не пускать в него лишнее. Субагент работает в собственном контексте: он получает задачу, делает десяток вызовов инструментов, а главному агенту возвращает только результат.
from deepagents.middleware.subagents import SubAgentMiddleware
middleware = SubAgentMiddleware(
subagents=[{
"name": "researcher",
"description": "Ищет и сверяет информацию в интернете",
"tools": [search, fetch_page],
"model": "openai:gpt-4o-mini",
}],
)Поиск, который занял бы у главного агента двадцать страниц выдачи, оставляет в его контексте один абзац с выводом. Плата за это дополнительные вызовы модели на координацию.
Стратегии складываются. Типовая сборка для агента, который долго работает с внешними источниками, выглядит так:
from langchain.agents import create_agent
from langchain.agents.middleware import (
ClearToolUsesEdit,
ContextEditingMiddleware,
SummarizationMiddleware,
)
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[search, fetch_page],
middleware=[
ContextEditingMiddleware(edits=[ClearToolUsesEdit(trigger=80_000, keep=3)]),
SummarizationMiddleware(model="openai:gpt-4o-mini", trigger=("fraction", 0.85)),
],
)Порядок здесь осмысленный: сначала убирается объёмный мусор от инструментов, и только если этого не хватило, включается сжатие переписки.
Не стоит подключать управление контекстом «на всякий случай»: каждая мидлварь добавляет вызовы модели и меняет то, что видит агент. Ориентиры простые.
Посмотрите на usage_metadata.input_tokens по шагам одного запуска. Если число растёт скачками после вызовов инструментов, начинать нужно с их результатов, а не с суммаризации.
Прикиньте типичную длину диалога. Если задача решается за пять шагов и укладывается в четверть окна, управление контекстом не нужно вовсе.
Если агент теряет исходную задачу к середине работы, дело обычно не в объёме, а в том, что важное вытеснено шумом. Здесь помогают субагенты и очистка, а не увеличение окна.
Ручная обрезка истории срезом списка. Легко разорвать пару из вызова инструмента и его результата и получить отказ провайдера.
Суммаризация как первое средство. Сначала стоит починить инструменты, возвращающие слишком много.
Слишком агрессивные пороги. Сжатие на каждом втором шаге удваивает число вызовов модели и стирает детали, которые ещё нужны.
Постоянная загрузка всех справочных материалов. Для больших наборов знаний нужны навыки с загрузкой по требованию, иначе окно занято ещё до начала работы.
Далее: Человек в цикле