OpenClaw调用某些模型缓存命中率低的原因分析

问题背景

OpenClaw 是一个开源的 AI Agent 框架,支持接入多种大模型 API。在使用过程中,我们发现一个奇怪的现象:同一个 OpenClaw 实例,调用小米 mimo-v2.5 模型时缓存命中率高达 99.3%,而调用智谱 GLM-5.2 模型时缓存命中率却是 0%

小米 mimo-v2.5 直连 api.xiaomimimo.com,智谱 GLM-5.2 走汉得 New API 代理(地址:handai-code.hand-china.com),都通过同一个 OpenClaw 实例调用。为什么缓存命中率差异如此之大?本文记录了完整的排查过程和结论。

前置知识:大模型 API 的缓存机制

主流大模型 API(OpenAI、智谱、小米等)普遍支持前缀缓存(Prefix Caching)机制:如果两次请求的 prompt 前缀相同,服务端会跳过已缓存部分的计算,直接复用,从而大幅降低延迟和成本。

缓存命中率通常在 API 响应的 usage 字段中体现:

"usage": {
    "prompt_tokens": 35000,
    "prompt_tokens_details": {
        "cached_tokens": 34500
    }
}

其中 cached_tokens 表示命中缓存的 token 数。命中率 = cached_tokens / prompt_tokens。

排查过程

第一步:确认 OpenClaw 代码层面是否有缓存控制

通过阅读 OpenClaw 源码,确认了以下事实:

  • OpenClaw 有 cacheStablePromptPrefix 机制,但这是内部 memoization 优化(避免重复构建相同 prompt),不控制 API 层面的缓存
  • OpenClaw 对第三方 API(非 api.openai.com)默认不发送 prompt_cache_key 等缓存控制参数
  • 缓存命中率的统计完全依赖 API 返回的 usage 字段,OpenClaw 自动读取,不需要额外配置
  • system prompt 通过 <!-- OPENCLAW_CACHE_BOUNDARY --> 标记分为静态前缀和动态后缀,但此机制仅对 Anthropic 模型的 cache_control 有效

结论:OpenClaw 代码本身不会主动阻止缓存命中。

第二步:用 curl 直接测试智谱 GLM-5.2 的缓存能力

绕过 OpenClaw,直接用 curl 调用汉得代理访问 GLM-5.2:

# 固定 system prompt(~6000 tokens),动态内容在末尾
# 第1次:cached=0(冷启动)
# 第2次:cached=5952,命中率 99.8%
# 第3次:cached=5952,命中率 99.8%

结论:智谱 GLM-5.2 通过汉得代理完全支持前缀缓存,命中率可达 99%+。

第三步:对比 OpenClaw 调用两个模型的缓存数据

模型 调用方式 缓存命中率 备注
小米 mimo-v2.5 OpenClaw 直连 api.xiaomimimo.com 99.3% cacheRead=115,968
智谱 GLM-5.2 OpenClaw 走汉得代理 0% cacheRead=0
智谱 GLM-5.2 curl 直接调汉得代理 99.8% cacheRead=5,952

关键发现:

  • 同一个 OpenClaw 代码,小米能命中缓存,智谱不能
  • 同一个智谱模型,curl 能命中缓存,OpenClaw 不能
  • 汉得代理本身不是瓶颈(curl 测试已证明)

第四步:排除各种可能因素

逐一测试了以下可能影响缓存的因素:

测试项 结果 结论
stream=true vs stream=false 两者都能命中 不影响
带 tools 参数 vs 不带 两者都能命中 不影响
prompt_cache_key=null vs 不带 两者都能命中 不影响
prompt_cache_key=session-1 能命中 不影响
动态内容在 prompt 末尾 能命中 不影响
动态内容在 prompt 开头 第1次不命中,后续命中 影响首次

所有测试项在 curl 环境下都能正常命中缓存。问题不在这些参数上。

第五步:分析 OpenClaw 的 system prompt 结构

通过代码分析,OpenClaw 的 system prompt 结构如下:

[静态指令内容 - 固定不变]
[<!-- OPENCLAW_CACHE_BOUNDARY -->]
[动态项目上下文 - 可能变化]
[Runtime 信息 - 每次变化]

理论上,静态内容在前,动态内容在后,前缀缓存应该正常工作。但实际结果是 0% 命中率。

根因分析

排除汉得代理

汉得 New API 代理是透传模式,curl 测试已证明它完全支持缓存。而且其他用户通过同一代理使用 Codex、Cline、OpenCode 等工具,缓存命中率可达 10%-30%。

