news 2026/4/22 20:58:21

上下文压缩不是“丢数据“:Context Compressor 的血缘追踪与 Prefix Cache 保护

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
上下文压缩不是“丢数据“:Context Compressor 的血缘追踪与 Prefix Cache 保护

1. 引言:当 128K 上下文窗口撞上长程任务,焦虑从何而来?

Claude 3.5、GPT-4o 等模型已经将上下文窗口推到了 128K 甚至更高。但作为一个构建过生产级 Agent 的工程师,你会发现一个反直觉的事实:窗口越大,上下文治理的焦虑反而越重。

原因很简单:模型能"吃"进去 128K,不代表它能在 128K 里保持精准。当对话历史膨胀时,传统的"滑动窗口截断"就像一场慢性的信息自杀——系统指令可能被边缘化,早期的工具执行结果悄然消失,Agent 在任务中途突然"失忆",陷入逻辑断层。

Hermes Agent 的设计哲学是:上下文不是被"消耗"的资源,而是需要被"治理"的状态。本文将基于真实源码,深度拆解ContextCompressor的防御性压缩策略、基于 SQLiteparent_session_id的血缘追踪,以及为保护 Anthropic Prefix Cache 而精心设计的消息注入策略。


2. 为什么"直接截断"在 Agent 工程里是自杀式行为?

很多开发者处理上下文溢出的第一反应是:把最早的消息丢掉。这在简单的聊天机器人里或许可行,但在具备工具调用能力的 Agent 中,这是致命的。

致命点一:任务宪法丢失
Agent 的核心任务定义通常位于 System Prompt 或前几轮对话中。截断后,Agent 可能忘记自己应该扮演什么角色、要完成什么目标,沦为无意义的复读机。

致命点二:Prefix Cache 失效带来的成本爆炸
Anthropic 的 Prefix Caching 机制以 System Prompt 为缓存键的前缀。如果因为截断导致上下文起始位点偏移,或者 System Prompt 被改动,整个 KV Cache 会瞬间失效。根据 Hermes 内部的设计决策分析,这意味着后续每一轮都要为原本可以缓存的前缀支付全额输入 token 费用,成本可能飙升数倍。

致命点三:工具调用状态断层
Agent 的工作流高度依赖"我已经做了什么"的中间状态。一旦这些状态被粗暴删除,Agent 会产生幻觉,重复执行危险操作,或者对用户的反馈视而不见。

特性直接截断 (Sliding Window)智能压缩 (Context Compressor)
系统稳定性极低(易丢失任务宪法)高(头部消息永久受保护)
逻辑连贯性差(中间状态断层)强(摘要保留关键决策链)
Prefix Cache 保护无(起始位点偏移即失效)有(System Prompt 绝对静止)
历史可追溯性无(数据永久丢失)完整(SQLite 血缘链 + FTS5 检索)

3. Context Compressor 的"防御性压缩"算法

Hermes 的ContextCompressor不是一把剪刀,而是一套精密的治理引擎。它的核心逻辑嵌入在AIAgent.run_conversation()的预检阶段(run_agent.py:7973-8022)。

3.1 触发机制:75% 哨兵线

threshold_tokens默认设置为模型最大上下文长度的75%。这个数字不是拍脑袋定的,而是工程上的安全冗余:Hermes 的 Tool Registry 在运行时可能挂载 40+ 工具的 schema,这部分定义本身就占 20K-30K tokens。预留 25% 的缓冲带,是为了确保压缩后仍有足够空间容纳工具定义、插件上下文和当前轮次的推理输出。

3.2 四步压缩算法

根据agent/context_compressor.py的实现,压缩遵循以下步骤:

  1. 清理冗余:优先调用_prune_old_tool_results(messages)清理过旧的工具输出。在长程文件操作中,原始 tool results 是上下文膨胀的最大推手。
  2. 分割为 head / middle / tail
    • head = messages[:protect_first_n],默认protect_first_n = 3
    • tail = messages[-protect_last_n:],默认protect_last_n = 6
    • middle = messages[protect_first_n : -protect_last_n]
  3. 语义摘要 middle:使用配置中指定的**辅助模型(通常是更便宜的模型)**对中间消息进行摘要。摘要 prompt 经过精心设计,要求保留四类信息:关键决策、错误与修复过程、最终状态、待办事项(TODOs)。
  4. 重建消息列表:将摘要封装为一条role="system"的消息,插入 head 和 tail 之间。
