GLM 上下文缓存怎么才能命中:触发条件与写法
数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。
先把结论说完:GLM 的上下文缓存是隐式缓存,官方文档明确写了「无需手动配置」,所以你在 API 里找不到一个叫「开启缓存」的参数——找不到不是你漏看了,是它本来就没有。命中与否完全由你发出去的 messages 内容决定:官方的说法是系统对输入的消息内容进行计算,识别出与之前请求中相同或高度相似的部分,检测到重复内容就复用之前的计算结果。所以「怎么让它命中」这个问题,实际上等价于「怎么让你的前缀内容保持稳定」。判断有没有命中只有一个入口,就是响应里的 usage.prompt_tokens_details.cached_tokens 这个字段,它给出这次请求里有多少输入 Token 是命中缓存的。
第一件事:别再找开关了
很多人接 GLM 的时候第一反应是去翻文档找缓存的开启参数,或者找一个类似「缓存断点」「缓存标记」的字段,翻半天没找到就以为 GLM 不支持。
官方文档在功能特性里第一条就是「自动缓存识别」,原话是隐式缓存、智能识别重复的上下文内容、无需手动配置。这句话有两层意思:好处是你不用改代码就已经在吃缓存的红利了;代价是你也没法强制让某一段内容进缓存——你唯一能操作的杠杆就是内容本身怎么组织。
至于覆盖范围,官方文档写的是「广泛兼容性,支持所有主流模型」。具体到某个型号支不支持,以官方文档当前版本为准,别照着模型名去推断。
命中没命中,只看一个字段
官方文档把这一点叫「透明化计费」:响应中会详细显示缓存命中的 Token 数量,字段路径是 usage.prompt_tokens_details.cached_tokens。
文档里给出的响应结构长这样:
{
"usage": {
"prompt_tokens": 1200,
"completion_tokens": 300,
"total_tokens": 1500,
"prompt_tokens_details": {
"cached_tokens": 800
}
}
}
这里有个工程上的细节值得抄下来。官方的 Python 示例读这个值的时候没有直接点属性,而是先做了存在性判断:
cached = response.usage.prompt_tokens_details.cached_tokens \
if hasattr(response.usage, 'prompt_tokens_details') else 0
在另一段示例里判断条件更严一点,是 hasattr(...) 加上 cached_tokens 本身为真才去算比例。这个写法说明 prompt_tokens_details 不是每次都保证存在的结构,直接点下去可能抛异常。你在自己的埋点里最好照这个防御性写法来,不然监控代码本身会成为线上崩溃点。
算命中率的口径也要留意:官方一段示例里除的是 total_tokens,另一段除的是 prompt_tokens。缓存只作用在输入侧,所以真正有意义的分母是 prompt_tokens——用 total_tokens 当分母会把输出 Token 也算进去,得到一个偏低的数字。你要是自己搭成本看板,这两个分母必须先统一,不然两周后自己都看不懂曲线为什么会跳。
容易命中的三种摆法
官方文档的代码示例区实际给了三个可复用的模式,都是围绕「把不变的东西固定在前面」这一个思路。
稳定的系统提示词
最基础的那组示例是两次请求用完全一样的 system 内容,只换 user 问题。文档在最佳实践里把这条叫「使用稳定的系统提示词」,示例里的 system prompt 是写死成一个模块级常量的。
这条看着简单,实际最容易在业务代码里被破坏——比如有人在 system 里拼当前时间、拼用户昵称、拼一个随机的 trace id。这些动态片段一旦落在前缀里,前缀就每次都不一样。
长文档放进 system 消息
官方的「长文档分析示例」和最佳实践里的「文档内容复用」是同一个套路:把长文档内容格式化进 system 消息,用户的具体问题放在 user 消息里,然后对同一份文档反复提问。文档里那个封装函数的注释写得很直白——多次调用相同文档,系统提示词会被缓存,第二次及以后的调用会命中缓存。
这个模式适用面很广:合同问答、代码库问答、规范文档答疑,都是「一份长材料 + N 个短问题」的形状。反过来说,如果你的实现是把文档塞进 user 消息、每次再拼一段不同的前言进去,那就等于亲手把可复用的前缀切碎了。
多轮对话按顺序追加
官方的多轮对话示例是把 system 加历史消息拼成一个 conversation_history 列表,新一轮请求发的是这个列表加上新的 user 消息。最佳实践里那个 ConversationManager 类做的是同一件事:初始化时把 system 放进 self.history,之后每轮只往列表尾部 append。
重点在「只往尾部追加」。缓存复用的是前面那一段,所以只要历史部分不动,前缀就是稳定的。至于第二次调用「会复用之前的对话缓存」,这也是官方示例注释里的原话。
批量任务共用一段前缀
官方的「批量处理优化示例」是一个 for 循环,把同一个 system_prompt 配上一批不同的代码片段,逐条送去做代码审查。这个形状对应的是文档里说的「重复任务」场景:一致的指令,多次处理相似内容。
要提醒一句,文档在这段示例里顺手统计了每次调用的耗时。那是示例代码的写法,不是性能承诺,别把它当成基准。
什么会把命中打掉
官方文档「注意事项」里的「缓存机制理解」这一栏,把关键的几条讲得很克制,但每一条都有信息量:
- 缓存基于内容相似度自动触发,无需手动配置;
- 完全相同的内容缓存命中率最高;
- 轻微的格式差异可能影响缓存效果;
- 缓存有合理的时效性,过期后会重新计算。
第三条是实践中最坑的。「轻微的格式差异」意味着多一个空格、换行符从 LF 变成 CRLF、JSON 序列化时键的顺序变了、模板引擎渲染时多缩进了两格,都可能落进「差异」这一侧。所以稳定前缀不能靠人守纪律,得靠代码保证——把 system prompt 固化成常量或模板文件,序列化统一参数,别在前缀里拼任何带时间戳、随机数、会话 ID 的内容。
第四条说的是时效性。官方只写了「有合理的时效性,过期后会重新计算」,没有给具体的存活时长,那就不要去猜一个数字填进你的架构设计里。工程上的安全做法是:把缓存当成一个尽力而为的优化,别把成本预算建立在「一定命中」的假设上。
另外「性能考虑」那一栏提到首次请求建立缓存可能稍慢、需要合理管理对话历史长度、避免过于频繁的内容变化。这些是官方文档的表述,我们没有跑过这些接口,具体的耗时表现不做任何断言。
命中之后,账单口径怎么变
这一节最需要看清边界。官方在「计费说明」开头挂了一个提示框:仅适用于标准 API 计费,不包括资源包和 GLM Coding Plan 套餐。
也就是说,如果你走的是编程套餐,缓存这条省钱路径在计费上跟你没关系——省的是别的账,别在套餐场景下拿命中率去解释账单。这一点在做成本归因时经常被搞混,具体的分账口径可以对照 GLM API 计费方式 一起看。
标准 API 下的计费结构,官方说的是差异化计费:新内容 Token 按标准价格计费,缓存命中 Token 按优惠价格计费,输出 Token 按标准价格计费。三段口径要分开记——尤其是输出侧不吃缓存这一点,如果你的任务是长输出短输入,那再怎么优化前缀,账单大头也不会动。具体的价格数字以官方定价页为准,本文不列。
想横向理解这套机制在各家 API 里的通用形态,可以看 API 缓存计费机制;把命中率接进日常监控的做法,可以参考 API 成本监控。
最容易栽的坑
按踩坑概率从高到低排:
第一,在 system 里拼动态内容。时间戳、随机 ID、用户信息这些东西挪到 user 消息里去,前缀留给真正不变的部分。
第二,把命中率当成 SLA。官方原文是「基于内容相似度自动触发」加上「有合理的时效性」,两个都是不保证的措辞,你的预算模型里要留出全价的余量。
第三,监控代码直接点 prompt_tokens_details。官方示例都做了 hasattr 保护,你也做。
第四,命中率的分母算错。缓存只在输入侧起作用,分母用 prompt_tokens。
下一步建议很实在:先别改架构,把 cached_tokens 和 prompt_tokens 这两个值打进日志跑几天,看看你的真实命中比例落在什么区间。如果本来就很高,说明你的前缀已经稳了,再优化收益有限;如果基本为零,那八成是前缀里混了动态内容,先去把那段拼接找出来。接入侧的基础配置可以回看 GLM API 接入。