Перейти к содержанию

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: terminal: mkdir project
[3] tool: "directory created"
[4] assistant: write_file: main.py
[5] tool: "file written (2.3KB)"
... ещё 30 оборотов редактирования файлов, тестирования, отладки ...
[38] assistant: terminal: pytest
[39] tool: "8 passed, 2 failed\n..." (5KB вывода)
[40] user: "Fix the failing tests"
[41] assistant: read_file: tests/test_api.py
[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. Стабильный системный промпт: Системный промпт — это точка останова 1, кешируется на всех оборотах. Избегайте его изменения в середине разговора (сжатие добавляет примечание только при первом уплотнении).
  2. Порядок сообщений имеет значение: Попадания в кеш требуют совпадения префикса. Добавление или удаление сообщений в середине инвалидирует кеш для всего последующего.
  3. Взаимодействие сжатия и кеша: После сжатия кеш инвалидируется для сжатой области, но кеш системного промпта сохраняется. Скользящее окно из 3 сообщений восстанавливает кеширование в течение 1-2 оборотов.
  4. Выбор 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 * Как это работает * Паттерны проектирования с учётом кеша * Включение кеширования промптов * Предупреждения о давлении контекста