Hermes-Agent——(四)Prompt分层构建与缓存

📅 2026/7/24 22:32:56
Hermes-Agent——(四)Prompt分层构建与缓存
上篇聊了工具执行策略这篇聊一个更现实的问题——钱。每次调大模型 API对话历史里的 System Prompt 都要原封不动发一遍。一轮长对话跑下来光 System Prompt 重复传输的 token 量就很可观——全价。但 Anthropic 有个 Prompt Caching 机制如果请求前缀跟上次一样模型直接读缓存不用重新算。缓存命中时输入 token 费用降 90%。规则很直白请求前缀System Prompt 历史消息越稳定 越省钱 响应越快。问题是 Agent 的运行需要动态内容。用户记忆、插件上下文、当前时间、Session ID——这些每轮都在变。你要 System Prompt 不变这些动态信息往哪放这就是 hermes-agent 的分层架构要解决的核心矛盾既要 prompt 稳定不变给缓存省钱又要注入动态信息让 Agent 正常工作。三层架构解法是把内容拆成三层分界线只有一个标准放这里会不会让缓存失效。第一层缓存 System Prompt稳定层Agent 身份、工具使用规则、长期记忆快照、Skills 索引、项目上下文文件——这些在一次会话里基本不变。构建一次存到 SQLite多轮复用。只有两个触发点会重建上下文压缩、模型切换。这层占请求前缀的绝对大头也是 Prompt Caching 省钱的核心。只要它不变缓存前缀就能匹配上被缓存的那部分 token 费用降 90%。第二层ephemeral_system_prompt追加层预加载的 Skill 全文、子 Agent 任务描述、API Server 的 instructions——这些也是 system 级别的指令享有 system role 的高指令遵循权重但每轮可能不同。拼在缓存层之后。代价很明显内容变了缓存就失效。但 hermes-agent 赌的是大多数场景下 ephemeral 在一次会话内是稳定的。CLI 配置不变Gateway context 不变预加载的 Skill 在构造时确定。赌赢了省钱赌输了全价——跟没做分层一样。源码的注释说得明白# Note: ephemeral_system_prompt is NOT included here. Its injected at# API-call time only so it stays out of the cached/stored system prompt.ephemeral_system_prompt 不参与这里的构建。它只在 API 调用时刻注入因此不会进入缓存或持久化的 system prompt。第三层User Message 动态注入动态层外部记忆检索结果、插件上下文——每轮必变。直接拼到当前 user message 末尾彻底不碰 system 消息。缓存前缀纹丝不动。三层的关系一句代码就说清楚了effective_systemself._cached_system_promptor# 先拿缓存层ifself.ephemeral_system_prompt:# 如果有追加层effective_system(effective_system\n\n# 用两个换行拼在后面self.ephemeral_system_prompt).strip()cached ephemeral 拼成最终的 system 消息。动态内容走另一条路注入到 user message。缓存层里到底拼了什么缓存层的构建逻辑按顺序逐层拼接最后用空行连起来。源码注释说是 7 层但实际 append 操作有十几个。按逻辑归成四组身份与行为规则。Agent 身份优先读 SOUL.md没有就用硬编码的默认身份。然后根据已加载的工具动态追加行为指导——有 memory 工具就拼记忆使用说明有搜索聊天记录就拼历史搜索说明。强制工具调用禁止光说不练Google 和 OpenAI 模型各自有专属操作指令。记忆与知识。内置 MemoryStore 的快照——用户偏好、项目约定——在构建时从磁盘加载后冻结。外部记忆系统的工具声明——给了搜索外部记忆的工具但声明不是数据本身。项目上下文文件HERMES.md → AGENTS.md → CLAUDE.md → .cursorrules 按优先级只加载第一个找到的单文件超 2 万字符会头尾保留截断。能力清单。Skills 索引——不是 SKILL.md 全文是名称一行描述的目录模型根据目录决定调哪个查看详情的工具。付费功能的可用状态清单。环境与元信息。当前时间、Session ID、模型名——冻结在构建时刻。平台格式规则Telegram/Discord/WhatsApp 各有要求。阿里云 API 的身份修正workaround阿里云总是返回错误的模型名。WSL/Termux 等特殊环境的路径提示。构建完就冻结。源码注释里有个关键表述Rebuilding would pick up memory changes from disk that the model already knows about (it wrote them!), producing a different system prompt and breaking the Anthropic prefix cache.翻译过来如果每轮都重新从磁盘读 memory模型刚写进去的新记忆会立刻出现在 system prompt 里——缓存前缀变了缓存失效。而且这是死循环模型写记忆 → 下一轮 system prompt 多了新内容 → 缓存失效 → 全价。所以构建时拍快照冻结除非压缩或换模型绝不重建。追加层的取舍ephemeral_system_prompt 的通路不止一条CLI 的--skills参数启动时读 SKILL.md 全文拼进 ephemeral子 Agent任务委派工具创建子 Agent 时任务目标写进子 Agent 的 ephemeralAPI Server外部系统通过 API 调用时传入的指令它跟缓存层的核心权衡就一句话方案Cache 稳定性指令遵循度ephemeral 放 systemhermes 选变就失效高system role 权重大ephemeral 放 user永远稳定低混在用户闲聊里这不是一个正确答案的选择是看你更在意省钱还是更在意指令执行精度。hermes-agent 选了 ephemeral 放 system代价是内容变了缓存从头废。为什么动态内容走 User Message两个原因第一个是钱第二个是正确性。钱外部记忆检索结果和插件上下文每轮必变。放 system prompt → 前缀每轮不同 → 缓存每轮失效 → 全价。放 user message 末尾前缀完全不变。对 LLM 来说 system 和 user 都是上下文效果基本等价成本天差地别。正确性System Prompt 存在 SQLite 里跨 Gateway 实例共享。如果每轮改存哪个版本重建会重新读磁盘上的 memory 文件可能拿到模型刚写的内容——“用户喜欢 Python记住了”——这条记忆出现在下一轮 system prompt 里而且跟上一轮的 prompt 不一样。不一致。User Message 注入的实现也干净操作的是api_messagesAPI 发送时的临时副本不碰messages持久化到 SQLite 的原始消息。源码注释写得很清楚API-call-time only — the original message inmessagesis never mutated, so nothing leaks into session persistence.数据库存的是干净历史API 发的是带注入的版本。两层分离。另外 Memory Prefetch 在主循环外只调一次源码注释写得很清楚Reuse the cached result on every iteration to avoid re-calling prefetch_all() on each tool call (10 tool calls 10x latency cost).缓存保护策略三个缓存逐级兜底内存缓存有→ 直接用 没有 → SQLite 里有→ 恢复Gateway 跨实例复用 没有 → 全新构建 → 同时存内存和 SQLite两个触发点会让缓存失效但行为不一样上下文压缩对话太长压缩历史 → 缓存失效从磁盘重新加载 memory → 重建。压缩后 memory 是新读的包含对话中写入的内容。模型切换用户换模型 → 直接清空缓存 → 下一轮重建。但不重新读 memory用的是已有的记忆数据。两个触发点的 memory 行为不一致——压缩后刷新换模型不刷新。这不一定是 bug但说明缓存失效是个敏感操作什么该重建什么该保留需要分清楚。Gateway 场景下 SQLite 持久化尤其关键。Gateway 为每条消息创建新的 Agent 实例没有 SQLite 的话每次都重新构建 prompt缓存永远命中不了。有 SQLite 之后消息1→ 新 Agent → 全新构建 → 存 SQLite → 缓存未命中 消息2→ 新 Agent → 从 SQLite 读到跟上次完全一致的 prompt → 缓存可能命中总结第一分层的标准不是内容类型是缓存稳定性。什么放哪一层不取决于它是什么内容而取决于它会让缓存失效吗。稳定层塞不变的东西追加层塞偶尔变的东西动态层塞每轮变的东西。每一步都在算成本。第二冻结快照。构建时的状态冻结运行时不回流。Agent 对话中写了新记忆 → 不反哺到当前会话的 system prompt → 等压缩重建时才重新加载。不是所有系统都需要这个但如果你的 Agent 会自我修改知识你得想清楚回流时机。第三追加层是显式赌博。system role 的权威性 vs cache 的稳定性。hermes-agent 选了权威性代价是 ephemeral 变的时候缓存从头废。你的场景可能反过来。关键是别装不知道这个 trade-off。第四注入不持久化。messages 表存干净历史临时消息副本是带注入的临时数据。动态内容在 API 时刻拼用完就丢。