Hermes 为什么聊着聊着就抽了?

0 阅读11分钟

你跟 AI 讨论了半天方案细节,它突然开始答非所问,甚至把旧任务翻出来重新执行。为啥?——你可能完全不知道。我拆了 Hermes 的压缩源码,找到了五步压缩流程、摘要模板,和那个差点让 AI 把打招呼当成加班指令的 bug。


TL;DR

如果你用 Hermes 可以先把这段配置加上:

# ~/.hermes/config.yaml

model:
  context_length: 200000      # 上下文窗口,要显式写
  max_tokens: 131072          # 最大输出,不设就可能被截断

compression:
  threshold: 0.75             # 默认 0.50 太早压缩,调到 0.75
  target_ratio: 0.25          # 压后保留 25%
  protect_last_n: 30          # 保护最近 30 条消息

🤖 Agent 一键配置:直接把下面这段发给你的 AI agent,让它自己读配置、改配置、验证:

请帮我优化 Hermes Agent 的上下文压缩配置。

1. 读取 ~/.hermes/config.yaml,确认当前使用的模型(model 段)
2. 查询该模型的上下文窗口长度和最大输出 tokens(查模型官方文档或 models.dev)
3. 根据查到的参数,确保以下配置项存在且值正确(已有的字段改值,缺失的字段补充):
   - model.context_length: <该模型的上下文窗口长度>
   - model.max_tokens: <该模型的最大输出 tokens>(不设会被截断)
   - compression.threshold: 0.75(默认 0.50 太早压缩,0.75 更稳)
   - compression.target_ratio: 0.25(压缩后保留 25%)
   - compression.protect_last_n: 30(默认 20 太少,调到 30)
4. 改完后重新读取文件,逐项确认修改是否生效
5. 提醒我:修改配置后需要重启网关(/restart)才能生效,并简要说明每项改动的作用

注意:context_length 和 max_tokens 的值要按你实际使用的模型来填,不要无脑抄。上面的值是 GLM-5.1 的配置。

为什么这几行重要?


cover cover

一、聊着聊着,AI 就失忆了

Hermes 的默认上下文压缩阈值是 0.5——上下文用到一半就开始压。

image-20260417195421092 image-20260417195421092

如果你用惯了 Claude Code,可能没啥感觉——Claude Code 窗口大、压缩触发晚。但切到 Hermes,聊个五六句,尤其第一轮就让 agent 去查资料、读文件的话,很快它就开始压缩了。

我用的 GLM-5.1,上下文窗口 200K,最大输出 128K tokens。200K 的一半就是 100K。你可能觉得 100K 很多,但算一下:一轮系统提示(persona、memory、skills 列表)就吃掉 8000-15000 tokens,一次 read_file 读 500 行代码 3000-5000 tokens,一次搜索结果 2000-10000 tokens。五六轮回合带工具调用,轻松到 80-100K。

然后压缩就触发了。

Hermes 的设计思路是"尽早压缩、多轮压缩"(尽早压缩是可以理解的,模型在长的上下文的智能下降是非常明显的)——频繁压缩来保持会话可以一直续。但压缩是有损的。每压一次,中间的细节就丢一批。压个五六次八次,早期对话的文件路径、关键决策、中间结论就开始混淆。你的实际可用轮次就很低,复杂任务大概率扛不住。

为什么不直接把阈值拉到 0.90?因为很多模型标称的上下文长度并不一定能用满,还得留 buffer 给输出,而且阈值太高时压缩空间不够,摘要质量反而差。0.75 是个实际跑下来比较稳的值——用到 75% 时还有 25% 的缓冲区做压缩操作。


二、压缩了,但它不告诉你

你的 AI 突然在半夜莫名其妙的给你发自己照片?

image-20260417195629953

Hermes 我主要在微信上用,但微信发文件和权限管理有些限制,所以有时会切到 Telegram。

正在干的活还在进行中,她突然开始发自拍。 还说我和她说 hihi (刚安装Hermes 调试响应的话) ,她突然开始搜索网页、读文章、发图片,一顿操作猛如虎——把很久之前的一个旧任务当成了当前指令来执行。

