MiniMax 主动缓存怎么显式设置:cache_control 断点放在哪
数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。
MiniMax 的 Prompt 缓存分两种模式:一种是官方称为「被动缓存」的自动缓存,官方描述是「自动识别重复的上下文内容,无需更改接口调用方式」;另一种官方称为「主动缓存」,定义写的是「在 anthropic API 中使用的需要显式设置参数的缓存模式」,那个要显式设置的参数就是 cache_control。所以两者的分界不在于你打的是哪个端点,而在于你有没有在请求体里显式打上 cache_control 这个标记。主动缓存说到底只有一个动作——决定 cache_control 这个标记加在哪一块内容上。加在哪里,从请求开头一直到那一块为止的全部内容就作为一个前缀被整体缓存;加错位置,或者前面任何一个字符变了,这个前缀就整段失效。理解主动缓存,本质上就是理解「前缀」两个字:它按 tools、system、messages 的固定顺序累积,是有层次的,改动上游会连带下游一起作废。
先分清你在用哪一套缓存
这一点容易一开始就搞混。官方 Prompt 缓存那一页把两种模式并列写着:自动缓存是被动的,系统自己识别重复的上下文内容,调用方式不用改;而「在 anthropic API 中使用的需要显式设置参数的缓存模式」,官方称之为主动缓存,单独用一页文档讲。
这里要先纠正一个很容易先入为主的印象:被动缓存并不是「OpenAI 兼容接口专属」。官方 Prompt 缓存那一页的代码示例是并排两个 Tab,第一个就是 Anthropic SDK 示例,环境变量写的是 ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic,而整段示例里没有出现任何 cache_control,靠的正是系统自动识别。也就是说,走 Anthropic 兼容地址、但请求体里不打标记,你吃到的仍然是被动缓存。判断依据只有一条:有没有显式设置 cache_control,而不是你连的哪个 base_url。
官方主动缓存那页给的示例里,客户端初始化写的是:
import anthropic
client = anthropic.Anthropic(
base_url="https://api.minimaxi.com/anthropic",
api_key="<your api key>"
)
主动缓存这一整页的示例都建立在这条 Anthropic 兼容路径上,官方对主动缓存的定义本身也写死在「anthropic API 中」这几个字上。至于把 cache_control 原样搬进 OpenAI 兼容的请求体里会发生什么,官方文档里没有找到相关说明——既然没写,就别按「应该也能用」去赌,照官方示例的写法走。关于用 Anthropic SDK 完整接入的步骤,可以看用 Anthropic SDK 接入 MiniMax 的官方接法那一篇。
两种模式之间还有两个容易被忽略的差别,动手前值得先对一遍。
一个是门槛:自动缓存那页在注意事项里给缓存命中标了一个最小输入 token 门槛(具体数值以官方文档为准),而主动缓存这一页并没有出现对应的门槛说明。两页的适用条件不能互相套用,用哪套就照哪页读。
另一个是模型范围,这一条更要命。官方那张 Cache 对比表里,两种模式的「支持模型」列写的并不是同一份清单:被动缓存那一列是 MiniMax-M3 加上 M2.7、M2.5、M2.1 三个系列;主动缓存那一列是 M2.7、M2.5、M2.1、M2 四个系列,M3 不在其中(两份清单均以官方文档当前版本为准)。这意味着「这个模型支持缓存」推不出「这个模型支持显式打 cache_control」,两件事的模型集合本来就不重合。选模型和选缓存模式必须一起定:先看你要用的模型出现在哪一列,再决定是靠自动识别,还是自己排断点。
断点加在哪:标记的是「结束位置」不是「这一块」
官方给的最小写法是把长文本放进 system 数组,然后在那个块上加标记:
system=[
{"type": "text", "text": "You are an AI assistant..."},
{
"type": "text",
"text": "<the entire contents of 'Pride and Prejudice'>",
"cache_control": {"type": "ephemeral"}
}
]
这里有个反直觉的地方:cache_control 不是「把这一块缓存起来」,而是「可缓存内容到这里为止」。官方原文的说法是,使用 cache_control 参数标记可缓存内容的结束位置。所以上面这个例子里,第一个没打标记的系统块同样在缓存范围内——因为它排在断点前面。
官方把这条明确成三个核心原则之一:缓存内容是累积的,用 cache_control 标记一个块时,缓存内容是从所有先前的块按顺序生成的,每个缓存都依赖于它之前的所有内容。想清楚这一点,后面所有的失效行为都好解释了。
配套的还有一条:你可以在静态内容的末尾只使用一个缓存断点,系统会自动找到最长的匹配前缀。也就是说不用逐块去标,标一个收尾的就够。
前缀的拼接顺序是固定的:tools → system → messages
官方写明缓存前缀按 tools → system → messages 的顺序创建,这个顺序形成一个层次结构,每个级别都建立在前一个级别之上。
这个顺序不是文档排版的先后,它直接决定了失效范围。官方在缓存失效那一节的说法是:每个级别的更改都会使该级别及所有后续级别失效。翻译成日常场景就是——你临时给 Agent 加了一个工具,tools 变了,那么 system 和 messages 上原本命中的缓存也跟着一起没了;反过来,你在对话末尾追加一轮用户消息,前面的 tools 和 system 不受影响。
所以工程上的取舍很清楚:工具清单和系统提示词要尽量稳住,别在每次请求里动态拼接时间戳、随机 ID、会话编号这类东西塞进系统提示的前部。动态内容往后放,这是官方最佳实践里反复强调的写法——把静态可复用的内容(工具定义、系统指令、示例等)放在 prompt 的开头。
哪些内容块可以打标记
官方列出的可缓存对象包括这几类:tools 数组中的工具定义;system 数组中的内容块;messages.content 数组中的文本块,user 和 assistant 两种轮次都适用;以及 messages.content 里 tool_use 和 tool_result 类型的内容块,同样两种轮次都适用。上面这份清单以官方文档当前版本为准。
工具那一类有个现成的用法值得抄:官方缓存工具定义的示例里,cache_control 是放在最后一个工具上的,这样所有工具定义——包括它前面定义的任何其他工具——都会作为单个前缀缓存。这比给每个工具都标一遍省事,也符合「标结束位置」的语义。
回溯窗口:为什么改一个字,整段缓存就没了
这套机制值得在动手排断点之前先弄明白。官方三原则里的另外两条是「向前顺序检查」和回溯窗口限制:系统从显式的 cache 断点向前检查缓存命中,以尽量命中最长的缓存;但在每个显式断点之前最多只检查约 20 个块,检查这么多块后仍未找到匹配项,就停止检查并移至上一个显式断点(如果有的话)。这个块数以官方文档当前版本为准。
官方给的例子把三种结果摆得很清楚。假设你在第 30 个块上设了 cache_control,然后重复发请求:
- 前面所有块都没改,系统能命中第 1 到第 30 块的全部内容;
- 第 25 个块被改了,系统从第 30 块往前找,找到第 24 块能匹配上,于是第 1 到第 24 块命中缓存;
- 第 5 个块被改了,系统从第 30 块往前找,找到第 11 块仍然匹配不上,本次缓存失效。
第三种情况就是回溯窗口在起作用:从第 30 块往前数够二十来块正好到第 11 块,再往前它就不查了。这也解释了一个容易让人摸不着头脑的现象——为什么只是把对话很靠前的一句话改了个措辞,整轮请求的缓存就全丢了。不是缓存不够聪明,是它按设计只往回看这么远。
顺带解决另一个问题:如果一次调用的内容块数量本身就超过回溯窗口,官方的建议是添加额外的 cache_control 参数,让所有内容都有机会被缓存到。断点在这里的作用是「分段锚点」,把一个超长的前缀切成几段分别锚住。
断点数量有上限,超了会被静默丢弃
官方常见问题里写得很直白:一次调用最多支持 4 个 cache_control 参数,若超过 4 个,只取从后向前最近的 4 个。这个数量上限同样以官方文档当前版本为准。
「只取从后向前最近的 4 个」这句话值得留意——超额的断点不会报错,是被安静丢掉的,而且丢的是最靠前的那些。你在系统提示词开头精心设的那个锚点,很可能就是被丢掉的那一个,然后你会看到一个既不报错、命中率又莫名其妙偏低的结果。
官方那个综合示例演示的正是把四个断点用满的排法:一个放在最后一个工具定义上收住整个 tools,两个放在 system 里分别收住指令段和知识库上下文段,最后一个放在当前这轮用户消息的文本块上。官方说这种模式特别适用于带大型文档上下文的 RAG 应用、使用多个工具的 Agent 系统、需要保持上下文的长对话,以及需要独立优化 prompt 不同部分的应用。
多轮对话还有个官方明说的省事写法:每一轮用 cache_control 标记最终消息的最后一个块即可,之前标记过的块不需要再标一遍——只要还在生命周期内被访问到,它们仍然会命中缓存并刷新。同时官方建议在系统消息上也保留一个 cache_control,这样万一它因为长时间未使用被驱逐,下一个请求会把它重新缓存回来。
生命周期:会自动续,但只对「被用到」的部分续
官方给出的缓存生命周期是一个分钟级的短窗口(具体时长以官方文档为准),并且明确写着:每次命中缓存内容时,缓存生命周期都会自动刷新,无需额外费用。
这句话有两层意思。好的一层是:只要请求密度够,一段长上下文可以靠不断命中一直续下去,续期本身不收钱。不那么好的一层是:刷新的前提是「被命中」。对话冷了一段时间没人说话,前缀就会过期;再回来发一句,看到的就是一次完整的缓存写入,而不是命中。官方在常见问题里把「确认调用在缓存生命周期内进行」单列为排查项,就是这个原因。
所以主动缓存真正吃香的场景是官方列的那几类:包含许多示例的 prompt、大量上下文或背景信息、具有一致指令的重复任务、长时间的多轮对话——共同点都是短时间内高频复用同一段前缀。一天跑两次的批处理任务,指望缓存省钱是不现实的。
用 usage 的三个字段确认到底命中没有
不用猜,官方给了可直接读的字段。在 usage 对象里(流式传输时看 message_start 事件):
cache_creation_input_tokens:创建新缓存条目时写入缓存的 token 数量;cache_read_input_tokens:本次请求从缓存中读取的 token 数量;input_tokens:既没从缓存读、也没用于创建缓存的输入 token,也就是最后一个缓存断点之后的那部分。
官方还给了换算关系:total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens。按位置理解就是——断点之前已缓存的算读取,断点之前正在缓存的算写入,断点之后的不符合缓存条件。官方对健康状态的描述是:有效使用缓存时,input_tokens 通常会比你的总输入小得多。
拿这三个字段自查非常直接。第一次请求应该看到写入不为零、读取为零;第二次改掉用户消息再发,应该看到写入归零、读取接上第一次写入的量。如果第二次的写入数依然很大,说明前缀根本没对上,回去查是不是有动态内容混进了断点之前。
计费上和被动缓存差在哪
官方那张 Cache 对比表把两种模式的计费方式并排写着:被动缓存是命中缓存的 token 以优惠价格计费、写入缓存的部分无额外计费;主动缓存是命中缓存的 token 以优惠价格计费、首次写入缓存的 token 需要额外计费。缓存读取和缓存写入在官方定价页上也是独立于标准输入价的两列。具体单价,以及它们与标准输入价之间的相对关系,一律以官方定价页为准,本文不列数字。
差别的重点不在数字,而在于主动缓存多出了「首次写入要额外计费」这一项。前缀命中不了,你付出的就不只是「没省到」,而是每一次请求都要为同一段内容重新产生一次写入计费。一个把变动内容拼在系统提示词前部的实现,配上一个大文档,很可能一直在做无效写入而毫无察觉。所以判断划不划算的关键,本质是看同一段前缀在生命周期内被复用了几次——复用次数够多,读取的优惠价才有机会摊平首次写入那笔额外开销;一次都复用不上,断点就等于白打。
关于缓存计费结构的通用讨论,可以看提示缓存是怎么计费的;把缓存命中率纳入日常成本盯盘的做法,参考API 成本怎么监控。
命中率不对时的排查顺序
官方常见问题给的四条,按从便宜到麻烦的顺序排一下就是一份可用的排查清单:
- 内容一致性:确认被缓存的部分在多次调用中完全相同,并且
cache_control打在同一个位置。官方把这一条排在常见问题的第一位,动态时间戳、随机排序的工具列表、每次重新序列化导致的空格差异都算「不相同」。 - 是否还在生命周期内:确认调用发生在缓存存活窗口之内。
- 块数量是否超过回溯窗口:内容块太多时补一个额外的
cache_control,把长前缀切段锚住。 - 断点是不是被丢掉了:数一数请求里到底放了几个
cache_control,超过上限的会被从前往后丢弃。
排完这四条还是不对,再回头看层次结构那条——是不是上游的 tools 或 system 悄悄变了,把下游一并带崩了。多人协作的 Agent 项目要特别留意这种情况:有人给工具描述改了个错别字,按官方「每个级别的更改都会使该级别及所有后续级别失效」的规则,别人那条对话上原本命中的缓存也跟着作废。官方在缓存失效那一节只说明了失效的范围,并没有提到失效会以报错或任何提示的形式暴露出来,所以这类问题只能靠 usage 字段自己看出来。
最后一句
主动缓存的开关成本很低——加一个 cache_control 字段而已,但它把「prompt 该怎么排版」这件事变成了成本问题。上线前值得做的一件事是:把请求体按 tools、system、messages 三段过一遍,逐段问自己「这一段在下一次请求里会一模一样吗」。答案是「会」的部分往前挪,答案是「不一定」的部分往后挪,断点放在两者之间。这件事做对了,后面的字段监控和成本核算才有意义。