首页/新闻资讯/正文详情

Hermes 上下文压缩架构拆解:长任务 Agent 不失忆的 ContextEngine 与 ContextCompressor 关键设计

发布时间:2026/9/27 11:49:49 来源:云帆数科 栏目:资讯中心
Hermes 上下文压缩架构拆解:长任务 Agent 不失忆的 ContextEngine 与 ContextCompressor 关键设计
1. 长任务 Agent 为什么会“失忆”做长任务 Agent 的朋友大概率都遇到过这种诡异现象任务跑到第 30 轮模型突然开始重复读同一个文件或者把前面已经确认过的参数又改回去。你去看日志发现它并不是推理能力崩了而是它已经“看不全”自己刚才做过什么了。一次典型的工具型会话里每一轮请求都要重新塞回这些东西系统提示、历史对话、工具调用记录、终端输出、文件读取结果、代码片段、偶尔还有截图。窗口没满的时候模型已经开始丢约束、重复劳动窗口真正溢出的时候provider 直接返回 413 或者 context overflow整个任务链断掉。Hermes 的上下文压缩机制就是冲着这个问题来的。需要先划清一个边界它处理的不是长期记忆。MEMORY.md、USER.md 这类 memory provider 负责跨会话的事实沉淀上下文压缩只管当前会话的工作窗口。这个区分很重要因为压缩天然有损不能拿它替代真正的记忆系统否则你会得到一个“记得住用户名字但记不住刚才干了啥”的怪东西。下面我把 Hermes 的压缩链路拆开讲ContextEngine 怎么调度、ContextCompressor 怎么裁剪、压缩什么时候触发、阈值怎么算最后给一份可复制的 config.toml 骨架和长任务多轮验证步骤你可以直接搬到自己工程里试。2. TaoToken 前置把模型接入和压缩链路分开在动手配压缩之前得先把模型调用这条链路理顺。压缩逻辑再漂亮如果 provider 侧不稳定、token 用量拿不到真实值阈值判断就是瞎猜。我自己的做法是把模型接入统一走 TaoToken官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。这样做的实际好处是Agent 主循环里只关心“问引擎要新消息列表”provider 细节被隔离掉压缩阈值依赖的 prompt_tokens 也能稳定拿到。如果你要长期跑编码类 Agent建议直接看 Coding Plan 页面把额度模型和压缩策略一起规划https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Key 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 生成。想先验证模型行为再配压缩可以用模型对话页快速试https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这里有个顺序建议先把 provider 接通、能稳定拿到 usage再去调压缩阈值。反过来做你会分不清是压缩策略有问题还是 token 统计不准。3. ContextEngine把上下文管理做成可替换引擎Hermes 没有把压缩逻辑写死在主循环里而是抽象成agent/context_engine.py里的 ContextEngine。内置实现是 ContextCompressor插件也可以接管整套上下文管理比如换成无损上下文管理 LCM。引擎选择由config.yaml的context.engine控制。插件不会自动启用必须显式配置没有匹配插件时系统回退到内置 ContextCompressor。这层设计让压缩从“一个功能”变成了“一个策略接口”——主循环只关心什么时候问引擎、什么时候拿回新的消息列表不必知道具体是 LLM 摘要、无损索引还是别的方案。3.1 双层压缩Gateway 兜底Agent 主压缩Hermes 有两道压缩线位置和目的都不同。第一道在 gateway属于会话检查。它在 Agent 开始处理消息之前运行阈值固定在 85%。它不是日常压缩主力主要兜住隔夜会话、群聊积压、外部通道疯狂灌消息这类情况。第二道在 Agent 内部也就是 ContextCompressor。它运行在工具循环里默认 50% 阈值优先使用 provider 返回的真实 prompt_tokens。这是日常上下文管理的核心。维度Gateway 会话卫生Agent 压缩器触发阈值固定 85%默认 50%可通过 compression.threshold 配置运行位置Agent 处理前Agent 工具循环内部token 来源上轮真实 token缺失时用字符估算provider 返回的真实 prompt_tokens 优先主要目标防止超大会话直接打挂请求常规上下文管理额外保护hygiene_hard_message_limit 默认 5000 条防抖、边界对齐、会话锁、摘要失败降级阈值错开不是随便定的。gateway 如果也按 50% 触发长会话会在很多轮里提前压缩成本高信息损耗也大。它只应该接管那些 Agent 压缩器没来得及处理的异常会话。3.2 三个触发器预检、响应后、错误恢复ContextCompressor 不是等 API 报错才行动。一次 turn 内它有三个入口。Preflight 预检压缩位于agent/turn_context.py的build_turn_context。它先跑一个便宜判断消息数量是否已经超过保护头尾的安全范围或者字符粗估是否已经很大。只有通过这个门控才会做更贵的 token 粗估。这里有两个细节容易被忽略粗估必须把 tool schemas 算进去工具一多schema 本身就可能占 20K 到 30K token另外 Hermes 会用should_defer_preflight_to_real_usage()抵抗 schema-heavy 请求的噪声如果上一次压缩后的真实 token 已经证明请求能装下就不要被同一批 schema 的粗估反复吓到。Post-response 响应后压缩位于agent/conversation_loop.py。它在模型响应回来、工具结果追加后执行是最常见的压缩路径。核心逻辑可以简化成这样_compressor agent.context_compressor if _compressor.last_prompt_tokens 0: real_tokens _compressor.last_prompt_tokens elif _compressor.last_prompt_tokens -1: real_tokens 0 else: real_tokens estimate_request_tokens_rough(messages, tools...) if agent.compression_enabled and _compressor.should_compress(real_tokens): messages, active_system_prompt agent._compress_context(...)它只看 prompt_tokens不把 completion_tokens 算进触发条件。原因很实际推理模型的 reasoning token 可能很大如果 completion 也参与判断会让会话还没真正挤占输入窗口就过早压缩。last_prompt_tokens -1是一个哨兵值压缩刚结束时系统还没拿到下一轮 provider 的真实 usage此时把 token 视为 0避免刚压完就被 schema 粗估拉回压缩循环。Error recovery 错误恢复压缩在 provider 返回 413、context overflow或 Anthropic 长上下文层的 429 时触发。它会降级 context 设置并强制压缩重试最多max_compression_attempts3次。这条路径不是主流程而是保险丝。真正健康的会话应该靠预检和响应后压缩解决大部分时候不该走到 provider 报错这一步。4. 可复制配置config.toml 骨架与压缩阈值下面这份骨架是我按 Hermes 的配置项整理的字段名对齐config.yaml/config.toml的常见写法你可以按自己工程改。重点是context.engine、compression.threshold、max_tokens和protect_first_n这几个。[provider] name taotoken base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY context_length 200000 max_tokens 32768 [context] engine builtin # 可选 builtin / lcm 插件名 compression_enabled true [context.compression] threshold 0.50 # Agent 主压缩阈值默认 50% gateway_threshold 0.85 # Gateway 兜底阈值固定 85% protect_first_n 3 # 首次压缩保护的首轮消息数 protect_tail_tokens 24000 # 尾部按 token 预算保护 max_compression_attempts 3 min_context_length 65536 # 大窗口模型不早压 [context.hygiene] hard_message_limit 50004.1 阈值计算不是窗口乘百分比很多上下文压缩实现会直接用context_length × threshold。Hermes 没这么做。它先从窗口里扣掉max_tokens因为输出空间也占 provider 给的总窗口。输入预算应该是effective_window context_length - (max_tokens or 0)完整逻辑大致如下staticmethod def _compute_threshold_tokens( context_length: int, threshold_percent: float, max_tokens: int | None None, ) - int: effective_window context_length - (max_tokens or 0) if effective_window 0: effective_window context_length pct_value int(effective_window * threshold_percent) floored max(pct_value, MINIMUM_CONTEXT_LENGTH) if effective_window 0 and floored effective_window: return max( 1, min( int(effective_window * ContextCompressor._MIN_CTX_TRIGGER_RATIO), effective_window - 1, ), ) return floored这段代码同时解决了几个问题。第一给输出预留空间。自定义 provider 如果把max_tokens配到 65536输入预算会明显变小不扣掉它就容易撞。第二大窗口模型不应该太早压。MINIMUM_CONTEXT_LENGTH64K让大上下文模型不会因为 50% 阈值就频繁压缩。4.2 ContextCompressor先剪枝再摘要最后重组ContextCompressor.compress()的目标不是把历史消息简单截断而是把会话改造成三段保护头 结构化摘要 原样保留的尾部消息。压缩过程分四步。第一阶段不调用模型只做工程剪枝。保护尾之外的长工具输出会被压成信息化的一行而不是丢成空占位[terminal] ran npm test - exit 0, 47 lines output [read_file] read config.py from line 1 (3,400 chars)这一步有三遍扫描Pass处理内容为什么需要Pass 1重复文件读取去重只保留最近全文同一个文件被反复 read 时旧副本没必要继续占窗口Pass 2长工具结果缩成一行截图剥离 base64防止旧终端输出和旧截图永久拖累每轮请求Pass 3截断超大 tool_call 参数但保持 JSON 合法避免坏 JSON 毒化后续 provider 请求这个阶段看起来朴素却很值。很多上下文膨胀来自工具结果而不是用户真正说了多少话。先用确定性规则降噪可以减少后面 LLM 摘要的压力。4.3 边界算法压缩不能切坏消息结构压缩边界是这套机制里最有工程味的部分。它既要尽量多压又不能把 tool call 组切坏不能把最新用户任务卷进摘要也不能让早期头部无限增长。保护头只在第一次压缩时保护首轮任务框架。protect_first_n默认保护最初几条非系统消息让首次任务设定活过第一次压缩。但这份保护会衰减if self.compression_count 1 or self._previous_summary: return 0 return self.protect_first_n原因很直接第一次压缩后早期任务框架已经进入 handoff 摘要。如果后续每次还保护前几条老消息它们会变成“不朽消息”在每个子会话里反复复制头部无界增长。系统提示不参与这个衰减它由_protect_head_size()单独保护始终保留。尾部优先按 token 预算保护不是简单保留最后 N 条消息而是从尾往前切直到达到protect_tail_tokens预算。这样即使某条工具输出特别长也不会把尾部预算吃光导致最新用户指令被卷进摘要。5. 验证请求长任务多轮对话下确认不失忆配好之后怎么验证压缩真的生效、而且没把关键信息压丢我一般跑一个三步验证。第一步构造一个会触发压缩的长会话。用一段脚本连续发 40 轮请求每轮都带一个工具调用结果让 token 稳步上涨for i in $(seq 1 40); do curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {\model\:\your-model\,\messages\:[{\role\:\user\,\content\:\第 $i 轮读取 config.py 并报告行数\}]} \ | jq .usage done第二步观察压缩触发点。在日志里盯last_prompt_tokens和should_compress的返回值。正常情况下当真实 prompt_tokens 超过effective_window × 0.50时你应该看到一次压缩事件随后last_prompt_tokens被置为 -1下一轮重新从 provider 拿真实值。第三步检查记忆保留。压缩后问模型一个只有早期轮次才出现过的事实比如“第一轮我让你读的是哪个文件”。如果它能答出来说明保护头 摘要 尾部三段结构工作正常如果答不出来大概率是protect_first_n设太小或者摘要阶段把关键约束丢了。实测下来把protect_tail_tokens设成max_tokens的 70% 左右比较稳既保住最新任务上下文又给摘要留出空间。6. 本篇常见错排查压缩太频繁成本反而升高。先检查threshold是不是设太低再看max_tokens有没有从effective_window里扣掉。如果 provider 返回的prompt_tokens一直拿不到系统会退回字符粗估粗估偏高就会反复触发压缩。确认 API Key 和 usage 字段正常可以在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 重新生成一个再试。压缩后模型开始重复读文件。这是典型的尾部保护不足。protect_tail_tokens太小最新几轮的工具结果被卷进摘要模型看不到自己刚读过什么。把它调大或者检查 Pass 1 去重逻辑是不是把最近全文也去掉了。tool call 组被切坏provider 报 JSON 解析错误。边界算法没有对齐 tool call 组。压缩边界必须落在完整消息组之间不能从一组 tool_call 和 tool_result 中间切开。检查_compress_context的边界对齐逻辑确保 Pass 3 截断参数时 JSON 仍然合法。刚压完立刻又压。看last_prompt_tokens -1的哨兵处理有没有生效。压缩结束后系统还没拿到下一轮真实 usage此时如果直接用 schema 粗估判断很容易被拉回压缩循环。确认should_defer_preflight_to_real_usage()在 schema-heavy 场景下返回 true。错误恢复压缩反复触发。说明主压缩没兜住。检查 gateway 的 85% 阈值和 Agent 的 50% 阈值是否都生效以及max_compression_attempts是不是被打满。如果连续三次强制压缩还失败通常是单条消息本身就超过了窗口需要在 Pass 2 阶段更激进地裁剪长工具输出。排障和接入相关的细节接入文档里写得更全https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你在跑长期编码 Agent压缩策略和额度规划最好一起看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。想先单独验证模型在压缩后的行为用模型对话页最快https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后留一个我踩过的坑别把protect_first_n设太大。第一次压缩后它就该归零否则那几条老消息会在每个子会话里反复复制头部无界增长压缩越压越多。

