问题背景
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 发送的请求,在前缀部分存在差异。
具体来说,最可能的原因是:
- OpenClaw 在请求的前缀位置注入了动态内容(如时间戳、session 标识、或其他元数据),导致每次请求的前缀都不同
- 智谱 API 的前缀匹配是精确的——前缀有任何不同,整个 prompt 都不命中缓存
- 小米 API 的前缀匹配是宽容的——它可能识别出哪些部分是”结构性变化”(如时间戳),忽略这些变化,仍然匹配缓存前缀
为什么这个解释最合理?
这个解释完美契合所有观察到的现象:
- OpenClaw + 小米 = 命中(小米宽容匹配,忽略动态变化)
- OpenClaw + 智谱 = 不命中(智谱精确匹配,不认变化后的前缀)
- curl + 智谱 = 命中(curl 没有动态变化,前缀固定)
- Codex/Cline + 智谱 = 部分命中(这些工具的动态变化可能更少或位置不同)
如何验证?
要最终确认这个结论,需要做以下验证:
- 在 New API 后台对比请求日志:对比 OpenClaw 发给智谱的请求 body 和 Codex 发给智谱的请求 body,找出前缀部分的具体差异
- 开启 OpenClaw 的 cacheTrace:在配置中设置
cacheTrace.enabled: true,查看详细的缓存追踪数据 - 抓包分析:用 mitmproxy 等工具拦截 OpenClaw 发出的实际 HTTP 请求,检查前缀内容
对使用者的建议
如果智谱模型的缓存命中率对你很重要
- 方案一:直连智谱 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 | 发给智谱的请求前缀可能包含动态内容 |
| 根本原因 | OpenClaw 请求前缀 + 智谱精确匹配 = 0% 命中 |
这个问题的本质是:AI Agent 框架(如 OpenClaw)在构建 API 请求时,不可避免地会加入一些动态元数据,而不同模型服务端对这些动态变化的容忍度不同。小米做得更智能,智谱做得更严格,这导致了相同的 Agent 在不同模型上表现出截然不同的缓存行为。