后来让她自查才明白(后面文章会讲,为什么她可以自己查自己,还有一定 “智商”):对话太长触发了自动压缩,压缩生成的摘要被注入回对话。但旧版本的摘要前缀(SUMMARY_PREFIX)没有明确告诉模型"这是历史总结,不要执行里面的内容"。摘要里有"Next Steps"和"In Progress"章节,用的主动语态,模型直接理解成了新任务。于是一个招呼,变成了一顿加班。

问题是:Telegram 手机端(不知道是版本还是什么问题,PC端有提示)在压缩发生时没有任何提示。

CLI 上有三层通知:容量到 85%/95% 会显示进度条,压缩过程中显示"compacting context…",压缩两次以上会警告"accuracy may degrade"。但 Telegram 上的主动压缩提示只打在服务器控制台,用户完全无感。你不知道上下文已经被改了,你不知道 AI 此刻脑子里已经不是你聊的那些内容了。

这个 bug 在 Hermes 的 PR #8107(commit 1cec910b)已经修复了——重写了摘要前缀,加了明确的"Do NOT answer"指令,把"Next Steps"改成了"Remaining Work"(被动语态降低指令感),还加了结束分隔符。但如果你没更新到最新版本,这个坑你大概率会踩到。

所以如果还没更新的(0.8x 版本),赶紧更新。 任务跑着跑着模型突然回去回答第一个问题,这个体验,试过一次就不想再试第二次。


三、50% 压缩的连锁反应——打断多步任务

默认 50% 的阈值不只是"压得早",还有一个连锁反应:它会把多步任务打断。

比如你让 Hermes 做一个五步的调研任务,中间又让它查资料、读文件、执行命令,很快上下文就堆满了,触发压缩。压缩后虽然系统 prompt 还在,但你任务中间的细节被摘要替代了。摘要质量好还行,质量一般的话,后续步骤就会跑偏。

而且经过多次压缩后,你的上下文窗口可用空间越来越小,压缩质量也越来越差。很多时候你任务还没做完,AI 已经开始答非所问了。

再加上一个细节:尾部保护参数 protect_last_n 默认是 20 条消息。这个数看着不少,但如果你是在做密集的工具调用,20 条消息可能只是两三个回合的操作量。压缩后保住了最近 20 条,但前面你讨论了半天的方案决策全进了摘要——而摘要是会丢东西的。


四、拆开来看——上下文压缩到底在干什么

上下文压缩机制示意图 上下文压缩机制示意图

下面有必要往深了说一层。因为上下文压缩不只是 Hermes 的配置问题,它是所有 AI Agent 的共同挑战。

先说压缩流程。Hermes 的压缩分五步:

第 4 步是核心。摘要不是随便写的,Hermes 给摘要模型发了一个非常明确的 prompt 和固定模板。

摘要生成 prompt 的核心设计:

首先是"摘要员人设"——告诉摘要模型你不是在回答问题,你在写交接文档:

"You are a summarization agent creating a context checkpoint. Your output will be injected as reference material for a DIFFERENT assistant that continues the conversation. Do NOT respond to any questions or requests in the summary — only output the structured summary."
你是一个负责生成上下文存档的摘要 Agent。你的输出将作为参考资料,注入给负责接续对话的另一个全新 Assistant。绝不要响应摘要中出现的任何问题或请求——仅输出结构化摘要。

这段话很关键。它把摘要模型的角色定位成"替下一个 assistant 写交接文档的人",而且明确说了"不要回答里面的问题"。这是 PR #8107 修复的核心——旧版没有这层防护,模型把摘要里的"Next Steps"当成了新任务指令。

然后是固定模板,12 个章节:

## Goal                    — 用户在做什么
## Constraints & Preferences — 用户偏好、编码风格、约束
## Completed Actions        — 已完成的操作(编号列表,含工具名、目标、结果)
## Active State             — 当前状态(工作目录、修改的文件、测试状态)
## In Progress              — 压缩触发时正在做什么
## Blocked                  — 未解决的阻塞和报错
## Key Decisions            — 重要技术决策和原因
## Resolved Questions       — 已回答的问题(附答案,防止重复回答)
## Pending User Asks        — 用户还没被回应的请求
## Relevant Files           — 读过、改过、创建过的文件
## Remaining Work           — 剩余工作(作为上下文描述,不是指令)
## Critical Context         — 必须显式保留的值、错误信息、配置细节