# agent/context_compressor.py 核心逻辑示意defcompress(self,messages,...):messages=self._prune_old_tool_results(messages)head=messages[:self.protect_first_n]tail=messages[-self.protect_last_n:]middle=messages[self.protect_first_n:-self.protect_last_n]summary=self._summarize_turns(middle)compressed=head+[{"role":"system","content":summary}]+tailreturncompressed

3.3 三轮预检循环:工程师的防御性思维

run_agent.py中,压缩逻辑被包裹在一个for _pass in range(3)的循环中。这不是过度设计,而是对极端场景的敬畏:面对异常密集的对话历史,单次摘要可能仍不足以将 token 数压到阈值以下。最多三轮的渐进式压缩,确保在不触发硬截断的前提下,平滑回滚到安全水位。

3.4 压缩后的 history 清零策略

一个极易被忽视但极其关键的细节:压缩完成后,源码会将conversation_history = None。这意味着后续写入SessionDB时,所有消息(包括被压缩掉的旧消息和新生成的摘要)都会被完整持久化到新 session,而不是因为"已经存在于旧 history 中"被跳过。这保证了 SQLite 中的记录是完整且自洽的。


4. 血缘追踪:压缩不是删除,而是归档

在 Hermes 的存储层设计中,上下文压缩被严格定义为一种"信息归档"行为,而非"数据销毁"。这得益于hermes_state.pyparent_session_id的谱系设计。

4.1 Session 谱系的建立

当压缩触发时,原始 session 会被调用end_session(end_reason='compressed')标记结束。随后,系统会创建一个全新的 session,其parent_session_id指向旧 session。于是一条血缘链便形成了:

session_aaa (原始) --压缩--> session_bbb (parent=aaa) --压缩--> session_ccc (parent=bbb)

这在hermes_state.py的 schema 中有明确定义:

CREATETABLEsessions(idTEXTPRIMARYKEY,...parent_session_idTEXT,-- 压缩后新会话的父 IDend_reasonTEXT,-- 'completed', 'compressed', 'pruned'...);

4.2 FTS5 让历史始终可检索

即使原始消息已从当前 LLM 上下文中消失,它们依然完整保存在 SQLite 中,并通过 FTS5 全文索引实时可搜。SessionDB.search_messages()的底层实现会通过rowidJOIN 回messages表,并返回带高亮片段(snippet)的搜索结果。

这意味着:压缩只是把数据从 LLM 的"工作内存"移到了 Agent 的"硬盘"里。当 Agent 需要追溯某个早期决策时,它可以通过session_search_tool秒级检索,而不是依赖 LLM 那已经不堪重负的上下文窗口。


5. Prefix Cache 保护:一个深思熟虑的注入策略

Hermes 在处理插件上下文(如pre_llm_callhook 的返回值)时,做了一个看似微小、实则影响巨大的决策:将动态上下文注入到 User Message,而不是 System Prompt。

5.1 为什么这是成本优化的生命线?

Anthropic 的 Prefix Caching 以 System Prompt 为前缀缓存键。如果 System Prompt 每轮都变——比如因为注入了新的历史摘要或插件上下文——缓存就会立即失效。根据 Hermes 内部的设计注释,这会导致每次 API 调用都为原本可以缓存的前缀支付全额费用。

源码中的决策非常明确(run_agent.py:8028-8058):

  • pre_llm_call返回的上下文被追加到当前用户消息的内容中。
  • System Prompt 保持绝对静止,从而确保后续 turn 仍能享受 Prefix Cache 的折扣。

5.2 Gateway 模式下的双保险缓存

对于 Gateway 模式,每一 turn 都可能新建一个AIAgent实例,实例级别的_cached_system_prompt会在 turn 之间失效。为了避免每次都重建 System Prompt(同样会破坏 Prefix Cache),Hermes 会从 SQLite 中读取上一 turn 存储的system_prompt

# run_agent.py:7925-7962ifself._cached_system_promptisNone:stored_prompt=Noneifconversation_historyandself._session_db:session_row=self._session_db.get_session(self.session_id)ifsession_row:stored_prompt=session_row.get("system_prompt")ifstored_prompt:self._cached_system_prompt=stored_promptelse:self._cached_system_prompt=self._build_system_prompt(system_message)self._session_db.update_system_prompt(self.session_id,self._cached_system_prompt)

这是"实例缓存 + SQLite 回读"的双保险设计:既保护了 Prefix Cache,又兼容了 Gateway 每 turn 新建实例的运行模式。


6. 开放架构:Context Engine 的策略模式

