Appearance
Hermes 的上下文压缩不是简单的截断,而是五阶段结构化算法:先裁剪旧工具输出(无需 LLM),再保护头尾消息,然后用 LLM 生成结构化摘要压缩中间段,多次压缩时迭代更新而非从头重写。关键设计是「摘要模板」——7 个固定章节确保每次压缩后 AI 都能找到继续工作所需的所有上下文。
Hermes Agent 上下文压缩机制深度解析:五阶段算法与迭代摘要策略
上下文管理是长对话 AI Agent 的核心工程问题。对话越长,需要的 token 越多,成本越高,而且超过模型的 context length 后对话就断了。
大多数工具的解法是:限制历史消息条数(简单但损失信息)或者直接截断(最简单但最粗暴)。Hermes 选择了更复杂的路径:结构化压缩 + 迭代摘要更新。
什么时候触发压缩
python
threshold_percent: float = 0.50 # 默认:上下文使用率达到 50% 触发Hermes 在每次 API 调用后检查 token 使用量,达到阈值就触发压缩。50% 看起来很激进,但有两个原因:
- 压缩需要空间:压缩后的摘要本身要占一定 token,太晚触发会没有足够空间放摘要
- 提前压缩可以保留更多有效上下文:70~80% 时才压缩,能压缩的历史消息已经不多了
还有一个 pre-flight 检查(在 API 调用前的粗略估算),避免发出已经超限的请求:
python
def should_compress_preflight(self, messages):
rough_estimate = estimate_messages_tokens_rough(messages)
return rough_estimate >= self.threshold_tokens五阶段压缩算法
阶段 1:工具输出裁剪(最廉价,无 LLM)
目标:清除旧的工具调用结果,用占位符替换。
python
_PRUNED_TOOL_PLACEHOLDER = "[Old tool output cleared to save context space]"判断逻辑:
- 只裁剪
role == "tool"的消息 - 保护「尾部 token 预算」内的消息(最近的 ~20K token 不动)
- 只裁剪内容超过 200 字符的工具输出(小输出留着,裁剪收益不大)
为什么先做这步?工具输出通常很大(整个文件内容、搜索结果的完整 JSON、命令输出),而且往往是一次性信息(AI 已经处理过了,不需要再看)。裁剪后上下文可能已经够用,就不用调 LLM 做摘要了。
阶段 2:保护「头部」消息
python
protect_first_n: int = 3 # 默认保护前 3 条消息系统提示 + 第一轮对话(通常是任务描述)不参与压缩。原因:第一轮消息通常包含最关键的上下文信息(你要我做什么、用什么约束),丢失这些会让 AI 失去方向。
阶段 3:确定「尾部」保护范围(基于 token 预算)
python
protect_last_n: int = 20 # 默认保护最近 20 条消息
# 但同时有 token 预算限制:tail_token_budget保护范围用 token 预算而不是固定消息数:从尾部往前数,累积 token 直到达到预算上限。这样对话里有大量长消息时,保护的消息数会少一些;短消息多时保护范围更大。比固定条数更合理。
阶段 4:生成结构化 LLM 摘要
中间的消息(头部之后、尾部之前)被送给一个「辅助 LLM」(通常是比主模型便宜的快速模型)生成摘要。
摘要模板(7 个固定章节):
markdown
## Goal
[用户在做什么]
## Constraints & Preferences
[用户偏好、约束、重要决策]
## Progress
### Done / In Progress / Blocked
## Key Decisions
[技术决策和原因]
## Relevant Files
[涉及的文件列表]
## Next Steps
[接下来需要做什么]
## Critical Context
[不保留会丢失的具体数据:错误信息、配置值、数值结果]
## Tools & Patterns
[工具使用方式和发现]摘要 token 预算(和被压缩内容等比例):
python
_SUMMARY_RATIO = 0.20 # 摘要 ≈ 被压缩内容的 20%
_MIN_SUMMARY_TOKENS = 2000
_SUMMARY_TOKENS_CEILING = 12_000被压缩 10,000 tokens 的对话 → 摘要约 2,000 tokens。比例缩放比固定上限更合理:对于大 context 窗口(200K+),固定 8K token 上限意味着摘要只有 4%,很容易丢失细节。
阶段 5:迭代更新(多次压缩时)
这是 Hermes 压缩机制里最精妙的设计:
第一次压缩:从头生成结构化摘要。
后续压缩(self._previous_summary 已存在):不重新摘要,而是更新:
python
if self._previous_summary:
prompt = f"""Update the summary...
PREVIOUS SUMMARY:
{self._previous_summary}
NEW TURNS TO INCORPORATE:
{content_to_summarize}
PRESERVE all existing information that is still relevant.
ADD new progress. Move items from "In Progress" to "Done".
Remove information only if clearly obsolete."""为什么迭代更新比重新摘要更好?
对话经历多次压缩后,如果每次都从头重新摘要,早期的关键决策可能在多次压缩中逐渐丢失(每次压缩都有信息损失,多次叠加的损失是指数级的)。迭代更新策略确保一旦写入「Key Decisions」的内容,会在后续所有压缩中持续保留,除非明确过时了才删除。
压缩失败的降级策略
摘要 LLM 调用失败时(网络错误、API Key 无效等),Hermes 有失败降级策略:
python
_SUMMARY_FAILURE_COOLDOWN_SECONDS = 600 # 失败后 10 分钟内不重试
def _generate_summary(self, turns):
if time.monotonic() < self._summary_failure_cooldown_until:
return None # 冷却期内直接跳过
# ... 尝试生成
except Exception:
self._summary_failure_cooldown_until = time.monotonic() + _SUMMARY_FAILURE_COOLDOWN_SECONDS
return None返回 None 时,调用方直接丢掉中间消息(没有摘要占位),而不是插入一条「摘要失败」的无用消息。这样对话虽然有信息损失,但不会因为失败消息混入上下文而让 AI 困惑。
消息被注入时的前缀
压缩后的摘要以特定前缀注入对话:
python
SUMMARY_PREFIX = (
"[CONTEXT COMPACTION] Earlier turns in this conversation were compacted "
"to save context space. The summary below describes work that was "
"already completed, and the current session state may still reflect "
"that work (for example, files may already be changed). Use the summary "
"and the current state to continue from where things left off, and "
"avoid repeating work:"
)这个前缀的作用:明确告诉后续对话中的 AI「这是压缩摘要,不是对话历史,继续工作不要重复已做的事」。没有这个说明,AI 可能把摘要当成用户发来的新消息处理。
和 Claude Code 的 /compact 对比
| 维度 | Hermes 自动压缩 | Claude Code /compact |
|---|---|---|
| 触发时机 | 自动(50% context 阈值) | 手动(用户运行 /compact) |
| 摘要模型 | 辅助 LLM(便宜快速模型) | Claude 自身 |
| 摘要结构 | 7 章节固定模板 | 自由格式 |
| 迭代更新 | 是(多次压缩累积摘要) | 否(每次重新生成) |
| 工具输出裁剪 | 先做廉价裁剪,再做 LLM 摘要 | 整体 LLM 处理 |
FAQ
Q: 辅助摘要 LLM 用哪个模型?
优先用 summary_model_override(config.yaml 里可以配),没有配置时用和主会话相同的 Provider 的最便宜模型(如 Gemini Flash 或 GPT-4o-mini)。目标是摘要成本远低于被压缩对话的 token 成本。
Q: 压缩会导致 AI 忘记前面说过的事吗?
是的,有信息损失,这是不可避免的权衡。摘要结构的设计目标是最大限度保留「做了什么、为什么这样做、接下来要做什么」——这些对任务连续性最重要。单条对话的细节(比如某次搜索的完整结果)会丢失。
Q: 能关闭自动压缩吗?
可以,设置阈值为 1.0(永远不触发)或者直接关闭配置项。但超过 context length 后会直接 API 报错,那时只能手动清理。