相关推荐

网站建设更新踩坑实录:新手入门必看的3个救命细节
网站建设更新踩坑实录:新手入门必看的3个救命细节

网站建设更新踩坑实录:新手入门必看的3个救命细节 网站上线三个月,后台流量却只有个位数,你是不是也急得抓耳挠腮?很多新手入门建站的朋友,花大价钱做了官网,结果上线后就像石沉大海,没人看、没人点,连百度收录都慢得让人怀疑人生。其实,问题往往不… · 2026/9/27 11:49:49

返回比分析RRA:精确求解反馈环路增益与相位裕度
返回比分析RRA:精确求解反馈环路增益与相位裕度

反馈放大器做了这么多年,跟“环路增益”打过无数次交道,但我越来越发现一个尴尬的事实:很多工程师嘴上说在算环路增益,实际上用的断开方法并不严格,同一个电路,在不同位置断开,得到的增益曲线和… · 2026/9/27 11:49:49

STM32嵌入式C++实战:从寄存器操作点亮LED到类封装
STM32嵌入式C++实战:从寄存器操作点亮LED到类封装

说实话,看到这个标题的时候,我盯着屏幕乐了好几秒,因为前三篇我们一直在聊为什么在 STM32 嵌入式开发里值得用 C、C 和 C 在单片机上到底差在哪、编译工具链用的又是什么套路——结果评论区和私信里已经有朋友憋不住了:“CLion 都… · 2026/9/27 11:49:43