排除模型本身

智谱 GLM-5.2 的 curl 测试证明它支持前缀缓存,命中率 99%+。

锁定问题范围

问题锁定在:OpenClaw 发送给智谱的 API 请求,与 curl 发送的请求,在前缀部分存在差异。

关键实验:用本地 HTTP 代理拦截请求

为了看清 OpenClaw 实际发出了什么请求,我们在本地搭建了一个 HTTP 拦截代理,将 OpenClaw 发给汉得代理的请求先经过本地,记录完整的 request body,再转发到汉得代理。

结果发现了根本原因:OpenClaw 每次发送的 system prompt 前缀都不一样——因为工具列表在动态变化。

请求4 (bodyLen=64KB):
- read: Read file contents
- write: Create or overwrite files
[只有2个工具]

请求8 (bodyLen=291KB):
- read: Read file contents
- write: Create or overwrite files
- edit: Make precise edits to files
- apply_patch: Apply multi-file patches
- exec: Run shell commands
[有几十个工具]

第一个差异出现在 system prompt 的第 278 个字符——正好是工具列表的位置。OpenClaw 根据不同的 agent 配置动态生成工具列表,导致每次请求的前缀不同。

智谱 vs 小米的缓存策略差异

这就是为什么同样走 OpenClaw,小米能命中缓存而智谱不能:

  • 智谱 API:精确前缀匹配——前缀有任何不同,整个 prompt 都不命中缓存。这是更严格但也更可预测的行为。
  • 小米 API:宽容前缀匹配——即使前缀有变化,只要大部分相同,就能命中缓存。这更智能,但也可能在某些情况下误判。

这个差异解释了所有观察到的现象:

  • OpenClaw + 小米 = 命中(小米宽容匹配,忽略工具列表变化)
  • OpenClaw + 智谱 = 不命中(智谱精确匹配,工具列表不同就不认)
  • curl + 智谱 = 命中(curl 请求的工具列表固定,前缀不变)

验证方法(已完成)

我们通过以下方法完成了最终验证:

  1. 搭建本地 HTTP 拦截代理:用 Node.js 写了一个简单的代理服务器,拦截 OpenClaw 发给汉得代理的所有请求,记录完整的 request body
  2. 对比不同请求的前缀:分析拦截到的多个 GLM-5.2 请求,发现它们在第 278 个字符处就开始不同——正好是工具列表的位置
  3. 确认动态内容来源:差异完全来自工具列表(tools),其他部分(如 modelstream 等参数)都相同

对使用者的建议

如果智谱模型的缓存命中率对你很重要

  • 方案一:直连智谱 API,绕过汉得代理,减少中间层可能的干扰
  • 方案二:使用小米 mimo-v2.5,它的前缀匹配更宽容,对 OpenClaw 这类 Agent 更友好
  • 方案三:等待 OpenClaw 优化,如果确认是 OpenClaw 注入了不必要的动态前缀内容,可以向 OpenClaw 社区反馈

关于缓存命中率的实际影响

缓存命中率 0% 意味着:

  • 每次请求都需要完整处理 prompt(~35,000 tokens)
  • 延迟更高(首次处理 vs 缓存复用)
  • 成本更高(如果 API 按处理 token 计费)

但对于日常使用来说,如果 API 本身价格合理且延迟可接受,缓存命中率低并不影响功能正确性。它主要影响的是成本效率和响应速度

总结

项目 结论
汉得代理 不是原因,完全支持缓存
智谱 GLM-5.2 支持缓存,但前缀匹配精确
小米 mimo-v2.5 支持缓存,前缀匹配宽容
OpenClaw 动态生成工具列表导致每次请求前缀不同
根本原因 工具列表变化 + 智谱精确匹配 = 0% 命中

这个问题的本质是:AI Agent 框架(如 OpenClaw)在构建 API 请求时,会根据 agent 配置动态生成工具列表,导致每次请求的 system prompt 前缀不同。智谱 API 采用精确前缀匹配策略,前缀有变化就不命中缓存;而小米 API 采用更宽容的匹配策略,能容忍工具列表这类”结构性变化”。

对于需要优化缓存命中率的用户,建议:

  1. 统一 agent 的工具配置:尽量让同一个 agent 的工具列表保持一致,减少前缀变化
  2. 选择对缓存更宽容的模型:如小米 mimo-v2.5,对 OpenClaw 这类动态工具列表的 Agent 更友好
  3. 向 OpenClaw 社区反馈:推动在 system prompt 中将工具列表放在 CACHE_BOUNDARY 之后,减少前缀变化