Парадокс контекстного окна: чем длиннее документ, тем тупее LLM
Помните тот душераздирающий момент, когда вы скормили Claude 200-страничный контракт, а он через 50 токенов начал "забывать" начало? Формально модель обработала все токены — но качество ответа на последних страницах резко падает. Это не баг, это физика: даже у самых продвинутых моделей (на май 2026 — Amazon Nova 3 с контекстом 256K, GPT-5 с 512K) стоимость внимания растёт квадратично, а точность на удалённых токенах плавно стремится к нулю.
Типичные решения — RAG, чанкование, map-reduce — работают, но ломают целостность. Если документ требует понимания глобального контекста (юридический анализ, научная статья, аудит), разбиение на куски убивает суть. Именно здесь выручает Recursive Language Models (RLM) — архитектура, которая учит модель "проглатывать" документ целиком, рекурсивно пересказывая его саму себя.
В этой статье я покажу, как собрать production-ready пайплайн на Amazon Bedrock AgentCore с Code Interpreter, который пережуёт документ любого размера (хоть 1000 страниц) и выдаст связный анализ. Без RAG, без внешних vector stores — только чистая рекурсия и немного наглости.
💡 Важное уточнение — мы не изобретаем велосипед. Концепция RLM описана в статье 2024, но до недавнего времени её нельзя было продакшн-реализовать из-за отсутствия инфраструктуры для рекурсивных вызовов. Amazon Bedrock AgentCore + Code Interpreter (апрель 2026) и Strands Agents SDK (релиз 2.0 в марте 2026) наконец-то дают такую возможность “из коробки”.
Как работает RLM и почему это не просто "обобщи чанк"
Идея проста до безобразия: вместо того чтобы засовывать весь документ в одно окно, мы учим модель рекурсивно сжимать информацию. Первый проход — модель читает первую половину документа и пишет её краткое содержание. Второй проход — модель читает это содержание + следующую часть документа, и так далее. На каждом шаге мы не просто отбрасываем старые данные, а трансформируем их в компактное представление, сохраняющее ключевые связи.
Звучит как map-reduce? Только без потери контекста: в map-reduce каждый чанк анализируется изолированно, а потом результаты склеиваются. В RLM каждый следующий шаг видит обобщение предыдущих, а не их сырые данные. Это позволяет модели строить иерархическое понимание — почти как человек, который сначала прочитывает аннотацию, потом главу, потом подглавы.
Почему Bedrock AgentCore — идеальная среда для RLM?
AgentCore изначально проектировался для мультишаговых цепочек действий. Он умеет:
- Вызывать сам себя (рекурсия через действия агента).
- Хранить промежуточные результаты в сессионной памяти (Session State).
- Запускать Code Interpreter с Python-скриптами — идеально для чанкования и токенизации.
- Работать с документами через S3 и Bedrock Knowledge Bases — хотя мы их не используем напрямую, но для загрузки исходника сойдёт.
В сочетании с Strands Agents SDK (специализированный фреймворк для рекурсивных агентов) можно построить цикл: загрузить документ → разбить на части → обобщить часть → зафиксировать в памяти → повторить → выдать финальный ответ. Strands даёт управление глубиной рекурсии и автоматическое обнаружение переполнения стека.
⚠️ Частая ошибка — пытаться делать рекурсию через вызов агента из самого себя без контроля глубины. LLM, зациклившись, может начать галлюцинировать бесконечные summaries. Strands Agents SDK по умолчанию включает guardrails для этого, а в чистом AgentCore придётся прописывать лимиты вручную (см. шаг 3).
Пошаговая реализация: от документа до финального отчёта
1 Настройка Bedrock AgentCore с Code Interpreter и DynamoDB для памяти
Первый шаг — создать агента в Bedrock, который будет иметь права на S3 (для загрузки документа), DynamoDB (для хранения промежуточных summary) и Lambda (если нужно выполнять кастомную токенизацию). Я использую модель Claude 4 Opus (доступна на май 2026) — она имеет одно из лучших соотношений "цена/качество" для рекурсивных задач благодаря длинному контексту в 200K и низкой стоимости токена.
import boto3
import json
bedrock_agent = boto3.client('bedrock-agent')
# Создаём агента с инструкцией для рекурсивной обработки
response = bedrock_agent.create_agent(
agentName='doc-recursive-summarizer',
foundationModel='anthropic.claude-4-opus-20260501',
instruction="""Ты — агент для рекурсивного анализа документов.
Твоя задача:
1. Получить документ из S3.
2. Разбить его на чанки по 30 000 токенов (используя Code Interpreter).
3. Для каждого чанка вызвать под-агента 'summarizer' с целью создания краткого резюме.
4. Сохранить все резюме в DynamoDB.
5. После обработки всех чанков — собрать единый ответ.
Глубина рекурсии: максимум 3 уровня.""",
actionGroups=[
{
'actionGroupName': 'ChunkAndSummarize',
'actionGroupExecutor': 'code-interpreter', # встроенный Code Interpreter
'functionSchema': {
'functions': [
{
'name': 'summarize_chunk',
'description': 'Принимает текст чанка, возвращает summary',
'parameters': {
'chunk': {'type': 'string'}
}
}
]
}
}
]
)
print(f"Агент создан: {response['agent']['agentId']}")
Обратите внимание: мы не пишем сложный код для чанкования — Code Interpreter выполнит Python на ходу. Но можно и через Lambda, если нужны библиотеки вроде tiktoken.
2 Код рекурсивного summarizer (Code Interpreter)
Самый важный шаг — логика рекурсии. Мы используем псевдо-рекурсию через вызов агента с новым контекстом. Вот пример, который можно вставить в action 'summarize_chunk':
import json
import boto3
def lambda_handler(event, context):
chunk = event.get('chunk', '')
session_id = event.get('session_id', '')
depth = int(event.get('depth', 0))
if depth > 3:
# возвращаем предыдущий summary, если глубина превышена
return {'summary': '', 'depth': depth}
# 1. Обобщаем текущий чанк
bedrock_runtime = boto3.client('bedrock-runtime')
response = bedrock_runtime.invoke_model(
modelId='anthropic.claude-4-opus-20260501',
body=json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 8000,
"messages": [{
"role": "user",
"content": f"Пожалуйста, сделай краткое резюме следующего текста (не более 2000 слов).\n\n{chunk}"
}]
})
)
summary = json.loads(response['body'].read())['content'][0]['text']
# 2. Сохраняем в DynamoDB (для следующего шага)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('RecursiveSummaries')
table.put_item(Item={
'SessionId': session_id,
'Depth': depth,
'Summary': summary,
'Timestamp': str(datetime.utcnow())
})
# 3. Возвращаем summary и увеличиваем глубину
return {'summary': summary, 'depth': depth + 1}
Важно: агент Bedrock при следующем вызове сможет прочитать предыдущие summary из DynamoDB и использовать их как контекст. Именно это даёт эффект рекурсивного сжатия.
3 Оркестрация через Strands Agents SDK (контроль глубины и избежание циклов)
Чистый AgentCore требует ручного управления циклом. Strands Agents SDK (версия 2.0) добавляет RecursiveManager, который автоматически:
- Отслеживает глубину рекурсии.
- Проверяет, не повторяются ли summary (дублирование).
- Прерывает цикл, если модель начала галлюцинировать (метрики уверенности).
Пример конфигурации:
from strands.agents import Agent, RecursiveConfig
from strands.bedrock import BedrockAdapter
agent = Agent(
model="anthropic.claude-4-opus-20260501",
adapter=BedrockAdapter(),
recursive_config=RecursiveConfig(
max_depth=3,
redundancy_check=True, # отсекает одинаковые summary
converge_on_repetition=True # если summary не меняется — завершаем
)
)
# Запуск: передаём S3 URI документа
result = agent.run(
document="s3://my-bucket/500page_report.pdf",
output_format="full_analysis"
)
print(result.final_summary)
Strands SDK также умеет генерировать промежуточные чанки с помощью встроенного Code Interpreter Bedrock, что снижает latency.
Подводные камни и как на них не наступить
1. "Зацикливание" агента
Если модель на каждом шаге пересказывает одно и то же — рекурсия не сходится. Причина: слишком большие чанки или слабая инструкция. Решение: чанки по 10-15K токенов, явно указывать в промпте "не повторяй информацию из предыдущего summary, добавляй только новое".
2. Потеря деталей на большой глубине
RLM сжимает информацию — это хорошо для общего понимания, но если нужны цитаты и конкретные цифры, глубина больше 2 может потерять их. Стратегия: сохранять raw-чанки в DynamoDB и на последнем шаге давать модели ссылку на таблицу с цитатами. Или использовать гибридный подход RAG, где RLM даёт контекст, а RAG — точные факты.
3. Стоимость
Каждый рекурсивный вызов — это отдельный запрос к модели. Для документа в 500 страниц (примерно 250K токенов) с чанками по 30K потребуется ~8 шагов. 8 * 8000 выходных токенов = 64K токенов + входные. На Claude 4 Opus это около $0.5. Недорого, но для тысяч документов — уже серьёзно. Используйте меньшие модели для промежуточных summary (например, Nova Express — дешевле в 3 раза).
Реальные примеры из практики (май 2026)
В одном из недавних проектов мы внедрили описанный пайплайн для обработки юридических due diligence документов (связка мультиязычной поддержки и RLM). Документы на трёх языках — английский, немецкий, португальский. Strands SDK автоматически детектировал язык каждого чанка и переключал промпты. В результате анализатор находил скрытые клаузулы, которые раньше упускали юристы.
Другой кейс — обработка логов Kubernetes за месяц (10 млн строк). Вместо того чтобы засовывать все в векторную БД, мы рекурсивно обобщили тренды — нашли причину OutOfMemory после деплоя новой версии. Решение заняло 10 минут и $3.
FAQ: коротко о главном
Чем RLM отличается от простого map-reduce?
Map-reduce анализирует каждый чанк изолированно, затем агрегирует. RLM передаёт обобщение предыдущих чанков в следующий шаг — это даёт целостное понимание, особенно когда информация связана через весь документ.
Какой максимальный размер документа можно обработать?
Теоретически без ограничений. Практически — зависит от бюджета и лимитов AgentCore (максимальная длительность выполнения 1 час). На 2000 страниц уходит около 15 рекурсивных шагов, что укладывается в 40 минут.
Нужен ли отдельный экземпляр DynamoDB для памяти?
Да, мы используем её как хранилище промежуточных summary. Можно также использовать Redis или сессионную память AgentCore, но там есть лимит на размер (400KB для сессии). Для больших документов DynamoDB надёжнее.
Итоговый совет, который вы не прочитаете в документации
Не пытайтесь скормить рекурсивному агенту весь Raw Data сразу. Сначала пропустите документ через Structured Outputs — попросите модель извлечь ключевые сущности и факты. Это даст «скелет», по которому RLM будет строить полотно. Без этой структуры рекурсия часто проваливается: модель обобщает обобщение обобщения... и получается каша. Сначала факты, потом понимание, потом ответ. Убедитесь, что ваш AgentCore умеет делать валидные JSON-ответы — на них проще строить рекурсию.
И помните: контекстное окно растёт, но человеческая потребность в понимании целого — остаётся. RLM не просто обходной путь, это шаг к настоящему «чтению» машиной. Если обычный RAG — это быстрое «ctrl+F», то RLM — это вдумчивый пересказ. Выбирайте инструмент под задачу.