Compression And Caching
On this page
Hermes Agent использует двойную систему сжатия и кеширование промптов Anthropic для эффективного управления использованием контекстного окна в длительных разговорах.
Исходные файлы: agent/context_engine.py (ABC), agent/context_compressor.py (движок по умолчанию), agent/prompt_caching.py, gateway/run.py (гигиена сессий), run_agent.py (поиск _compress_context)
Подключаемый движок контекста¶
Управление контекстом построено на ABC ContextEngine (agent/context_engine.py). Встроенный ContextCompressor — это реализация по умолчанию, но плагины могут заменять его альтернативными движками (например, Lossless Context Management).
[code]
context:
engine: "compressor" # по умолчанию — встроенное сжатие с потерей информации
engine: "lcm" # пример — плагин, обеспечивающий контекст без потерь
[/code]
Движок отвечает за:
* Решение о том, когда запускать уплотнение (should_compress())
* Выполнение уплотнения (compress())
* Опциональное предоставление инструментов, которые агент может вызывать (например, lcm_grep)
* Отслеживание использования токенов из ответов API
Выбор осуществляется через конфигурацию context.engine в config.yaml. Порядок разрешения:
1. Проверка каталога plugins/context_engine/<name>/
2. Проверка общей системы плагинов (register_context_engine())
3. Возврат к встроенному ContextCompressor
Плагины движков никогда не активируются автоматически — пользователь должен явно установить context.engine на имя плагина. Значение "compressor" по умолчанию всегда использует встроенный движок.
Настройка через hermes plugins → Provider Plugins → Context Engine, или напрямую в config.yaml.
Для создания плагина движка контекста см. Плагины движка контекста.
Двойная система сжатия¶
Hermes имеет два отдельных слоя сжатия, работающих независимо:
[code]
┌──────────────────────────┐
Входящее сообщение │ Гигиена сессий Gateway │ Срабатывает при 85% контекста
─────────────────► │ (до агента, грубая оценка) │ Предохранитель для больших сессий
└─────────────┬────────────┘
│
▼
┌──────────────────────────┐
│ Агент ContextCompressor │ Срабатывает при 50% контекста (по умолч.)
│ (в цикле, реальные токены) │ Обычное управление контекстом
└──────────────────────────┘
[/code]
1\. Гигиена сессий Gateway (порог 85%)¶
Находится в gateway/run.py (поиск Session hygiene: auto-compress). Это предохранитель, который срабатывает до того, как агент обработает сообщение. Он предотвращает ошибки API, когда сессии становятся слишком большими между оборотами (например, ночное накопление в Telegram/Discord).
* Порог: Фиксирован на 85% длины контекста модели
* Источник токенов: Предпочитает фактические токены, сообщённые API с последнего оборота; при отсутствии использует грубую оценку на основе символов (estimate_messages_tokens_rough)
* Срабатывает: Только когда len(history) >= 4 и сжатие включено
* Назначение: Перехват сессий, которые избежали собственного компрессора агента
Порог гигиены Gateway намеренно выше, чем у компрессора агента. Установка его на 50% (как у агента) приводила к преждевременному сжатию на каждом обороте в длинных сессиях Gateway.
2\. Агент ContextCompressor (порог 50%, настраиваемый)¶
Находится в agent/context_compressor.py. Это основная система сжатия, работающая внутри цикла инструментов агента с доступом к точным подсчётам токенов, сообщённым API.
Конфигурация¶
Все настройки сжатия читаются из config.yaml под ключом compression:
[code]
compression:
enabled: true # Включение/отключение сжатия (по умолчанию: true)
threshold: 0.50 # Доля контекстного окна (по умолчанию: 0.50 = 50%)
target_ratio: 0.20 # Сколько от порога оставить в хвосте (по умолчанию: 0.20)
protect_last_n: 20 # Минимальное количество защищённых последних сообщений (по умолчанию: 20)
# Модель/провайдер суммаризации настраивается под auxiliary:
auxiliary:
compression:
model: null # Переопределение модели для суммаризации (по умолчанию: автоопределение)
provider: auto # Провайдер: "auto", "openrouter", "nous", "main" и т.д.
base_url: null # Пользовательская endpoint, совместимый с OpenAI
[/code]
Детали параметров¶
Параметр| По умолчанию| Диапазон| Описание
---|---|---|---|---
threshold| 0.50| 0.0-1.0| Сжатие срабатывает, когда токенов промпта ≥ threshold × context_length
target_ratio| 0.20| 0.10-0.80| Управляет бюджетом токенов для защиты хвоста: threshold_tokens × target_ratio
protect_last_n| 20| ≥1| Минимальное количество последних сообщений, всегда сохраняемых
protect_first_n| 3| (жёстко задано)| Системный промпт + первый обмен всегда сохраняются
Расчётные значения (для модели с контекстом 200K при настройках по умолчанию)¶
[code]
context_length = 200,000
threshold_tokens = 200,000 × 0.50 = 100,000
tail_token_budget = 100,000 × 0.20 = 20,000
max_summary_tokens = min(200,000 × 0.05, 12,000) = 10,000
[/code]
Алгоритм сжатия¶
Метод ContextCompressor.compress() следует 4-фазному алгоритму:
Фаза 1: Очистка старых результатов инструментов (дёшево, без вызова LLM)¶
Старые результаты инструментов (>200 символов) за пределами защищённого хвоста заменяются на: [code] [Old tool output cleared to save context space]
[/code] Это дешёвый предварительный проход, который экономит значительное количество токенов от подробного вывода инструментов (содержимое файлов, вывод терминала, результаты поиска).
Фаза 2: Определение границ¶
[code]
┌─────────────────────────────────────────────────────────────┐
│ Список сообщений │
│ │
│ [0..2] ← protect_first_n (системное + первый обмен) │
│ [3..N] ← средние обороты → СУММАРИЗИРУЮТСЯ │
│ [N..end] ← хвост (по бюджету токенов ИЛИ protect_last_n) │
│ │
└─────────────────────────────────────────────────────────────┘
[/code]
Защита хвоста основана на бюджете токенов: проходит назад от конца, накапливая токены, пока бюджет не исчерпан. Возвращается к фиксированному счётчику protect_last_n, если бюджет защитил бы меньше сообщений.
Границы выравниваются, чтобы не разрывать группы tool_call/tool_result. Метод _align_boundary_backward() проходит последовательные результаты инструментов назад, чтобы найти родительское сообщение ассистента, сохраняя группы целыми.
Фаза 3: Генерация структурированной сводки¶
Длина контекста модели сводки
Модель для сводки должна иметь контекстное окно как минимум такого же размера, как у основной модели агента. Весь средний раздел отправляется модели сводки в одном вызове call_llm(task="compression"). Если контекст модели сводки меньше, API возвращает ошибку длины контекста — _generate_summary() перехватывает её, логирует предупреждение и возвращает None. В этом случае компрессор отбрасывает средние обороты без сводки, что приводит к тихой потере контекста разговора. Это наиболее частая причина ухудшения качества уплотнения.
Средние обороты суммаризируются с использованием вспомогательной LLM и структурированного шаблона:
[code]
## Goal (Цель)
[Чего пользователь пытается достичь]
## Constraints & Preferences (Ограничения и предпочтения)
[Предпочтения пользователя, стиль кодирования, ограничения, важные решения]
## Progress (Прогресс)
### Done (Сделано)
[Выполненная работа — конкретные пути файлов, выполненные команды, результаты]
### In Progress (В процессе)
[Работа, ведущаяся в данный момент]
### Blocked (Заблокировано)
[Любые блокеры или возникшие проблемы]
## Key Decisions (Ключевые решения)
[Важные технические решения и их обоснование]
## Relevant Files (Релевантные файлы)
[Файлы, прочитанные, изменённые или созданные — с кратким примечанием о каждом]
## Next Steps (Следующие шаги)
[Что нужно сделать дальше]
## Critical Context (Критический контекст)
[Конкретные значения, сообщения об ошибках, детали конфигурации]
[/code]
Бюджет сводки масштабируется в зависимости от объёма сжимаемого содержимого:
* Формула: content_tokens × 0.20 (константа _SUMMARY_RATIO)
* Минимум: 2,000 токенов
* Максимум: min(context_length × 0.05, 12,000) токенов
Фаза 4: Сборка сжатых сообщений¶
Список сжатых сообщений выглядит так: 1. Головные сообщения (с примечанием, добавленным к системному промпту при первом сжатии) 2. Сообщение сводки (роль выбирается для избежания нарушения последовательности одинаковых ролей) 3. Хвостовые сообщения (без изменений)
Пары tool_call/tool_result оставшиеся без пары очищаются _sanitize_tool_pairs():
* Результаты инструментов, ссылающиеся на удалённые вызовы → удаляются
* Вызовы инструментов, чьи результаты были удалены → вставляется заглушка результата
Итеративное повторное сжатие¶
При последующих сжатиях предыдущая сводка передаётся LLM с инструкцией обновить её, а не суммаризировать с нуля. Это сохраняет информацию через несколько уплотнений — элементы перемещаются из «В процессе» в «Сделано», добавляется новый прогресс, удаляется устаревшая информация.
Поле _previous_summary в экземпляре компрессора хранит последний текст сводки для этой цели.
Пример до/после¶
До сжатия (45 сообщений, ~95K токенов)¶
[code]
[0] system: "You are a helpful assistant..." (системный промпт)
[1] user: "Help me set up a FastAPI project"
[2] assistant:
[3] tool: "directory created"
[4] assistant:
[5] tool: "file written (2.3KB)"
... ещё 30 оборотов редактирования файлов, тестирования, отладки ...
[38] assistant:
[39] tool: "8 passed, 2 failed\n..." (5KB вывода)
[40] user: "Fix the failing tests"
[41] assistant:
[42] tool: "import pytest\n..." (3KB)
[43] assistant: "I see the issue with the test fixtures..."
[44] user: "Great, also add error handling"
[/code]
После сжатия (25 сообщений, ~45K токенов)¶
[code]
[0] system: "You are a helpful assistant...
[Note: Some earlier conversation turns have been compacted...]"
[1] user: "Help me set up a FastAPI project"
[2] assistant: "[CONTEXT COMPACTION] Earlier turns were compacted...
## Goal (Цель)
Set up a FastAPI project with tests and error handling
## Progress (Прогресс)
### Done (Сделано)
- Created project structure: main.py, tests/, requirements.txt
- Implemented 5 API endpoints in main.py
- Wrote 10 test cases in tests/test_api.py
- 8/10 tests passing
### In Progress (В процессе)
- Fixing 2 failing tests (test_create_user, test_delete_user)
## Relevant Files (Релевантные файлы)
- main.py — FastAPI app with 5 endpoints
- tests/test_api.py — 10 test cases
- requirements.txt — fastapi, pytest, httpx
## Next Steps (Следующие шаги)
- Fix failing test fixtures
- Add error handling"
[3] user: "Fix the failing tests"
[4] assistant: <tool_call> read_file: tests/test_api.py </tool_call>
[5] tool: "import pytest\n..."
[6] assistant: "I see the issue with the test fixtures..."
[7] user: "Great, also add error handling"
[/code]
Кеширование промптов (Anthropic)¶
Исходный код: agent/prompt_caching.py
Снижает затраты на входные токены примерно на 75% в многоповоротных разговорах путём кеширования префикса разговора. Использует точки останова cache_control от Anthropic.
Стратегия: system_and_3¶
Anthropic допускает максимум 4 точки останова cache_control на запрос. Hermes использует стратегию «system_and_3»:
[code]
Точка останова 1: Системный промпт (стабилен на всех оборотах)
Точка останова 2: 3-е с конца несистемное сообщение ─┐
Точка останова 3: 2-е с конца несистемное сообщение ├─ Скользящее окно
Точка останова 4: Последнее несистемное сообщение ─┘
[/code]
Как это работает¶
apply_anthropic_cache_control() делает глубокую копию сообщений и вставляет маркеры cache_control:
[code]
# Формат маркера кеша
marker = {"type": "ephemeral"}
# Или с TTL в 1 час:
marker = {"type": "ephemeral", "ttl": "1h"}
[/code]
Маркер применяется по-разному в зависимости от типа содержимого:
Тип содержимого| Куда помещается маркер
---|---
Строковое содержимое| Преобразуется в [{"type": "text", "text": ..., "cache_control": ...}]
Списочное содержимое| Добавляется в словарь последнего элемента
None/пусто| Добавляется как msg["cache_control"]
Сообщения инструментов| Добавляется как msg["cache_control"] (только нативный Anthropic)
Паттерны проектирования с учётом кеша¶
- Стабильный системный промпт: Системный промпт — это точка останова 1, кешируется на всех оборотах. Избегайте его изменения в середине разговора (сжатие добавляет примечание только при первом уплотнении).
- Порядок сообщений имеет значение: Попадания в кеш требуют совпадения префикса. Добавление или удаление сообщений в середине инвалидирует кеш для всего последующего.
- Взаимодействие сжатия и кеша: После сжатия кеш инвалидируется для сжатой области, но кеш системного промпта сохраняется. Скользящее окно из 3 сообщений восстанавливает кеширование в течение 1-2 оборотов.
- Выбор TTL: По умолчанию
5m(5 минут). Используйте1hдля длительных сессий, где пользователь делает перерывы между оборотами.
Включение кеширования промптов¶
Кеширование промптов автоматически включается, когда:
* Модель является моделью Anthropic Claude (определяется по имени модели)
* Провайдер поддерживает cache_control (нативный API Anthropic или OpenRouter)
[code]
# config.yaml — TTL настраивается (должно быть "5m" или "1h")
prompt_caching:
cache_ttl: "5m"
[/code] CLI показывает статус кеширования при запуске: [code] 💾 Prompt caching: ENABLED (Claude via OpenRouter, 5m TTL)
[/code]
Предупреждения о давлении контекста¶
Промежуточные предупреждения о давлении контекста были удалены (см. блок итерационного бюджета в run_agent.py, где отмечено: «Нет промежуточных предупреждений о давлении — они заставляли модели «сдаваться» преждевременно при выполнении сложных задач»). Сжатие срабатывает, когда токены промпта достигают настроенного compression.threshold (по умолчанию 50%) без предварительного этапа предупреждения; гигиена сессий Gateway срабатывает как вторичный предохранитель при 85% контекстного окна модели.
* Подключаемый движок контекста
* Двойная система сжатия
* 1\. Гигиена сессий Gateway (порог 85%)
* 2\. Агент ContextCompressor (порог 50%, настраиваемый)
* Конфигурация
* Детали параметров
* Расчётные значения (для модели с контекстом 200K при настройках по умолчанию)
* Алгоритм сжатия
* Фаза 1: Очистка старых результатов инструментов (дёшево, без вызова LLM)
* Фаза 2: Определение границ
* Фаза 3: Генерация структурированной сводки
* Фаза 4: Сборка сжатых сообщений
* Итеративное повторное сжатие
* Пример до/после
* До сжатия (45 сообщений, ~95K токенов)
* После сжатия (25 сообщений, ~45K токенов)
* Кеширование промптов (Anthropic)
* Стратегия: system_and_3
* Как это работает
* Паттерны проектирования с учётом кеша
* Включение кеширования промптов
* Предупреждения о давлении контекста