Hermes 的上下文引擎采用了Strategy PatternContextEngine作为抽象基类,允许开发者根据任务场景动态切换压缩方案。

config.yaml中,你可以这样配置:

context:engine:"custom-vector-compressor"# 切换至基于向量数据库的 RAG 压缩插件threshold_tokens:0.8# 将触发阈值调至 80%

这种设计的工程价值在于解耦:

  • 对于常规代码重构,默认的ContextCompressor(摘要模式)已经足够。
  • 对于超大规模文档处理,可以无缝切换到RAG 语义检索引擎,只按需加载相关的历史片段,让 Agent 在海量信息中保持高召回精度。

7. 结语:上下文治理权,才是 Agent 的魂

Hermes 的上下文压缩哲学可以总结为三点:

  1. 防御性保留:用protect_first_nprotect_last_n守护任务宪法与即时记忆。
  2. 透明化追溯:用parent_session_id和 FTS5 确保压缩不是删除,而是归档。
  3. 缓存友好:将动态上下文注入 User Message,让 System Prompt 成为 Prefix Cache 的稳定锚点。

当你真正理解了这套机制,你就会明白:上下文窗口的大小只是硬件参数,而上下文治理的能力才是软件灵魂。掌握了治理权,你的 Agent 才能在长程任务中既"不忘本",又"不烧钱"。

思考题:当 Agent 的parent_session_id血缘链可以无限延伸,它究竟是变得更聪明了,还是被过去的冗余信息所束缚?在追求"永不遗忘"的路上,我们是否也需要为它设计一套主动的"遗忘机制"?


延伸阅读

  • 第3篇 一条消息的“生死之旅”:穿越 Hermes 六层架构的 2900 行状态机
  • 第4篇 别再往 System Prompt 里塞东西了!深度拆解 Hermes Agent 的“五层记忆”黑科技
  • 第7篇 40+ 工具、7 种执行后端:Agent 的“手脚”是如何安全伸出去的?
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 20:58:20

STM32F407ZGT6标准库工程模板搭建全流程解析

1. 准备工作:获取固件库与开发环境搭建 第一次接触STM32F407ZGT6开发时,最让人头疼的就是不知道从哪里开始。我刚开始用正点原子探索者开发板时,花了整整两天时间才把开发环境搭好。现在回头看,其实只要按照正确的步骤来&#xff…

作者头像 李华
网站建设 2026/4/22 20:58:13

Claude code功能介绍和安装教程

前面学习编程的时候,我对ai大模型很感兴趣,现在也算是基本入门了吧,我决定写一篇博客来讲一下Claude code功能介绍和安装教程。希望能为大家学习ai编程提供一点帮助。 1.Claude code概述和核心功能 Claude code概述 Claude Code 是AI公司A…

作者头像 李华
网站建设 2026/4/22 20:56:17

情感化设计与AI功能设计的融合趋势

1. 情感化设计的必然崛起:当功能设计遇上人性需求在Jason Calacanis那篇关于AirPods的预言性文章里,我看到了一个令人着迷的未来图景——当AI和语音交互能够完美替代我们笨拙的手指操作时,耳机将成为连接数字世界的主要入口。这让我意识到&am…

作者头像 李华
网站建设 2026/4/22 20:53:17

Docker集群日志黑洞破解记(etcd+Fluentd+Prometheus链路级追踪全披露)

第一章:Docker集群日志黑洞的典型表征与根因诊断当Docker集群规模扩展至数十节点、数百容器时,日志采集链路常出现“有日志产生却无日志落地”的静默丢失现象,即所谓“日志黑洞”。其典型表征包括:应用容器内 stdout/stderr 持续输…

作者头像 李华
网站建设 2026/4/22 20:52:29

计算机毕业设计:Python股票技术面分析与LSTM价格预测平台 Flask框架 TensorFlow LSTM 数据分析 可视化 大数据 大模型(建议收藏)✅

博主介绍:✌全网粉丝50W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业项目实战8年之久,选择我们就是选择放心、选择安心毕业✌ > 🍅想要获取完整文章或者源码,或者代做,拉到文章底部即可与…

作者头像 李华
网站建设 2026/4/22 20:51:17

CUDA 12.1大内核参数支持解析与性能优化

1. CUDA 12.1大内核参数支持解析在CUDA编程中,内核函数的参数传递一直存在一个关键限制——参数总大小不能超过4,096字节。这个限制源于CUDA使用常量内存(constant memory)来传递内核参数的设计。CUDA 12.1版本将这个限制从4,096字节提升到了32,764字节,…

作者头像 李华