Hermes Agent 缓存调优实战指南:4 步完成从 cache_ttl 到命中验证的完整闭环
会话中断 10 分钟,5 分钟的提示缓存就过期了,下一条请求会把整个系统前缀重新写入——按 1h 档计费,这次写入按输入价格的 2 倍算。本文带你实操 Hermes Agent 的缓存管理:从改 cache_ttl 到验证命中情况、压缩旧轨迹,4 步走完全程。
快速上手:4 步完成缓存调优闭环
Step 1:把缓存 TTL 改成 1h。
打开你的 Hermes 配置文件(config.yaml;仓库根目录的样例模板是 cli-config.yaml.example),定位 prompt_caching 段:
prompt_caching:
cache_ttl: "1h" # 默认 "5m";带停顿的长会话用 "1h"
保存后运行 hermes config check,预期无 cache_ttl 相关报错。这是唯一直接影响后续费用的开关——TTL 档位决定每次缓存写入的单价(5m 档 1.25 倍输入价,1h 档 2 倍)。
Step 2:重启会话,让新档位生效。
为什么要这一步:TTL 是按会话初始化的,改配置不会刷新已存在的会话。
重启 Gateway 或开新会话,确保当前会话以 1h 档位重新规划缓存断点。
Step 3:在 Models 面板读取缓存命中占比。
为什么要这一步:调优是否生效,只能看后续请求的 token 构成,猜不出来。
打开仪表盘 Models 页,每张模型卡片上的 CACHE READ 是从缓存命中的 token 量,INPUT 是按常规输入计费的量:
预期看到:同一会话第 2 次请求起 CACHE READ 开始增长、INPUT 占比下降。仓库注释给出的量级是输入成本约降 75%。如果 CACHE READ 一直是 0,说明路由没走 Anthropic 缓存(比如 Qwen 系路由),先别急着加预算。
Step 4:压缩长会话沉淀的旧轨迹。
为什么要这一步:缓存再便宜,前缀也随轮次增长,压缩历史等于直接缩短每次要付钱的前缀。
对已导出到磁盘的 JSONL 轨迹,先 dry-run 再实际压缩:
python trajectory_compressor.py --input=data/trajectories.jsonl --dry-run
python trajectory_compressor.py --input=data/trajectories.jsonl --output=out.jsonl --target_max_tokens=16000
跑完应先看到目标 token 数,结尾输出 ✅ Compression complete!,输出文件旁多一个 *_metrics.json 记录压缩前后 token 数。压缩策略是保护头部和最后 N 轮、只摘要中间段,且不会拆开工具调用/结果对。
它是怎么跑的:原理拆解
每次请求发出前,agent/prompt_caching.py 里的 apply_anthropic_cache_control 会先把上一次请求残留的标记剥掉,再重新摆放。断点预算是 4 个:有静态系统前缀时,系统侧占 2 个(前缀和尾部各一个),其余 2 个给最近 2 条可承载标记的非系统消息;没有前缀时退化为 1 个系统断点加最近 3 条。"先剥后摆"保证重复调用时标记数不会超过 4。
def apply_anthropic_cache_control(
api_messages, cache_ttl="5m", native_anthropic=False,
static_system_prefix=None, tool_part_markers=True,
) -> list:
"""Apply Anthropic cache-control markers; idempotent, max 4 markers."""
# 实现略
返回的消息列表里所有 cache_control 携带同一个 TTL,而存储的会话记录不受影响(copy-on-write)。
摆标记前还有一道闸门:is_one_shot_request 判断这次请求是否"一次性"——没有工具、没有 assistant/tool 轮次、至多 1 条用户消息(压缩摘要、标题生成这类),就不放标记,因为这次写入花 1.25 倍(5m)或 2 倍(1h)的输入价,却没有任何后续请求能读回。同理,cache_ttl: "auto" 不按时间挑档位,而按"谁在驱动会话"挑:人工交互的会话(cli/tui/desktop/消息平台)给 1h,subagent、cron、oneshot、webhook、kanban 这类机器驱动来源给 5m。仓库自己的测量数据是:交互会话上 1h 省约 42% 写入成本,subagent/cron 上用 1h 反而多花约 49%。
其余三块缓存的时机各不相同:agent/model_metadata.py 的模型元数据走 1 小时内存缓存,过期先回落磁盘缓存,OpenRouter 拉取失败时返回旧数据而不是空;技能中心把 hub 索引落到 <HERMES_HOME>/skills/.hub/index-cache/,1 小时新鲜度窗口;内存工具的 MEMORY.md / USER.md 则是会话开始时注入系统提示的冻结快照,会话中写入只落盘、不改提示,就是为了不打断前缀缓存。
| 缓存项 | 默认 TTL | 位置 / 参数 | 何时需要动 |
|---|---|---|---|
| 提示缓存断点 | 5m | config.yaml prompt_caching.cache_ttl | 交互长会话改 1h |
| 模型元数据 | 3600s | 内存 + 磁盘回落 | 拉取失败后保持 False |
| 端点模型 | 300s | 内存 | 一般不动 |
| 技能索引 | 3600s | <HERMES_HOME>/skills/.hub/index-cache/ | 过期未刷新时清理 |
| 内存字符限额 | 2200 / 1375 | config.yaml memory | 笔记膨胀导致前缀变长时 |
踩过的坑与处理办法
现象:cache_ttl 改了 "1h",Qwen 系模型却仍按 5m 档走。 根因:⚠️ effective_cache_ttl() 会把 Qwen/Alibaba 系路由钳制到 5m——这些路由不支持 1h 档,只有实测过的 opencode-go 路由例外。 解法:Qwen 系模型显式写 cache_ttl: "5m",别指望 1h;确实需要 1h 保持期就换 OpenRouter 或原生 Anthropic 路由。
现象:subagent/cron 驱动的会话改成 1h 后成本不降反升。 根因:1h 档写入按 2 倍基础输入价计费,而机器驱动会话每隔几秒就调用一次,永远收不回保持期红利。 解法:cache_ttl: "auto"。人工交互会话落 1h,机器驱动来源(subagent/cron/oneshot/webhook/kanban/api/tool/batch)落 5m。
现象:长时间停顿后的下一次请求仍全冷,token 全额重付。 根因:5m TTL 到期,长轨迹又把前缀撑长,重写代价比之前更贵。 解法:交互长会话设 cache_ttl: "1h";磁盘上的旧轨迹先 python trajectory_compressor.py --input=data/trajectories.jsonl --dry-run 确认,再带 --target_max_tokens=16000 实压,大文件可加 --sample_percent=15 抽样。
速查表
| 命令 / 参数 | 默认值 | 位置 | 推荐值 |
|---|---|---|---|
prompt_caching.cache_ttl | "5m" | config.yaml prompt_caching | 交互长会话 "1h"、混合 "auto" |
| 缓存断点数 | 4(固定) | 请求线上,勿手工注入 | 交给默认布局 |
fetch_model_metadata | force_refresh=False | 函数参数 | 失败后保持 False 吃旧缓存 |
| 技能索引缓存目录 | TTL 3600s | <HERMES_HOME>/skills/.hub/index-cache/ | 过期未刷新时删对应 .json |
memory_char_limit / user_char_limit | 2200 / 1375 | config.yaml memory | 保持默认 |
--target_max_tokens | 15250 | trajectory_compressor.py CLI | 16000 |
--sample_percent / --seed | 全量 / 42 | trajectory_compressor.py CLI | 大文件 15,seed 保持 42 |
做完这四步,停顿 10 分钟后的那次请求不再整段重写前缀,Models 页的 CACHE READ 会持续往上走。如果你接下来想压缩的是上下文本身而不只是价格,下一站是运行时侧的上下文压缩(config.yaml 里的 Compression 段),它和 trajectory_compressor 处理的是同一条轨迹的另外一半。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考





