Skip to content

Hour 4: 压缩机制


一、概述

当对话历史太长时,Hermes 会自动压缩历史消息,用 LLM 生成的摘要替代原始消息。

目的: 避免超出模型的上下文窗口限制。


二、压缩触发条件

判断逻辑

python
# conversation_loop.py:4126
if agent.compression_enabled and _compressor.should_compress(_real_tokens):
    messages, active_system_prompt = agent._compress_context(...)

should_compress() 逻辑

python
# context_compressor.py:823
def should_compress(self, prompt_tokens: int = None) -> bool:
    tokens = prompt_tokens if prompt_tokens is not None else self.last_prompt_tokens
    
    # 条件 1: 超过阈值
    if tokens < self.threshold_tokens:
        return False
    
    # 条件 2: 防抖 — 最近压缩效果差(<10% 节省)则跳过
    if self._ineffective_compression_count >= 2:
        return False
    
    return True

阈值计算:

python
threshold_tokens = context_length * threshold_percent  # 默认 80%
threshold_tokens = max(threshold_tokens, MINIMUM_CONTEXT_LENGTH)

三、压缩流程

整体流程图

上下文太长 (should_compress = True)


compress_context()  ← conversation_compression.py:281

    ├── 1. 检查压缩可行性
    │       │
    │       └── aux model 可用?

    ├── 2. 获取压缩锁
    │       │
    │       └── 防止并发压缩(多个 agent 同时压缩同一 session)

    ├── 3. 通知 memory manager
    │       │
    │       └── agent._memory_manager.on_pre_compress(messages)

    ├── 4. 调用 context_compressor.compress()
    │       │
    │       └── 使用 LLM 生成摘要

    ├── 5. 压缩成功?
    │       ├── 是 → 用摘要替换历史,保存到新 session
    │       └── 否 → 返回原始消息,不压缩

    └── 6. 重建 system prompt

详细步骤

1. 压缩可行性检查(懒加载)

python
# conversation_compression.py:321
if not getattr(agent, "_compression_feasibility_checked", False):
    check_compression_model_feasibility(agent)
    agent._compression_feasibility_checked = True

首次压缩时才检查,节省 ~400ms 冷启动时间。

2. 压缩锁

防止多个 agent 同时压缩同一 session:

python
_lock_db = agent._session_db
_lock_acquired = _lock_db.try_acquire_compression_lock(_lock_sid, _lock_holder)
if not _lock_acquired:
    return messages, _existing_sp  # 放弃本次压缩

3. 调用压缩器

python
compressed = agent.context_compressor.compress(
    messages,
    current_tokens=approx_tokens,
    focus_topic=focus_topic,
    force=force
)

4. 处理结果

  • 压缩成功 → 用摘要替换历史
  • 压缩失败 → 返回原始消息,不丢失数据

四、ContextCompressor 算法

压缩算法详解

python
class ContextCompressor(ContextEngine):
    """
    算法:
      1. 修剪旧工具结果(无 LLM 调用)
      2. 保护头部消息(system prompt + 首次对话)
      3. 保护尾部消息(最近 ~20K tokens)
      4. 用 LLM 总结中间的消息
      5. 后续压缩时迭代更新摘要
    """

步骤 1: 工具输出修剪

python
# context_compressor.py:849
def _prune_old_tool_results(messages, protect_tail_count, protect_tail_tokens):
    """
    用摘要替换旧的工具结果内容
    例如:
      [terminal] ran `npm test` -> exit 0, 47 lines output
      [read_file] read config.py from line 1 (3,400 chars)
    """

步骤 2-3: 保护头尾

  • 头部保护: system prompt + 第一轮对话
  • 尾部保护: 最近 ~20K tokens(或最后 N 条消息)

步骤 4: LLM 摘要

用辅助模型(cheap/fast)生成结构化摘要:

python
SUMMARY_PREFIX = (
    "[CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted "
    "into the summary below. This is a handoff from a previous context "
    "window — treat it as background reference, NOT as active instructions. "
    ...
")

步骤 5: 迭代更新

后续压缩时,LLM 会更新之前的摘要而非重新生成。


五、原地压缩 vs Session 旋转

两种模式

模式配置行为
原地压缩compression.in_place = True保持 session_id 不变
Session 旋转compression.in_place = False创建新 session_id

原地压缩(默认关闭)

python
in_place = bool(getattr(agent, "compression_in_place", False))

if in_place:
    # 软存档旧消息,插入压缩后的消息
    # session_id 保持不变
    session.archive_and_compact(compressed, ...)
else:
    # 结束旧 session,创建新 session
    session.end_session(...)
    new_session = session.create_session(...)

六、压缩失败处理

失败场景

  1. Aux model 不可用 → 返回原始消息
  2. LLM 生成摘要失败 → 返回原始消息,显示警告
  3. 并发锁冲突 → 放弃本次压缩,下次重试

失败不丢失数据

python
# conversation_compression.py:469
if getattr(agent.context_compressor, "_last_compress_aborted", False):
    return messages, _existing_sp  # 返回原始消息

七、手动触发压缩

用户可以手动触发压缩:

bash
/compress              # 普通压缩
/compress <topic>      # 聚焦压缩(保留与 topic 相关的内容)

force=True 绕过摘要失败冷却时间。


八、关键文件位置

功能文件:行号
压缩入口conversation_compression.py:281
ContextCompressorcontext_compressor.py:593
触发判断context_compressor.py:823
工具修剪context_compressor.py:849
压缩调用conversation_loop.py:4126

九、配置项

yaml
# config.yaml
auxiliary:
  compression:
    model: gpt-4o-mini  # 用于摘要的辅助模型

compression:
  enabled: true
  threshold_percent: 0.8  # 80% 上下文时触发
  in_place: false  # 是否原地压缩

总结

Hour 4 核心理解:

  • 压缩在上下文超过阈值时自动触发(默认 80%)
  • 使用独立的辅助模型生成摘要
  • 保护头部和尾部消息,只压缩中间部分
  • 压缩失败不丢失数据,返回原始消息
  • 支持原地压缩(不创建新 session)

基于 VitePress 重建