Hermes 不再"猜"你的上下文多大:用量锚定机制让压缩阈值第一次对准真实数字


你的会话聊到第 40 轮,一切正常——然后突然弹出一条“上下文过长,正在压缩”的通知,你明明觉得离窗口上限还远得很;又或者某天 API 直接甩你一个 413 错误,说请求体太大,可你数了数对话根本没那么长。这两种情况以前在 Hermes 里都不罕见,根子是同一个:Hermes 一直在“猜”你的上下文有多大——用字符数除以 4、图片固定按 1500 token 估算这类启发式规则,把整段对话从头到尾重新估一遍,误差随着历史增长越滚越大。8 月 28 日合并的 PR #97206 把这个根子拔了:改用 provider 上报的真实用量做“锚点”,估算范围从“整段对话”缩到“最后一轮之后新增的消息”。

以前:整段对话靠估算,误差滚雪球

每个 provider 的响应里其实都带一份精确的用量报告usage.prompt_tokens(这次请求实际发了多少 token,包括系统提示、工具定义、整段历史)、usage.completion_tokens(生成了多少)。这是模型服务商自己数的,是真正的“ground truth”。

但旧版 Hermes 没怎么用它。每次需要检查上下文大小时,它都用启发式把整个会话重新估一遍:英文字符除以 4、图片固定 1500 token、CJK 字符有密度规则……这套估算在会话早期还挺准,但随着历史变长,误差开始复合累积——有的会话被高估,明明没到阈值就被压缩;有的被低估,请求体超了 provider 上限直接 413。官方 issue 里 #89938、#88960 就是这类“估算与现实的差距”问题的典型代表。

新机制:用量锚定,误差窗口缩到一轮

PR #97206 的核心改动就是一行公式:

当前上下文 token 数 =
  上一次响应的 usage.prompt_tokens
  + 上一次响应的 usage.completion_tokens
  + 对"那之后新增的消息"的估算

也就是说:整段对话的真实大小直接用 provider 报的数字,只有最后一轮之后新增的少量消息才需要估算。误差窗口从“整段对话”缩到“最后一轮”,而且每收到一次响应就用真实用量自我校正一次——估算永远追不上真实值,因为真实值本身就是起点。

实现上锚点只在一个地方捕获:主对话循环里 context_compressor.update_from_response() 之后的 usage 块(capture_usage_anchor),每次响应更新一次。压缩阈值、413 恢复逻辑全部改用这个锚定数字做判断。

配套修复:413 恢复按字节算

同一波改动还带了配套修复(#97197 等):413 恢复逻辑以前也用 token 估算衡量请求大小,现在改为按实际字节数衡量——因为 provider 的 413 判断是基于 HTTP 请求体大小的,估算 token 再准也换算不对字节。压缩器现在还会在压缩时真正释放历史图片占用的字节(#97160),让 413 恢复不只是“侥幸通过”而是“真腾出空间”。

对你意味着什么

  • 更少的意外压缩:阈值判断对准真实数字,会话没那么容易“虚胖”被压缩;
  • 更少的 413:请求体大小判断从估算改为字节计量,长会话不再突然报“请求过大”;
  • 更诚实的 /context:上下文占用数字与 provider 账单上的数字一致,你看到的就是真的。

维护者 Teknium 的原话是:“我真的很厌倦我们一直在估算 token。停止估算。”(“I’m really getting tired of us estimating tokens. Stop estimating things.”)——这一波改动就是把这句话落到了代码里。

这批改动 8 月 28 日合并,目前在上游 main,尚未进入发布 tag。hermes update 到包含它们的版本后自动生效。

延伸阅读