新闻详情

新闻详情

首页 / 资讯中心 / 详情

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

发布时间:2026/9/27 11:49:51来源:尧图网络
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设太大。第一次压缩后它就该归零否则那几条老消息会在每个子会话里反复复制头部无界增长压缩越压越多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Codex 小白入门:从安装到插件、MCP、Skills,一篇把配置讲明白|TaoToken 统一 Key 接入 2026/9/27 12:35:01

Codex 小白入门:从安装到插件、MCP、Skills,一篇把配置讲明白|TaoToken 统一 Key 接入

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

阅读更多 →
新乡做网站的公司选错坑多?3张图解步骤避坑指南 2026/9/27 12:35:01

新乡做网站的公司选错坑多?3张图解步骤避坑指南

新乡做网站的公司选错坑多?3张图解步骤避坑指南 改个需求建站公司拖一周,这种憋屈事儿谁没遇上过?在河南新乡找建站团队,不少老板被拖得进度全乱,最后网站上线慢还一堆Bug。今天不聊虚的,直接给你拆解【新乡做网站的公司】筛选逻辑,用【图解步骤】…

阅读更多 →
seo内容优化避坑指南:3个维度判断服务商哪家好 2026/9/27 12:35:00

seo内容优化避坑指南:3个维度判断服务商哪家好

seo内容优化避坑指南:3个维度判断服务商哪家好 域名服务器搞不懂,是不是让你在选择建站服务时心里没底?很多老板一上来就问“哪家好啊”,但没搞清技术底层逻辑,很容易被销售话术带偏。其实, seo内容优化…

阅读更多 →
Longhorn Upgrade Checker 信息采集机制:`Allow Collecting Longhorn Usage Metrics` 设置与 extraInfo 指标格式全解析 2026/9/27 12:34:54

Longhorn Upgrade Checker 信息采集机制:`Allow Collecting Longhorn Usage Metrics` 设置与 extraInfo 指标格式全解析

云原生存储高可用容器编排 【免费下载链接】longhorn Cloud-Native distributed storage built on and for Kubernetes 项目地址: https://gitcode.com/gh_mirrors/lo/longhorn 点击查看 免费下载 Longhorn 的 Upgrade Checker(升级检查器)除…

阅读更多 →
基于XBRR-24Z8与R7KA8T2LFLCAC的工业双协议无线方案实战 2026/9/27 12:34:53

基于XBRR-24Z8与R7KA8T2LFLCAC的工业双协议无线方案实战

1. 项目缘起与方案选型:为什么是XBRR-24Z8加R7KA8T2LFLCAC1.1 一个真实的需求场景去年底接了个工业传感器联网的活儿,客户现场是个面积超过8000平米的钢结构厂房,需要部署60多个温振一体传感器,同时要满足两个硬性条件&#xff1a…

阅读更多 →
LangChain/LangGraph 执行 Claude Code/Code Buddy 等 Skill 的技术方案研究:TaoToken 统一 Key 接入配置骨架 2026/9/27 12:34:53

LangChain/LangGraph 执行 Claude Code/Code Buddy 等 Skill 的技术方案研究:TaoToken 统一 Key 接入配置骨架

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

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