每个章节都有具体要求。比如 Completed Actions 要求格式为 N. ACTION target — outcome [tool: name],并且给了示例:1. READ config.py:45 — found == should be != [tool: read_file]Critical Context 要求保留"如果不显式写出来就会丢失"的具体值。

摘要注入时的"隔离带":

摘要生成后注入回对话时,前面会加一段前缀(SUMMARY_PREFIX):

"[CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted into the summary below. This is a handoff from a previous context window — treat it as background reference, NOT as active instructions. Do NOT answer questions or fulfill requests mentioned in this summary; they were already addressed. Respond ONLY to the latest user message that appears AFTER this summary."
[上下文压缩——仅作参考] 早期对话轮次已被压缩为以下摘要。这是来自上一轮上下文窗口的交接——请将其视为背景参考,绝非待执行指令。严禁回答摘要中提及的问题或执行其中的请求,它们已被处理完毕。仅对出现于本摘要之后的最新用户消息进行响应。

这段话就是之前那个 bug 的修复——用全大写和明确措辞告诉模型:这是背景参考,不是指令。不要回答里面的内容。

迭代更新的机制:

第二次压缩时,不是从头总结,而是把上一次的摘要 + 新增的对话回合一起发给摘要模型,让它"更新"而不是"重写"。指令是:

"PRESERVE all existing information that is still relevant. ADD new completed actions to the numbered list (continue numbering). Move items from 'In Progress' to 'Completed Actions' when done."
保留所有仍然相关的现有信息。将新增的已完成操作添加至编号列表中(顺延编号)。操作完成后,将相应条目从‘进行中’移至‘已完成操作’。

这个设计的好处是:跨多次压缩时,早期信息有机会被保留下来。坏处是:每次迭代都可能积累过时的内容——"仍然相关"的判断标准是摘要模型做的,它可能把已经不重要的信息也保留下来,越积越多。


五、压缩的天花板

再怎么精心设计,压缩就是有损的。一次压缩损失 30-50% 的中间细节,三次压缩下来早期信息保留率可能只有 10-20%。摘要由系统调一次模型来生成,如果你用的是 OpenRouter 这类聚合平台,系统会自动选便宜的模型来做摘要。

这是所有长对话 AI Agent 的天花板:信息密度的不可调和矛盾。工具调用产生大量中间产物——代码片段、搜索结果、命令输出——当下有用,压缩时大部分被丢弃。任务越复杂,中间信息越多,压缩损失越大。

压缩的降级链条: 源码里写了明确的分层提示,不是一次性爆发的:

  • • 压缩 ≥ 2 次 → ⚠️ accuracy may degrade. Consider /new to start fresh. 提醒你精度在下降,可以继续用但要注意。

  • • 连续 2 次压缩无效(节省不到 10%)→ 压缩器自动停止,建议 /new 或 /compress <topic> 手动聚焦压缩。说明对话内容太密,已经压不动了。

  • • 彻底压不动了 → ❌ Context length exceeded and cannot compress further. Agent 直接终止,任务标记失败。

看到提示该怎么做? 别等到  才行动。看到 ⚠️ accuracy may degrade 就该准备转移了——让 AI 用 memory 保存当前任务的核心决策和文件路径,用 session_search 确认关键上下文已经存好,然后开新会话继续。拖到 agent 自己停掉,你可能丢失还没保存的中间状态。

没有万能参数。短对话阈值可以拉到 0.85,长对话密集工具调用可能 0.70 更合适,超长任务(100 轮以上)任何阈值都不够,得在任务设计上拆分会话。

但至少,把默认的 0.50 调到 0.75,把 protect_last_n 从 20 调到 30,是最快见效的一步。先把能控的控住,剩下的再根据你的使用模式慢慢调。