Grok 提示缓存最佳实践清单:六条官方建议逐条拆开讲
数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。
官方对提示缓存的定性是它由 xAI API 自动执行,紧接着又建议设置 x-grok-conv-id 头来最大化命中率——这两句话连在一起读才完整:自动执行的是机制,不等于每次都能命中。官方文档在 Best Practices 页把要点压成了六条:始终设置会话标识、用稳定的会话 ID、绝不修改历史消息、把静态内容前置、盯住 cached_tokens、以及把代码写成没有缓存也能正常工作。这六条里,前四条决定你能不能命中,第五条决定你能不能发现没命中,第六条决定没命中的时候你的服务会不会一起垮掉。下面按上线顺序逐条拆开,并把官方 FAQ 里几个容易误解的结论一并交代清楚。
第一条:会话标识必须显式传,三种 API 三种写法
官方原文的第一条是「Always set x-grok-conv-id(Responses API 用 prompt_cache_key)」,理由写得很直白:它把请求路由到同一台服务器,从而最大化缓存命中。
这条之所以排第一,是因为它揭示了 Grok 缓存的一个实现前提——缓存条目是按服务器存储的(官方 Maximizing Cache Hits 页写的是 cache entries are stored per-server)。也就是说,就算你的提示词前缀一字未改,只要这次请求落到了另一台机器上,那台机器上没有你的缓存,照样是 miss。会话标识起的是「粘性路由」的作用,不是「缓存开关」的作用。这个因果关系搞反了,后面所有排查都会跑偏。
三种接入方式各有各的传法,官方文档分别给了示例:
- Chat Completions API:走 HTTP 头
x-grok-conv-id。用 OpenAI SDK 兼容模式接入时,放在extra_headers里。 - Responses API:不走头,直接在请求体里放
prompt_cache_key字段。官方说明它的作用与x-grok-conv-id完全一致,同样是路由到同一台服务器。用 SDK 时放在extra_body里;用 AI SDK 的 xAI provider 则写成providerOptions.xai.promptCacheKey。 - gRPC API:用 xAI SDK 时,把
x-grok-conv-id作为 gRPC metadata 在构造Client时传入。
这里有个很容易踩的工程坑:如果你的服务同时用了 Chat Completions 和 Responses 两条路径(比如老接口没迁完),两边的写法不一样,很可能只有一边真正带上了标识。上线前把出网请求打一份日志,确认两条路径都带了。
第二条:会话 ID 要稳定,不是每次随机生成
官方建议用 UUID 或者你应用自己的 session ID。关键词是稳定:同一段对话的每一次请求都要用同一个值。
实践中出问题的往往不是「不会生成」,而是「生成得太勤」。常见的三种翻车姿势:每次请求前 uuid4() 一遍、把用户 ID 和时间戳拼在一起、在无状态的函数计算里没有把 ID 透传下去。这三种都会让每次请求看起来是一个新会话,路由到哪台机器就完全随机了。
反过来说,官方 FAQ 里也确认了这一点的另一面:想强制 miss,办法就是换一个 x-grok-conv-id,或者干脆不传这个头——请求会被路由到可能不同的服务器,那里没有你的缓存。做 A/B 对照或者排查「到底是不是缓存导致的行为差异」时,这是官方认可的手段。
第三条:历史消息只准追加,改一个字都不行
官方 What Breaks Caching 页的开篇只有一句话:任何对早期消息的改动都会破坏缓存,只能在末尾追加新消息。
文档给了三个 miss 的具体样例,值得一条条对照自查:
- 编辑:把某条 assistant 消息的内容改短了(示例里是把一段完整回复替换成一句话),前缀匹配即刻断裂。
- 删除:从历史里整条移除一条 assistant 消息。
- 重排:把 system 消息和 user 消息的顺序对调。
第三种最阴险,因为很多框架会在拼装消息时对 system 消息做「归一化」处理——比如统一提到最前、统一合并多条 system。你自己没写这行代码,但框架替你写了,结果就是每次请求的前缀都不完全一致。接第三方对话框架时,务必把最终发出去的 messages 数组原样打出来看一眼。
第二种翻车的典型场景是上下文裁剪:会话变长以后,为了控制输入长度,很多实现会把最早的几轮丢掉。这个动作从省钱角度看似乎合理,实际上它把整个缓存前缀连根拔了——从此往后每一轮都是全量重算。要裁就一次裁到位并接受这一次 miss,别做成「每轮都滑动窗口丢一条」,那等于永远命中不了。关于命中率本身的系统性优化手段,另有一篇怎么把 Grok 的缓存命中率做上去可以对照着看。
第四条:静态内容前置
官方第四条写的是把系统提示词、few-shot 示例、参考文档放在开头,让它们构成一个稳定的前缀。
这条的依据在 How It Works 页:缓存是从 messages 数组的开头开始匹配的,系统检查开头有多少条消息与之前的请求完全一致,那段一致的部分就是「前缀」,从缓存里取。所以顺序决定一切——同样的内容,放在开头是可复用资产,放在中间就只能给它后面的所有内容陪葬。
由此可以推出一条排版纪律:凡是每次请求都会变的东西(当前时间戳、随机 ID、用户实时状态、检索出来的动态片段),能往后放就往后放。很多人习惯在 system 提示词里塞一句「当前时间是 ×××」,这一句话就足以让整个系统提示词每次都 miss。要么去掉,要么挪到最后一条 user 消息里。
第五条:盯住 cached_tokens,别靠感觉
官方第五条明确了监控口径:如果 cached_tokens 一直是 0,就去检查会话 ID 和消息顺序。
字段路径两套 API 不一样,这点在排查时最容易看错地方:
| 接入方式 | 读取路径 |
|---|---|
| Chat Completions API | usage.prompt_tokens_details.cached_tokens |
| Responses API | usage.input_tokens_details.cached_tokens |
| gRPC(xAI SDK) | response.usage.cached_prompt_text_tokens |
取值语义官方也给了对照表:等于 0 是完全没命中,属于首次请求或缓存被清理后的正常现象;大于 0 表示前缀的一部分或全部命中,数值就是被复用的 token 数;等于 prompt_tokens 是全量命中,官方注明这种情况少见,一般出现在原样重发同一个请求的时候。
官方还给了一个健康的多轮对话应该长什么样的示意:随着轮次增加,cached_tokens 应该逐轮上台阶,且大致等于上一轮的 prompt_tokens。如果你的曲线是平的、一直贴着 0,那不是「缓存没生效」这种模糊结论,而是明确指向前面三条里的某一条出了问题。把这个指标接进你的可观测面板,和常规的接口成本监控放在一起看,比事后翻账单有效得多。
第六条:缓存必然会 miss,代码要扛得住
官方措辞是「Handle cache misses gracefully」,理由是驱逐和路由的存在让命中无法保证,你的应用必须在没有缓存的情况下也能工作。
How It Works 页的警告框把话说得更硬:提示缓存不是 100% 保证的,缓存条目可能因内存压力被驱逐,请求也可能被路由到不同服务器。FAQ 里回答「缓存条目能存活多久」时同样没有给出任何时长承诺,只说可能因服务器负载或重启在任何时刻被驱逐,用会话标识可以提高保留概率。
翻译成工程要求就是两条:第一,不要把「命中」写进任何硬逻辑(比如「如果没命中就报错重试」),命中与否只影响成本和首字延迟,不影响功能;第二,做成本预算时不能按满命中算,要按一个保守的命中假设算,把 miss 当常态。计费口径上,官方说明缓存 token 按「缓存提示词价格」计费,明显低于常规提示词价格,具体倍率随模型不同,以官方定价页为准——注意它是降价不是免费,缓存部分依然计入账单。跨厂商的缓存计费差异可以参考缓存计费的通用机制。
推理模型多出的一条硬要求
这条不在六条清单里,但官方在 What Breaks Caching 页用加粗写了:对推理模型,必须把上一轮响应里的 reasoning_content 一起带回去,漏掉它是缓存未命中的首要原因。
官方给的两条路子二选一:
- 把上一轮的
reasoning_content原样回传(官方称为加密推理内容,细节在文本推理能力文档里); - 或者改用有状态的 Responses 形态,用
previous_response_id让服务端自动接续对话。
之所以专门点名,是因为绝大多数对话历史的存储实现只存了 role 和 content 两个字段,reasoning_content 在落库那一步就被丢掉了。你的消息数组看起来一模一样,但对模型来说前缀已经不同了。用推理模型又发现命中率异常,先查这个字段有没有被存下来。
FAQ 里几个被误解得最多的点
- 缓存会不会影响输出质量:官方回答是不会。缓存只加速提示词处理阶段,输出与从头计算完全一致。所以「担心缓存导致回答变差所以关掉」是没有依据的。
- 流式请求支不支持:支持。官方补了一个细节——流里的第一个空 token 对应的正是缓存查找与预填充阶段。
- 工具调用支不支持:支持。官方说明可缓存的前缀包含直到工具调用结果为止的所有消息,只要前缀不变,后续请求照样受益。这意味着 Agent 类应用的长工具链其实是缓存的受益者,前提是工具返回结果被原样、按顺序追加进历史。
- 哪些模型能用:官方 Best Practices 页写的是所有
grok语言模型都提供提示缓存,并把「哪些模型支持缓存」与「缓存 token 具体怎么计价」这两个问题一并指向官方定价页。定价页和模型页的 Text API 表格确实把「缓存输入」单独列成了一列,逐个文本模型分别标注,等于把支持范围直接写进了价目表;图像生成和语音那两张表没有这一列,因为它们不属于语言模型。所以确认某个模型能不能吃到缓存,最直接的路径不是翻缓存文档,而是去定价页看这一列有没有对应条目,并以定价页当前版本为准。
上线前最后过一遍
把六条压成一次五分钟的自查:出网请求带没带会话标识(两套 API 分别确认);这个标识在同一段对话里是不是恒定的;框架有没有偷偷重排或合并 system 消息;上下文裁剪策略是不是在每轮都砍前缀;cached_tokens 有没有进监控且读的是对应 API 的正确字段路径;推理模型有没有把 reasoning_content 存下来并回传。
最容易栽的坑排第一位的其实不是技术问题,而是认知问题——把缓存当成「省钱开关」去追求 100% 命中,然后为了命中去扭曲业务逻辑(比如死活不敢裁上下文,结果输入越滚越长,省下的缓存价还不够多出来的 token 钱)。缓存是顺手拿的收益,不是可以押注的前提。前缀该断的时候就让它断,把注意力放回到「这段上下文到底有没有必要每次都发」这个更根本的问题上。