Узнайте, почему AI-агенты забывают диалог или тратят бюджет токенов. Разделяйте память разговора и память контекста. Практический гайд с кодом Python.
Ваш AI-агент забывает, что вы сказали три хода назад, или тратит весь бюджет токенов на один вызов инструмента? Корень проблемы — смешение двух типов памяти: памяти разговора (что было сказано) и памяти контекста (большие выводы инструментов вроде логов). Они требуют разного хранения и разного поиска. В этой статье вы узнаете, как разделить их с помощью фреймворка Strands, и увидите замеры: до 97% экономии токенов.
У AI-агента есть два вида памяти:
Если использовать один тип там, где нужен другой, агент либо забывает, либо переполняет окно контекста.
| Память разговора | Память контекста |
|---|---|
| Реплики, предпочтения, извлечённые факты | Большие выводы инструментов (логи, датасеты) |
| Поиск по смыслу (семантическое сходство) | Поиск по точному идентификатору (ссылка) |
| Вопрос: «Что пользователь сказал раньше?» | Вопрос: «Дай мне тот 5MB лог-файл, точно» |
| Не подходит для: 5MB лог-блоб | Не подходит для: «Как зовут пользователя?» |
Большие выводы инструментов переполняют окно контекста, потому что они неделимы и отправляются заново при каждом вызове модели. Инструмент, вернувший 200KB логов, стоит не 200KB один раз — эти данные едут во входе каждого последующего шага, пока не вытеснят исходный вопрос.
Исследование IBM Research (Solving Context Window Overflow in AI Agents, 2025) показало: пайплайн материаловедения потреблял 20 822 181 токен и падал; после выноса больших данных из контекста — 1 234 токена и успех.
Раньше мы хранили большие данные вручную: инструмент писал их в agent.state и возвращал короткий указатель; следующий инструмент читал по ключу. Но логика выноса жила внутри каждого инструмента. Теперь Strands поставляет этот паттерн как плагин ContextOffloader — ваши инструменты остаются обычными функциями:
from strands import Agent
from strands.vended_plugins.context_offloader import ContextOffloader, FileStorage
# Обычные инструменты — без логики указателей, без agent.state внутри
agent = Agent(
model=MODEL,
tools=[fetch_application_logs, count_errors_by_service],
plugins=[
ContextOffloader(
storage=FileStorage("./artifacts"),
max_result_tokens=800,
preview_tokens=200
)
],
)
agent("Fetch 2 hours of logs for 'api-gateway' and tell me the top error service.")Когда результат инструмента превышает max_result_tokens, плагин перехватывает его, сохраняет каждый блок в хранилище и оставляет в контексте маленький превью + ссылку. Агент получает инструмент retrieve_offloaded_content(reference), чтобы забрать полные данные по точной ссылке, когда они действительно нужны.
Нативный паттерн Memory Pointer — это ContextOffloader, плагин, который перехватывает слишком большие результаты инструментов во время выполнения, сохраняет каждый блок в бэкенде и заменяет контекстный результат на превью + ссылку. Большие данные никогда не затапливают окно контекста, а ваши инструменты не касаются логики указателей.
Я выполнил один и тот же запрос тремя стратегиями. Один запрос, gpt-4o-mini, 2 часа логов:
| Стратегия | Токенов в контексте |
|---|---|
| Без управления | ~18 000–20 000 |
| ContextOffloader (FileStorage) | ~490 |
| context_manager="auto" | ~1 000 |
Это примерно 97% меньше токенов для того же ответа. Цифры варьируются, потому что данные логов случайны; test_native_pointer.py воспроизводит их.
Честное предостережение: оффлоадер — это страховочная сетка, а не всё решение. Основная экономия достигается в паре с селективным инструментом. Мой count_errors_by_service вычисляет ответ на сервере и возвращает краткую сводку, так что агент отвечает из сводки, а логи остаются выгруженными. Без селективного инструмента агент, которому нужны все данные, просто вызовет retrieve_offloaded_content и вернёт всё обратно. Оффлоадер гарантирует, что вы не переполнитесь; селективные инструменты удерживают низкое количество токенов.
Для типичного многошагового агента не нужно настраивать выгрузку и суммаризацию отдельно:
agent = Agent(
model=MODEL,
tools=[...],
context_manager="auto"
)Это объединяет SummarizingConversationManager (суммаризирует старую историю с упреждающим сжатием) и ContextOffloader (в памяти) с проверенными на бенчмарках настройками. Всё, что вы передаёте явно, имеет приоритет.
FileStorage пишет на локальный диск. Поменяйте одну строку — и большие выводы инструментов попадают в настоящий S3-бакет, вызываются по точной ссылке, никогда не в окне:
from strands.vended_plugins.context_offloader import ContextOffloader, S3Storage
agent = Agent(
model=MODEL,
tools=[fetch_application_logs, count_errors_by_service],
plugins=[
ContextOffloader(
S3Storage(bucket=CONTEXT_BUCKET, prefix="log-artifacts/")
)
],
)Датасет логов размером 83KB был сохранён в S3, в контексте осталось ~486 токенов, данные вернулись байт-в-байт по точной ссылке:
📊 Tokens left in LLM context: 486
📦 Objects offloaded to S3: 1
pointer in context: s3://…/log-artifacts/1781569100199_1_call_…_0
storage.retrieve() → 77,050 bytes (text/plain)
verified: 200 log events recovered verbatim — exact data, no lossЭто вторая строка таблицы в производственной форме: поиск по точному идентификатору. Вам не нужны «логи, наиболее похожие на мой запрос». Вам нужны те логи, точно. Это объектное хранилище, а не семантический поиск.
В продакшне разделение становится архитектурой. Агент на Amazon Bedrock AgentCore держит каждую память на своём месте:
RetrieveMemoryRecords: эмбеддинги, top_k, оценка релевантности). Ограничено пользователем через actor_id. Подключается через Strands AgentCoreMemorySessionManager.ContextOffloader с S3Storage вместо FileStorage. Поиск по точной ссылке.Почему не класть логи тоже в AgentCore Memory? Потому что AgentCore Memory возвращает семантически наиболее похожую память, а это неправильно для задачи «верни этот датасет по идентификатору». Разговору нужен смысл; данным — точный ключ. Один агент, две памяти, каждая делает то, что умеет лучше всего.
agent = Agent(
model=BedrockModel(region_name=REGION),
tools=[fetch_application_logs, count_errors_by_service],
session_manager=AgentCoreMemorySessionManager(memory_config, REGION), # разговор
plugins=[
ContextOffloader(S3Storage(bucket=CONTEXT_BUCKET, prefix="…")), # данные
],
)На AgentCore полная наблюдаемость встроена. Вы добавляете библиотеку инструментирования и получаете трейсы, метрики и логи для каждого вызова без написания кода мониторинга. Развёртывание уже включает это: агент отправляет OpenTelemetry (OTEL) трейсы и метрики в пространстве имён bedrock-agentcore, а дашборд CloudWatch GenAI Observability показывает представления агента, сессии и трейсов (латентность, частота ошибок, использование токенов, вызовы инструментов) прямо из коробки.
Так я диагностировал ошибку разрешения ListEvents за секунды: неудачный трейс был прямо в CloudWatch, без дополнительной настройки. См. View observability data for AgentCore agents.
Та же инструментировка питает AgentCore Evaluations: автоматизированное, LLM-as-a-Judge оценивание выполнения задач и точности вызовов инструментов на основе тех же трейсов. Вы можете измерять качество агента непрерывно, а не только при запуске.
ContextOffloader(FileStorage(...)). Обычные инструменты, никакого кода указателей.context_manager="auto". Суммаризация + выгрузка в одной строке.ContextOffloader(S3Storage(...)) для данных. Держите их раздельно.В любом случае: сочетайте оффлоадер с селективными инструментами, которые возвращают сводки, а не сырые блобы. Оффлоадер предотвращает переполнение; селективные инструменты удерживают низкое количество токенов.
Вам понадобится Python 3.11+, uv и OPENAI_API_KEY (или замените модель на BedrockModel). Для шагов с S3 и AgentCore также нужны AWS-учётные данные.
git clone https://github.com/aws-samples/sample-why-agents-fail
cd sample-why-agents-fail/stop-ai-agents-wasting-tokens/01-context-overflow-demo
uv venv && uv pip install -r requirements.txt
uv run python test_native_pointer.py # локальное сравнение токенов
AWS_PROFILE=you uv run python test_s3_offload_local.py
# Продакшн-развёртывание + пошаговое руководство по двум памятям: setup_agentcore_s3.ipynbНоутбуки: test_native_pointer.ipynb (локально) и setup_agentcore_s3.ipynb (провижн + деплой + вызов на AWS).
ContextOffloader — это паттерн Memory Pointer как плагин; инструменты остаются обычными функциями.Нужен ли ContextOffloader'у AWS? Нет. С FileStorage или InMemoryStorage он работает полностью локально. AWS нужен только при выборе S3Storage или развёртывании на AgentCore.
Можно ли хранить большие файлы в AgentCore Memory вместо S3? Можно, но не стоит. AgentCore Memory ищет по семантическому сходству, поэтому возвращает наиболее похожую память, а не точный файл. Большим выводам инструментов нужен поиск по точному идентификатору — это даёт S3 (через ContextOffloader).
Нужен ли Docker для развёртывания на AgentCore? Нет. Стартовый набор собирает образ в облаке с AWS CodeBuild по умолчанию. Docker нужен только для локальной сборки.
В чём разница между agent.state и ContextOffloader? agent.state — это ручной паттерн Memory Pointer: вы пишете и читаете указатели внутри инструментов. ContextOffloader — та же идея как плагин: инструменты остаются обычными, а фреймворк выгружает большие результаты за вас.
Какая из моих двух памятей тратит токены? Память данных. Память разговора — это маленький текст; взрыв токенов происходит из-за больших выводов инструментов, которые едут в контексте. Эту память и исправляет ContextOffloader.
Какая из двух памятей вашего агента тратит токены? Напишите в комментариях.
Исследования
Реализация
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →