跳转到内容

08 - 上下文压缩策略

学习目标

本章聚焦 Hermes Agent 的上下文压缩实现。你将了解:

  • 预飞行压缩为何在进入主循环前就执行
  • 轨迹压缩如何用于训练下一代工具调用模型
  • 对话中间 N turn 摘要策略
  • 用户手动触发压缩的反馈机制

前置知识

本章涉及上下文压缩的通用原理,建议先阅读:

下文直接聚焦 Hermes 的具体实现。


项目实践

预飞行上下文压缩

Hermes 在进入主循环前,检测当前消息是否已超出模型上下文窗口:

if len(messages) > protect_first_n + protect_last_n + 1:
tokens = estimate_request_tokens_rough(messages, system_prompt, tools)
if compressor.should_compress(tokens):
for _pass in range(3):
messages, system_prompt = compress(...)
if len(messages) >= _orig_len: break # 无法继续压缩

为何预飞行? 通用指南中的压缩通常在 API 调用失败(context_window 错误)后触发。Hermes 选择提前检测,避免 API 调用失败导致的延迟和成本浪费。

适用场景:用户切换到小上下文模型时,历史对话可能已经超出新模型的窗口。预飞行压缩在 turn 开始前瘦身,确保 API 调用不会因超限而失败。

多策略压缩管线

Hermes 实现了多层次的压缩策略:

策略位置触发条件目标
预飞行压缩进入主循环前检测 token 超限防止 API 调用失败
对话压缩(conversation_compression)主循环中中间 N turn摘要化历史对话
轨迹压缩(trajectory_compressor)训练 pipeline数据收集阶段精简工具调用轨迹用于训练
手动压缩(manual_compression_feedback)用户触发用户指令按需压缩当前上下文

轨迹压缩(Trajectory Compression)

trajectory_compressor.py 专门用于压缩 Agent 的执行轨迹,目标是训练下一代工具调用模型:

  • 保留完整的工具调用序列结构(调用顺序、参数名)
  • 裁剪工具输出的内容(保留元数据,删除大段文本)
  • 压缩后的轨迹更小,适合大规模训练数据收集

与普通对话压缩的区别:对话压缩服务于运行时的上下文窗口管理;轨迹压缩服务于离线训练的数据精简。

对话中间 N Turn 摘要

agent/conversation_compression.py 实现对话历史中的中间 N turn 摘要策略:

  • 保留最近 N 轮对话的完整内容(tail 保护)
  • 对更早的对话轮次进行 LLM 摘要
  • 用户输入消息永久保留,不被摘要化

问题与规避

预飞行压缩误判

问题:粗糙的 token 估算可能高估或低估实际 token 数,导致不必要的压缩或遗漏超限。

对策:Hermes 使用 estimate_request_tokens_rough() 进行轻量估算。如果估算接近阈值,压缩器尝试压缩;如果压缩后长度未变化(无法压缩),放弃并让 API 调用自然失败或成功。

轨迹压缩丢失关键信息

问题:裁剪工具输出可能删除训练所需的关键细节(如代码错误的具体行号)。

对策:轨迹压缩保留工具调用的元数据(函数名、参数名、返回状态码),只裁剪内容体。对于关键工具(如代码执行),保留输出前 N 行。


设计取舍

预飞行压缩 vs 延迟压缩

方案优势代价
预飞行(Hermes)API 调用不失败、用户体验连贯可能多一次压缩开销
延迟(失败后)只在确实需要时压缩API 调用失败重试、延迟增加

Hermes 选择预飞行压缩,因为 API 调用失败的成本(额外延迟 + 可能的状态不一致)高于提前压缩的成本。

轨迹压缩的保留策略

方案优势代价
保留结构,裁剪内容训练数据紧凑,保留调用序列丢失输出细节
完整保留训练数据完整token 消耗大、存储成本高

Hermes 选择保留结构裁剪内容,因为训练工具调用模型主要需要的是调用序列模式参数结构,而非完整的工具输出内容。


参考来源

  • 源码验证:agent/context_compressor.pytrajectory_compressor.pyagent/conversation_compression.pyagent/manual_compression_feedback_feedback.py