Deep Seek R1本地化部署:用python代码调用模型并接入TaoToken统一API通道
Deep Seek R1本地化部署:用python代码调用模型并接入TaoToken统一API通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 13:28:11

新手入门:WordPress支持WooCommerce,3步解决网站没人访问难题
新手入门:WordPress支持WooCommerce,3步解决网站没人访问难题

新手入门:WordPress支持WooCommerce,3步解决网站没人访问难题 网站做好了,后台数据却是一片惨淡,每天只有几个IP,连自己都懒得点开看。这种“网站做好了没人访问”的绝望感,是无数新手站长最真实的写照。很多人以为只要把页面做… · 2026/9/27 13:28:11

存储过程游标与条件处理程序:TaoToken 统一 Key 下的 MySQL 调试配置骨架
存储过程游标与条件处理程序:TaoToken 统一 Key 下的 MySQL 调试配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 13:28:05

N1 飞牛 NAS 部署 OpenClaw 实战:TaoToken 统一 Key 打通 24h 常驻微信 AI 助理
N1 飞牛 NAS 部署 OpenClaw 实战:TaoToken 统一 Key 打通 24h 常驻微信 AI 助理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 13:27:59

如何查看网站图片尺寸详细步骤
如何查看网站图片尺寸详细步骤

站长自查:5种方式精准掌握图片尺寸的最佳实践 网站突然打不开,或者页面出现乱七八糟的乱码广告?别慌,先别急着删库重装。很多时候,这不是服务器崩溃,而是你的静态资源——尤其是图片,尺寸失控或者被恶意篡改了。很多站长遇到“网站被黑挂马不知道怎么… · 2026/9/27 13:27:53

Cursor+Playwright MCP 自动化能力提升:TaoToken 统一 Key 配置与验证
Cursor+Playwright MCP 自动化能力提升:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 13:27:53

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码