问题背景
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 请求的工具列表固定,前缀不变)
验证方法(已完成)
我们通过以下方法完成了最终验证:
- 搭建本地 HTTP 拦截代理:用 Node.js 写了一个简单的代理服务器,拦截 OpenClaw 发给汉得代理的所有请求,记录完整的 request body
- 对比不同请求的前缀:分析拦截到的多个 GLM-5.2 请求,发现它们在第 278 个字符处就开始不同——正好是工具列表的位置
- 确认动态内容来源:差异完全来自工具列表(
tools),其他部分(如model、stream等参数)都相同
对使用者的建议
如果智谱模型的缓存命中率对你很重要
- 方案一:直连智谱 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 采用更宽容的匹配策略,能容忍工具列表这类”结构性变化”。
对于需要优化缓存命中率的用户,建议:
- 统一 agent 的工具配置:尽量让同一个 agent 的工具列表保持一致,减少前缀变化
- 选择对缓存更宽容的模型:如小米 mimo-v2.5,对 OpenClaw 这类动态工具列表的 Agent 更友好
- 向 OpenClaw 社区反馈:推动在 system prompt 中将工具列表放在 CACHE_BOUNDARY 之后,减少前缀变化