做了大半年大模型应用,我越来越觉得“会调接口”和“会用接口”之间,隔着一整条成本曲线。前阵子压一个长上下文的客服问答项目,token 开销一度占到整体预算的七成,领导给的优化周期又很紧。正当我翻 API 文档翻到头皮发麻的时候,突然看到一个参数,试完之后有种“这半年白干了”的感觉——GLM-5.3-Flash 只加了一个参数,单次调用成本降了 6.3 倍,而且不是靠砍模型能力换来的。今天就把这个参数从原理到踩坑完整拆一遍。
这个参数不玄乎,就是上下文缓存(context cache)。如果你正在做大模型应用,尤其是 prompt 前缀基本稳定的长期项目,这篇文章就是为你准备的。你会看到它到底在计费链路里省掉了哪些钱,为什么会带来 6.3 倍这种量级变化,以及不同场景下怎么把命中率提上去。
1. 先搞懂你多花的那笔钱:重复计算前缀
1.1 大模型计费里最容易被忽略的“重复税”
大模型 API 按 token 计费,分输入和输出两块。输出大家都能理解,模型吐了多少字就收多少钱;但输入的计费方式,很多人其实没细想过——每次请求都会把整段 prompt 从头到尾处理一遍。换句话说,如果你的请求由“系统提示词 + 历史对话 + 用户问题”组成,那么每来一个新问题,系统提示词和历史对话都要被重新读取、重新计算一遍。
我习惯用一个餐厅的类比:你每次去同一家店点同一道菜,厨师都从洗菜、切菜开始重新做一遍,完全不管你上一顿刚吃过一模一样的。系统提示词和历史上下文就是那批“提前洗好的菜”,明明可以备着,下次来了直接下锅,却在每一次请求里都被强行从头处理一次。上下文缓存要解决的,正是这种重复劳动。
这个“重复税”在短请求里不明显,但一旦上下文拉长,问题就非常扎眼。我接过一个文档问答项目,每个用户请求都带一份 6000 字的业务说明书作为固定前缀,用户真正的问题只有几十个字。也就是说,输入里面有 95% 的内容是每一次都在重复计算,而用户问的那几个字,反而只占很小一部分成本。这种项目如果不开缓存,每个月就是在往水里扔钱。
1.2 输入 token 才是真正的成本大头
很多团队只看输出 token,因为输出是用户肉眼可见的回答,出了问题第一时间会盯着它。但输入 token 在长上下文场景里,往往是输出的几倍甚至几十倍。举个例子:一个包含 50 轮对话记录的客服请求,用户新输入可能只有 100 token,但由于历史消息全部要带上,输入侧可能已经堆到 10000 token 以上。
不同厂商的定价不完全一样,但普遍规律是输出单价高于输入单价。按理说单价低,输入成本应该不是问题,可架不住数量大。10000 token 的输入和 200 token 的输出放在一起,就算输出单价是输入的三倍,总费用依然是输入主导。所以很多项目的成本大头根本不在生成内容上,而是在“重复搬运历史内容”上。
这也是为什么“只加 1 个参数就能便宜 6.3 倍”会让人觉得夸张。因为它砍的不是无关痛痒的边缘费用,而是整份账单里占比最大的那一块。只要上下文缓存命中,重复输入部分的价格会掉到原来的十分之一甚至更低,整体成本自然出现断崖式下降。
2. 那个参数到底怎么用:把 cache 开关打开
2.1 不同调用方式下,参数放在哪里
在 GLM-5.3-Flash 的接口里,最直接的做法是在请求体中加一个 cache 相关的布尔参数。常见字段名是cache或者use_cache,具体以你使用的 SDK 版本和官方文档为准。我目前用的是 OpenAI 兼容接口,所以操作路径是这样的:
from openai import OpenAI client = OpenAI( api_key="你的 api key", base_url="https://open.bigmodel.cn/api/paas/v4" ) response = client.chat.completions.create( model="glm-5.3-flash", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": "GLM-5.3-Flash 的缓存参数怎么用?"} ], extra_body={"cache": True, "cache_ttl": 3600} ) print(response.usage)注意几个细节。extra_body里传自定义参数,是 OpenAI Python SDK 兼容第三方服务的常见做法,因为官方接口并不一定能识别所有扩展字段,放在extra_body里可以把参数原样透传到服务端。如果你用的不是 OpenAI SDK,而是智谱自己的 SDK,通常直接在请求体顶层加"cache": true就行。
还有一个容易被忽略的点:cache_ttl。这个字段控制缓存的有效期,单位一般是秒。我习惯把 TTL 设成 3600,也就是一个小时,因为客服场景里同一个用户的会话一般不会超过这个时长。如果你的业务是长期稳定的模板调用,可以把 TTL 调大一些,减少冷启动次数。
2.2 怎么确认它真的生效了
加完参数之后,第一件事不是看账单,而是看响应里的usage字段。开启缓存后,响应会多出一些细节数据。我判断是否命中的逻辑很简单:
- 看
usage里有没有prompt_tokens_details,里面有cached_tokens字段 cached_tokens大于 0,说明这次请求的前缀命中了缓存cached_tokens / prompt_tokens,就是这次请求的缓存命中率
第一次请求时cached_tokens往往为 0,这是正常的,因为缓存是冷启动,第一次访问需要把内容存进去。第二次开始,如果前缀完全一致,cached_tokens才会变大。所以测试时不要只发一次请求就下结论,至少要连续跑两三次,观察第二次和第三次的数值。
另外,如果你用curl调试,同样可以在返回 JSON 里直接找到这些字段。我建议把这段响应存下来,后续优化 prompt 时可以用来对比不同改动是否破坏了缓存命中。
3. 便宜 6.3 倍到底是怎么算出来的
3.1 缓存命中后的计费模型
要理解 6.3 倍这个数字,先看两组公式。
没有缓存时,单次成本是:
总成本 = 输入 token 数 × 输入单价 + 输出 token 数 × 输出单价开了缓存之后,输入部分会被拆成两块:
总成本 = 未命中输入 token × 输入单价 + 命中输入 token × 缓存命中单价 + 输出 token 数 × 输出单价缓存命中单价通常只有标准输入单价的十分之一甚至更低。换句话说,同样的输入内容,只要命中缓存,计费标准立刻降了一个数量级。
我拿一个简化例子说明。假设标准输入单价是 P,缓存命中单价是 0.1P。某次请求输入有 10000 token,其中 9000 token 是固定前缀且缓存命中了,剩下 1000 token 是新内容。那么输入侧费用从原来的 10000P 变成:
9000 × 0.1P + 1000 × P = 1900P对比一下,从 10000P 降到 1900P,大约是 5.26 倍。如果前缀更长、新内容更少,倍数还会继续往上走。6.3 倍对应的情况,其实就是缓存命中率在 75% 左右的综合结果。你不需要精确复现这个数字,只需要明白一件事:倍数来自命中部分的价格差,命中率越高,倍数越夸张。
3.2 为什么这个场景能达到 6.3 倍
要拿到 6.3 倍这样的效果,光开参数还不够,场景得对得上。我总结了几类特别吃这个红利的使用方式。
第一类是多轮对话。每一轮新的用户提问,系统都要带上之前的完整对话历史,历史越长,重复计算的输入越多。开了缓存之后,除了最新这一轮的新内容,前面所有轮次都可以走缓存,成本下降非常明显。
第二类是固定文档问答。比如你对同一份产品手册反复提问,系统提示词和文档内容完全不变,变的只是每一轮的具体问题。这类场景下,缓存命中率可以做到很高,成本优势也最大。
第三类是带长 system prompt 的 Agent。Agent 通常会在一开始注入角色设定、工具定义、回复规范,这些内容在一次完整的任务链路里是基本不变的。把参数打开之后,工具定义这部分不会再重复计费。
第四类是后台批量打分、审核任务。同样的规则说明,不同的待处理内容,前缀完全一致,又重复跑几百次,这种场景开缓存几乎是零成本就能省下一大笔。
反例也很明显。如果每个请求的前缀都完全不同,今天一句话明天一段代码,系统提示词就几个字,那缓存命中率会很低,这个参数对你的意义就不大。所以先看你自己的请求结构,再决定要不要花时间调它。
4. 我踩过的坑和排查清单
4.1 缓存死活不命中的问题
我一开始接入时,缓存没有生效,排查了半天,最后发现是 prompt 前缀不稳定造成的。
缓存命中对前缀一致性要求非常严格,可以说是逐字节级匹配。只要前缀里混入了一个时间戳、一个随机数,或者其他每次请求都会变化的内容,整条缓存链就断了。我第一次犯的错是在 prompt 里拼了一个trace_id用于日志追踪,结果这个每次请求都不同的 ID 把前缀搞得支离破碎,缓存一次都没命中。
解决办法是把这种动态信息移到 prompt 末尾,或者放到独立字段里,不要塞进固定前缀。还有一点要特别注意:messages 的结构顺序不能变。system prompt 在前,assistant 历史在后,顺序一旦打乱,缓存就可能失效。我在一次迭代里往对话中间插入了一条 extra 消息,结果后面全乱了,命中率直接从 90% 掉到 20%。
4.2 参数加了但费用没降的问题
有人会遇到这种情况:代码已经加了cache: true,响应里也能看到缓存字段,但账单费用没有明显变化。我建议按下面的顺序排查。
先看响应里的cached_tokens是不是一直是 0。如果一直是 0,说明请求没有命中,重点查前缀稳定性。如果cached_tokens有数值,但费用还是高,那就是命中率太低,看看命中 token 占总输入的比例,低于 50% 说明你的前缀占比不够高,优化空间有限。
再看缓存 TTL。超过有效期的缓存会被清除,长周期任务如果间隔时间太长,缓存早就过期了,相当于每天都在冷启动。需要根据业务节奏调整cache_ttl,让缓存覆盖到请求密集期。
还要确认模型名是不是支持缓存的后缀。GLM-5.3-Flash 这类带 Flash 标识的版本通常对缓存支持较好,但如果你走了一些老版本模型名或者网关代理,可能不支持这个能力。我排查过一个线上项目,模型名没写错,但是前面挂了一层网关,网关会把 prompt 重新组装,导致缓存失效。这种链路缓存问题最难查,只能从日志层面逐层确认。
最后检查 SDK 版本。部分旧版 SDK 会把未知字段直接忽略,你以为参数传上去了,实际服务端根本没收到。升级 SDK 到新版后,问题通常会消失。
4.3 团队落地时的一些建议
参数本身很简单,难的是让整个链路稳定地吃到缓存红利。我习惯把cached_tokens / prompt_tokens这个比例当成成本健康的观测指标。上线前后分别记录一组数字,改动 prompt 之后立刻对比,看命中率是不是掉了。
建议先在测试环境开百分之一流量验证,观察响应里的缓存字段和费用明细,确认没有问题后再全量。不要把缓存开关直接放在上线变更里,万一现场又出现前缀被网关改写的问题,线上费用会变得很难看。
另外,prompt 的前缀设计最好从一开始就固化下来。团队协作时,系统提示词这个文件要有版本管理,不能随便改一句措辞就上生产,因为哪怕只改了一个标点,缓存就全部失效,之前积累的缓存白存了。
最后再分享一个经验
这个参数给我最大的触动,不是省了多少钱,而是让我开始重新审视 API 文档里那些平时瞥都不瞥一眼的字段。很多团队只关注模型能力和响应速度,对成本优化缺乏感知。实际上,一个大模型的 API 能力,从来不只是“能跑”和“跑得快”,还包括“跑得省”。
我现在做新项目,第一件事就是梳理 prompt 前缀结构,把固定内容和动态内容分开,然后评估缓存命中空间,再决定要不要在前期就开启缓存参数。这种思路放到任何一家提供上下文缓存能力的模型厂牌上都适用,逻辑是相通的:把稳定内容缓存下来,只在真正需要变化的地方花新钱。
按我这个思路试一次,再看一眼月度账单,大部分团队都会想把那个参数早点打开。