什么写法会打断 GLM 的缓存命中
数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。
GLM 的上下文缓存是隐式的——官方文档写得很清楚,「智能识别重复的上下文内容,无需手动配置」。这句话的另一面是:它命不中的时候不会报错,不会告警,接口照常返回 200,你只有翻响应里的 usage.prompt_tokens_details.cached_tokens 才知道这一发到底命中没有。所以「打断缓存」这件事在 GLM 上永远是静默的。真正会打断它的写法,官方文档在延迟优化那一篇里几乎点名了:把每次都变的内容放在了 Prompt 前部、在系统提示词里插时间戳或随机编号、每次临时拼一版不太一样的规则、多个入口各写一份近似提示词、以及开了保留式思考却把 reasoning content 改过再传回去。下面按你实际排查的顺序走一遍,最后单列一节交代哪些东西官方压根没写,别去猜。
第一步:先确认到底命没命中,别凭感觉
排查缓存问题最常见的翻车方式,是从「我觉得没省钱」开始猜。官方在延迟优化文档里专门提了一句:接入上下文缓存后,建议不要只凭感觉判断效果,而是观察接口返回中的缓存命中信息。
要看的字段只有一个,在响应的 usage 里:
{
"usage": {
"prompt_tokens": ...,
"completion_tokens": ...,
"total_tokens": ...,
"prompt_tokens_details": {
"cached_tokens": ...
}
}
}
cached_tokens 在官方接口文档里的定义是「命中的缓存 Token 数量」。它是嵌在 prompt_tokens_details 这个对象下面的,不是 usage 的一级字段——这一点在写解析代码的时候很容易踩。官方给的 Python 示例本身就带着防御式写法,先判断 hasattr(response.usage, 'prompt_tokens_details') 再取 cached_tokens,这说明这个子对象并不保证一定存在。
于是排查的第一个分叉点就出来了:
- 这个子对象不存在 —— 你可能压根没拿到缓存相关信息,先别急着改 Prompt,回头看看走的是哪条链路、哪个端点
- 子对象存在但
cached_tokens是 0 或者很小 —— 这才是真正的「被打断了」,可以往下走了
把这个字段接进日志,每次请求都记一条,比事后回忆有用得多。你要的不是某一次的数值,而是同一个业务入口在一段时间里的命中趋势——某天忽然掉下来,通常就对应着某个人改了那段本该稳定不变的提示词。
第二类打断:把每次都变的内容放在了前面
这是最结构性的一类,也是最容易在一开始就写错的。
官方在延迟优化文档里给的组织原则是:把长期不变、可复用的内容放在前部,比如系统角色、回答规范、工具说明、业务规则;把每次请求都会变化的内容放在后部,比如用户本轮问题、检索结果、时间、订单状态。
官方给的知识库问答的顺序范例,前半是固定部分(企业知识库问答助手的角色设定等),后半才是用户本轮问题和本次检索到的知识库片段。文档给出的理由也很直白:前半部分在多次请求中保持稳定,更容易被缓存复用;后半部分虽然每次变化,但不会影响前面固定内容的复用效果。
反过来说就清楚了:如果你把检索结果拼在了角色设定前面,那么前缀在每一次请求里都是新的,后面那段固定的角色设定再稳定也无济于事。很多 RAG 服务的第一版都是这么拼的——先塞资料再讲规则,读起来更顺,但对缓存是最差的排列。
这个改动的成本其实极低,就是把字符串拼接的顺序调一下,但它是所有优化里收益最直接的一条。改之前先把 cached_tokens 记下来,改完再看同一批请求,别一次改三处然后分不清是哪处起了作用。
第三类打断:系统提示词里插了时间戳和随机编号
这一条官方写得非常具体,值得原样理解:如果希望系统提示词被反复缓存,就不要在每次请求中频繁改写它。即使只是增加时间戳、随机编号、临时说明,或者调整段落顺序,也可能降低缓存命中效果。
注意最后半句——「调整段落顺序」也算。也就是说,你没有增删任何一个字,只是把两段规则换了个位置,前缀就变了。这个设计有点反直觉:人读起来还是同一套规则,对缓存来说已经是另一份内容了。
官方点名的反例是这种写法:系统提示词里写着「你是一个客服助手。当前时间是 ×××」,后面跟着具体到秒的时间。时间一秒一变,这条系统提示词就永远是新的,每一次请求都从头算。
官方给的改法不是把时间删掉,而是挪位置:如果时间不是模型判断所必需的信息,可以不放进系统提示词;如果确实需要时间信息,就放到用户问题或者动态上下文那一段里。文档的示例里,系统提示词只留角色与规则,动态上下文那段才写当前日期和用户问题。
同类的隐形杀手还有几种,排查的时候一起看:
- 请求 ID、trace ID 被顺手拼进了系统提示词
- A/B 实验的分组标记写在了提示词开头
- 用户昵称、会话编号这种每人每次都不同的东西被放进了角色设定
- 模板引擎渲染时带出了随机的空白或者换行差异
官方在注意事项里还提了一句「轻微的格式差异可能影响缓存效果」。所以拼接的时候留意换行和空格是不是每次都一致,尤其是用多个模板片段拼起来的场景。
第四类打断:每次临时拼一版不太一样的规则
Agent、文档问答、代码审查这类场景里,工具说明、任务规则、评分标准、输出格式往往都很长。官方的建议是:如果这些内容每次都需要传入,就整理成稳定模板,避免每次请求临时拼接不同版本。
文档举的例子是代码审查——不要每次都让系统随机生成审查标准,而是固定成一套稳定规则(可读性、潜在 Bug、性能问题、安全风险、可维护性建议这样一份清单,外加统一的输出格式要求),后续每次只替换待审查的代码。
这里真正的病根往往不在 Prompt 本身,而在代码结构。一旦你的提示词是由若干个 if 分支拼出来的——「如果用户开了严格模式就多加一段」「如果是内部用户再加一句」——那么组合数一多,每一种组合都是一个独立的前缀,缓存被切得粉碎。把这些差异挪到后面的动态段落里,前缀就能收敛成一份。
第五类打断:多个入口各写了一份差不多的提示词
同一个业务能力被 Web 端、控制台、客服后台同时调用,是很常见的架构。官方的建议是这几个入口共用同一套 Prompt 模板,而不是每个入口各写一份类似但不完全相同的提示词。
「类似但不完全相同」这几个字是关键。人眼看三份提示词觉得它们是一回事,但只要有一个字不同,前缀就分成了三份,各自独立地去建立、去过期。文档还补了一句实际的好处:统一模板不仅有利于缓存复用,也方便后续统一维护和评估效果。
排查手法很简单:把线上所有调用方实际发出去的系统提示词原文抓出来,做一次去重统计。理论上应该只有寥寥几种,如果去重之后有几十上百种,那基本可以确定缓存被打成了碎片。
第六类打断:保留式思考的内容被你改过
这一条属于开了思考能力才会遇到的坑,但一旦踩上很难自己想明白。
官方在思考模式文档里写的是:模型可以在上下文中保留来自先前 assistant 回合的 reasoning content,这有助于保持推理连续性与对话完整性、提升模型表现,并提高缓存命中率。该能力在 Coding Plan 端点默认开启,在标准 API 端点默认关闭;要在 API 端点开启,通过 clear_thinking 参数控制,并且需要把完整、未修改的 reasoning content 传回 API。
后面那句是硬约束,原文的意思很明确:所有连续的 reasoning content 必须与模型在原始请求期间生成的序列完全一致,不要重新排序或修改这些 content,否则会降低效果并影响缓存命中。
现实里怎么被改掉的?多半不是有意的。比如你的会话存储层对消息做了裁剪、把过长的字段截断了;比如做日志脱敏的时候顺手清洗了一遍;比如为了省存储把多轮的思考内容合并压缩了。这些操作对普通的 content 也许无害,对 reasoning content 就是直接破坏。开了保留式思考之后,回传路径上任何一个「顺手处理一下」的环节都要单独审一遍。
以上默认行为均以官方文档当前版本为准。
还有一类不怪你的写法:时效性
官方注意事项里有一条:缓存有合理的时效性,过期后会重新计算。
所以低频场景下命中率天然就低——两次请求隔得久了,前一次建立的缓存已经不在了,你的 Prompt 写得再规整也没用。这类场景不该往缓存上使劲,官方在同一篇文档里给的更实在的思路是:把高度固定的回复直接交给业务系统返回,别调模型。文档举的例子包括「已收到您的反馈」「请先登录后再操作」「当前账号暂无权限」「请补充订单号」「系统繁忙,请稍后重试」这类固定文案——这些在程序里硬编码或者配成模板就行,只有真正需要自然语言理解、上下文判断或个性化生成的时候才调用模型。文档的原话是,这样直接减少了模型请求次数,比单纯依赖缓存更有效。
至于这个时效具体是多久,官方文档里没有找到相关说明,别去猜一个数字然后按它设计调度策略。
排查前先看一眼:你这条链路适不适用缓存计费
这一节放在最后,但排查的时候值得第一个确认,因为它能直接解释「为什么我改完了账单还是没变化」。
官方在缓存文档的计费说明里加了一个提示框:仅适用于标准 API 计费,不包括资源包和 GLM Coding Plan 套餐。
也就是说,如果你走的是编程套餐,缓存这件事的省钱路径跟标准 API 不是一回事,盯着账单去验证优化效果会得不出结论。计费口径上,官方给的差异化策略是:新内容 Token 按标准价格计费,缓存命中的 Token 按更低价格计费,输出 Token 按标准价格计费。具体的价格比例请以官方定价页为准,这类数字会变,不适合抄在文章里当结论。
顺带说一句,缓存只作用在输入侧。输出还是按标准价格算,所以如果你的成本大头在生成侧,缓存能改善的空间本来就有限,这时候该去调的是输出长度而不是前缀结构。想把整套口径理清楚,可以先看跨厂商的大模型 API 缓存计费机制,再回到 GLM 这边看怎么才能命中。
官方文档没说的部分,别自己补
写到这里必须把边界划清楚,因为这块最容易被各种二手教程编出内容来:
- 有没有手动开关、有没有 cache 相关的请求参数:官方描述的是隐式缓存、无需手动配置,除此之外的显式控制方式,官方文档里没有找到相关说明
- 多长的内容才够格进缓存:官方只说了长文档和重复内容的缓存效果最显著,具体的最小长度门槛,官方文档里没有找到相关说明
- 缓存的有效期具体多久:只写了「有合理的时效性」,没有给时长
- 哪些模型支持、支持到什么程度:官方在功能特性里写的是支持所有主流模型,并列举了 GLM-5.2、GLM-5.1、GLM-5 系列等。逐个模型的支持细节请以官方对应的模型文档为准,不要拿别家的缓存机制来类比 GLM
一份可以照着走的排查顺序
- 先确认这条链路走的是标准 API 计费,还是编程套餐——口径不同,验证方法也不同
- 把
usage.prompt_tokens_details.cached_tokens接进日志,拿到一段时间的曲线,别看单次 - 检查拼接顺序:稳定内容是不是真的在前面,变化内容是不是真的在后面
- 全文搜系统提示词里的时间、随机数、请求 ID、实验分组标记,有一个算一个,全挪到动态段落去
- 统计线上实际发出的系统提示词,去重之后看有几种,多的话合并模板
- 如果开了保留式思考,沿着 reasoning content 的回传路径审一遍有没有裁剪、脱敏、重排
- 改一处验一处,别一次改完再看
最容易栽的坑其实只有一个:把「缓存命中率低」当成一个玄学问题去调 Prompt 措辞。它不是玄学,它是前缀是否逐字节稳定的工程问题。你只要能回答「我这次请求的前缀和上次是不是一模一样」,就已经解决了八成。剩下两成是时效性和链路口径,那两成不归 Prompt 管。
再往外一层,缓存只是控成本的一环。命中率稳住之后,下一步该做的是把用量本身看住——可以接着看大模型 API 成本监控怎么做。