Hermes Agent 上下文处理机制:智能压缩与分层提示的深度解析
做过 AI Agent 开发的朋友,可能都有过这种体验:
对话一长,模型就开始「失忆」,要么上下文窗口不够用,要么响应越来越慢。
我最近在研究 Hermes Agent 的架构设计,发现它在上下文处理上有一套很有意思的设计:分层系统提示 + 智能上下文压缩。这套方案解决的不只是「塞不进去」的问题,而是让 Agent 在长对话中依然保持高效的推理能力。
今天就来详细拆解一下 Hermes 的上下文处理机制,看看它是怎么做到的。
[[IMG: Digital transformation concept]]
一、为什么上下文处理这么重要?
做 Agent 开发的人都知道,大模型的上下文窗口是有限的。
拿 GPT-4o 来说,虽然有 128K tokens 的上下文,但实际使用时,超过 4 万 tokens 后响应速度就会明显下降。而 Claude 3.5 虽然有 200K tokens,但如果每次都把所有历史对话塞进去,前缀缓存的效率也会大打折扣。
更关键的问题是:对话越长,无关的历史信息就越多,模型越容易被「噪音」干扰,反而影响输出质量。
所以,上下文处理的核心目标不是「塞更多」,而是「塞得更准」。
Hermes 在这个问题上,设计了一套完整的解决方案。
[[IMG: Technology trend concept]]
二、分层系统提示:让缓存命中更高效
Hermes 的系统提示构建,采用了三层架构:
┌─────────────────────────────────────────────────────────────┐
│ Stable Layer(稳定层)— 每个会话构建一次,缓存复用 │
│ - 身份标识、工具指导、技能提示、环境提示 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Context Layer(上下文层)— 依赖 cwd │
│ - AGENTS.md、.cursorrules 等项目配置文件 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Volatile Layer(变动层)— 每轮都变化,**永不缓存** │
│ - 内存快照、用户配置、时间戳 │
└─────────────────────────────────────────────────────────────┘
这样做的好处是什么?
第一,稳定层最大化前缀缓存命中。身份定义、能力说明这些内容每个会话都一样,构建一次就缓存起来,所有后续 API 调用都能复用。对于支持前缀缓存的提供商(比如 Anthropic),这能显著降低延迟和成本。
第二,变动层独立管理。内存快照、用户配置这些每轮都可能变化的内容单独管理,不会污染缓存。
第三,按需重建。只有压缩事件触发后,才重建系统提示,其他时候保持缓存。
这套设计的精髓在于:把「不变」和「变」分离,让缓存真正发挥作用。
[[IMG: Business chart and graph]]
三、ContextCompressor:智能上下文压缩
如果说分层提示是「前端优化」,那 ContextCompressor 就是「后端压缩」。
3.1 压缩算法的五步流程
def compress(self, messages, current_tokens=None, focus_topic=None, force=False):
"""压缩算法:
1. 修剪旧工具结果(无 LLM 调用)
2. 保护头部消息(系统提示 + 前 N 条)
3. 保护尾部消息(按 token 预算)
4. LLM 摘要中间轮次
5. 迭代更新之前的摘要
"""
第一步:工具结果修剪。这一步最巧妙——它不调用 LLM,只是用规则去掉重复的、冗长的工具输出,把大型输出替换成信息性摘要。比如一个 ls 命令返回 100 个文件名,只需要保留「共 100 个文件」这样的摘要。
第二步和第三步:边界保护。头部保留系统提示和前 3 条消息,尾部按 token 预算保护最近的内容(约 20K tokens)。中间的部分才是真正需要压缩的区域。
第四步:LLM 摘要。用结构化模板让 LLM 生成中间轮次的摘要:
## Active Task # 当前最重要的任务
## Goal # 用户总体目标
## Constraints # 约束和偏好
## Completed Actions # 已完成操作列表
## Active State # 当前工作状态
## Key Decisions # 重要技术决策
...
第五步:迭代更新。压缩后的摘要会保存下来,下次压缩时作为上下文提供,实现增量更新而非从头摘要。
3.2 压缩触发时机
Hermes 有三种压缩触发机制:
| 时机 | 触发条件 | 位置 |
|---|---|---|
| 预检压缩 | 用户切换到较小模型时 | conversation_loop.py:474 |
| 轮次后压缩 | API 响应后检测 token 使用率 | conversation_loop.py:3553 |
| 错误触发 | 上下文窗口溢出等错误 | error_classifier.py |
关键设计:压缩阈值默认是 50%。当检测到 token 使用量超过上下文的 50% 时,触发压缩。这个比例可以配置,给了开发者很大的灵活性。
3.3 防抖动机制
连续两次压缩如果节省的 token 不足 10%,则跳过第二次压缩。这避免了在临界点反复压缩的性能损耗。
[[IMG: Data visualization dashboard]]
四、统一入口:所有 LLM 调用的上下文一致性
很多 Agent 实现中,不同模块的 LLM 调用各自处理上下文,容易出现不一致。Hermes 的做法是统一入口:
def build_api_kwargs(agent, api_messages: list) -> dict:
"""Build the keyword arguments dict for the active API mode."""
if agent.api_mode == "anthropic_messages":
return _transport.build_kwargs(...)
elif agent.api_mode == "bedrock_converse":
return _bt.build_kwargs(...)
elif agent.api_mode == "codex_responses":
return _ct.build_kwargs(...)
else: # chat_completions (default)
return _ct.build_kwargs(...)
所有 API 调用都经过 build_api_kwargs(),确保上下文处理的一致性。系统提示的缓存策略也在这个层面统一管理。
这个设计让我想到一个点:很多 Agent 实现里,system prompt 每次都重新构建。但 Hermes 的做法是把系统提示存到 SQLite 会话数据库里,跨进程恢复。这意味着即使用户关闭终端再打开,对话历史的上下文处理依然是连续的。
[[IMG: Abstract technology background]]
五、与传统方案的对比
| 特性 | 传统方案 | Hermes |
|---|---|---|
| 系统提示 | 每次构建 | 分层缓存,稳定层复用 |
| 压缩触发 | 手动判断 | 自动检测 token 使用率 |
| 压缩内容 | 简单截取 | LLM 结构化摘要 |
| 压缩防抖 | 无 | 连续压缩节省 <10% 跳过 |
| 上下文入口 | 分散 | 统一 build_api_kwargs() |
| 会话连续性 | 丢失 | SQLite 持久化 |
传统方案的痛点是什么?往往是「上下文不够用了才想起来压缩」,而且压缩方式也简单粗暴——直接截取前面的历史。这种方式会丢失重要的中间上下文,而且压缩阈值不科学,有时候截了 50 条才发现 token 还是超了。
Hermes 的方案是预防性压缩 + 智能摘要。通过分层提示减少不必要的重复,通过阈值检测提前压缩,通过结构化摘要保留关键信息。
[[IMG: Network connections digital]]
六、这套设计给我的启发
研究 Hermes 的上下文处理机制,我有三点最深感受:
第一,「分离」比「统一」更灵活。分层提示的本质是把变化频率不同的内容分开管理。稳定的内容缓存复用,变化的内容独立更新。这种思路在很多场景都适用。
第二,「智能」比「规则」更高效。传统压缩是规则驱动(消息数、时间戳),Hermes 是智能驱动(token 使用率 + LLM 摘要)。虽然多了一次 LLM 调用,但压缩质量更高,后续调用的效率也更高。
第三,「一致性」比「灵活性」更可靠。统一入口的好处不只是性能优化,更重要的是保证所有 LLM 调用的上下文处理逻辑一致。分散的入口看起来灵活,实际上容易埋下 bug。
写在最后
上下文处理是 Agent 开发中的「隐形难题」。
表面上只是「塞进去、压缩掉」的操作,实际上涉及缓存策略、压缩算法、触发时机、入口统一等多个维度。Hermes 的方案不一定是唯一的正确答案,但它提供了一套可参考的设计思路。
如果你也在做 Agent 开发,不妨思考一下:你的上下文处理,有没有做到「该缓存的缓存,该压缩的压缩,该统一的统一」?
好的架构设计,往往不是解决「能不能」的问题,而是解决「好不好」的问题。
[[IMG: Circuit board close up]]
本文基于 Hermes Agent 开源代码整理,仅供参考
更多推荐


所有评论(0)