跳转到内容

06 - 提示缓存与组装策略

学习目标

本章聚焦 Hermes Agent 的系统提示构建与前缀缓存保护策略。你将了解:

  • 系统提示如何只构建一次并 SQLite 持久化
  • 三种异常状态如何诊断前缀缓存失效
  • 技能/插件内容为何注入用户消息而非系统提示
  • 缓存感知 slash 命令的设计

前置知识

本章涉及系统提示组装的通用原理,建议先阅读:

下文直接聚焦 Hermes 的具体实现。


项目实践

一次性构建 + SQLite 持久化

Hermes 的系统提示在首次 turn 时完整构建,然后持久化到 SQLite 会话数据库:

# 首次 turn:构建 + 持久化
agent._cached_system_prompt = agent._build_system_prompt(system_message)
agent._session_db.update_system_prompt(agent.session_id, agent._cached_system_prompt)
# 后续 turn:从 SQLite 读取,逐字节复用
stored_prompt = agent._session_db.get_session(agent.session_id)["system_prompt"]
agent._cached_system_prompt = stored_prompt

核心优势_build_system_prompt() 是一个重操作(需要聚合 bootstrap 文件、技能描述、插件上下文、工具 schema),只执行一次即可跨 turn 复用。SQLite 读取比每次重建快得多。

前缀缓存保护:一切动态内容注入用户消息

Hermes 严格区分系统提示(稳定前缀)动态上下文(可变后缀)

内容注入位置原因
工具 schema系统提示同一 session 内不变
技能描述用户消息按需加载,变化频繁
记忆 prefetch用户消息每轮可能不同
插件 pre_llm_call 上下文用户消息每轮动态
用户输入用户消息每轮必变

设计原则:所有可能变化的内容都不进入系统提示。系统提示保持跨轮次字节级稳定 → Anthropic prefix cache 前缀匹配 → 只有变化的用户消息部分消耗新 token。

三种异常状态诊断

Hermes 对系统提示的 SQLite 持久化设置了三种诊断状态:

状态含义行为
missing无会话记录正常首次构建,新建记录
null记录存在但值为 NULL旧版本遗留,WARNING 日志,重建
empty记录存在但为空串持久化 bug,WARNING 日志,重建

当 SQLite 写入失败时,Hermes 降级为每次重建系统提示。这会持续导致前缀缓存 miss,成本飙升。运维人员可通过 hermes logs 监控 WARNING 级别的 nullempty 状态。

缓存感知 Slash 命令

Hermes 的 slash 命令设计了 --now 标志来控制缓存失效策略:

  • --now:slash 命令内容注入用户消息,下一轮 API 调用时自然生效,不破坏前缀缓存
  • --now:立即触发缓存失效(如 /reset --now),强制重建系统提示

这使得用户可以在”常规操作不破坏缓存”和”特殊情况立即生效”之间做出选择。


问题与规避

系统提示重建导致的缓存失效

问题:如果 SQLite 写入失败,每轮都重建系统提示,前缀缓存持续 miss。

对策:三种异常状态以 WARNING 级别记录日志。降级重建虽然成本高,但保证了功能可用性。建议定期备份 SQLite 数据库。

技能内容膨胀导致用户消息过长

问题:当加载多个技能时,技能内容注入用户消息可能导致该消息 token 数过大。

对策:技能描述有 60 字符硬限制(见技能系统),确保注入的技能摘要紧凑。详细使用说明在 SKILL.md 正文中,模型按需读取。


设计取舍

系统提示注入 vs 用户消息注入

方案优势代价
用户消息注入(Hermes)系统提示字节级稳定,前缀缓存命中率高动态内容作为用户消息消耗新 token
系统提示注入动态内容在系统提示中,模型可能更重视每轮系统提示变化,前缀缓存持续 miss

Hermes 选择将所有动态内容注入用户消息,牺牲的是动态内容的 token 成本(用户消息本就会每轮变化),保护的是系统提示的前缀缓存命中率。对于 Anthropic 模型,前缀缓存命中可节省约 50-90% 的 prompt token 成本。


参考来源

  • 源码验证:agent/prompt_caching.pyagent/prompt_builder.pyagent/system_prompt.py