一、先说清楚:缓存命中 vs 缓存未命中
1.1 一句话说清楚
大模型处理你的请求时,主要成本在计算而不是“读取”。如果这次请求的前缀内容和之前某次请求一模一样,平台就不用从头再算一遍,而是复用之前保存下来的中间结果。
缓存命中(Cache Hit):你的请求前缀已经被处理过并缓存了 → 直接复用 →便宜 + 快
缓存未命中(Cache Miss):你的请求前缀没被处理过 → 从头计算 →贵 + 慢
1.2 价格差距有多大?用真实数据说话
以DeepSeek V4 Flash为例(平时价格):
| 类型 | 百万Token价格 | 备注 |
|---|---|---|
| 输入(缓存命中) | 0.02元 | 便宜到几乎可以忽略 |
| 输入(缓存未命中) | 1元 | 是命中价格的50倍 |
| 输出 | 2元 | 输出无法缓存 |
再看DeepSeek V4 Pro:
| 类型 | 百万Token价格 |
|---|---|
| 输入(缓存命中) | 0.025元 |
| 输入(缓存未命中) | 3元 |
| 输出 | 6元 |
差距有多大?同样是100万Token的输入,命中缓存只要2分钱,没命中要3块钱——差了120倍。
缓存命中的价格通常是未命中的十分之一到百分之一,具体取决于模型和厂商。
1.3 缓存命中 vs 未命中:一张图看懂
二、模型是怎么判断缓存是否命中的?——命中逻辑架构
2.1 核心原则:精确前缀匹配(Exact Prefix Match)
所有主流模型的缓存判断逻辑,都遵循同一个核心原则:
缓存命中要求两次请求的“前缀”在字节级别完全一致。
“完全一致”的意思是——哪怕多一个空格、改一个标点,缓存都可能失效。
2.2 三步判断流程
整个缓存判断过程可以分为三步:
2.3 不同厂商的缓存实现差异
OpenAI:内存缓存 + 扩展缓存
OpenAI的缓存机制:
自动开启:所有API请求自动启用,无需改代码
触发条件:提示词至少1024个Token才会触发缓存
命中单位:以128个Token为增量进行缓存命中
缓存时长:
内存缓存:5-10分钟不活跃即失效,最长1小时
扩展缓存:最长24小时
路由机制:基于前缀哈希值路由到特定机器
DeepSeek:硬盘缓存(全球首创)
DeepSeek采用硬盘缓存技术,是全球第一家在大范围API服务中采用硬盘缓存的大模型厂商:
默认开启:所有用户默认享受,无需修改代码
缓存落盘时机有三种:
请求边界落盘:每次请求结束位置产生缓存单元
公共前缀检测落盘:多次请求检测到公共前缀时独立落盘
固定Token间隔落盘:长输入按固定间隔截取缓存
匹配规则:后续请求必须完整匹配缓存前缀单元才能命中
查看命中:返回的
usage字段包含prompt_cache_hit_tokens和prompt_cache_miss_tokens
阿里云:显式缓存 vs 隐式缓存
阿里云提供两种模式:
| 模式 | 特点 | 命中价格 |
|---|---|---|
| 隐式缓存(自动) | 系统自动识别公共前缀,无需配置 | 输入单价的20% |
| 显式缓存(手动) | 主动创建缓存块,确定性高 | 输入单价的10% |
显式缓存需要主动添加cache_control标记:
{ "role": "system", "content": [ {"type": "text", "text": "这里放长文本...", "cache_control": {"type": "ephemeral"}} ] }2.4 为什么说“缓存命中的判断是精确匹配”?
来看DeepSeek官方文档的两个例子:
例1:能命中
第一次请求:A + B
第二次请求:A + B + C → 完整匹配 A+B → ✅ 命中
例2:第一次不能命中,但后续可以
第一次请求:A + B
第二次请求:A + C → 不能完整匹配 A+B → ❌ 未命中
→ 但系统检测到公共前缀 A,把它缓存了
第三次请求:A + D → 完整匹配 A → ✅ 命中
关键洞察:缓存是动态学习的。即使某次没命中,系统也会从失败中提取公共前缀,为后续请求创造命中机会。
三、怎么判断你的请求是否命中了缓存?
3.1 查看API返回的usage字段
大多数模型API会在返回中告诉你缓存命中情况。
DeepSeek API返回示例:
{ "usage": { "prompt_tokens": 1500, "prompt_cache_hit_tokens": 1200, // ← 命中了1200个Token "prompt_cache_miss_tokens": 300, // ← 没命中300个Token "completion_tokens": 200, "total_tokens": 1700 } }计费公式:
费用 = 命中Token数 × 命中单价 + 未命中Token数 × 未命中单价 + 输出Token数 × 输出单价
3.2 VSCode代码:解析缓存命中情况
import requests import json def call_with_cache_check(prompt, api_key, model="deepseek-chat"): """ 调用API并检查缓存命中情况 """ url = "https://api.deepseek.com/v1/chat/completions" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } data = { "model": model, "messages": [ {"role": "system", "content": "你是一个乐于助人的助手"}, {"role": "user", "content": prompt} ] } response = requests.post(url, headers=headers, json=data) result = response.json() # 提取缓存命中信息 usage = result.get("usage", {}) hit_tokens = usage.get("prompt_cache_hit_tokens", 0) miss_tokens = usage.get("prompt_cache_miss_tokens", 0) total_prompt = usage.get("prompt_tokens", 0) # 计算命中率 if total_prompt > 0: hit_rate = (hit_tokens / total_prompt) * 100 else: hit_rate = 0 print(f"📊 缓存命中统计:") print(f" - 命中Token: {hit_tokens}") print(f" - 未命中Token: {miss_tokens}") print(f" - 总输入Token: {total_prompt}") print(f" - 命中率: {hit_rate:.1f}%") # 估算费用(以DeepSeek V4 Flash为例) hit_price = 0.02 / 1_000_000 # 0.02元/百万Token miss_price = 1.0 / 1_000_000 # 1元/百万Token cost = hit_tokens * hit_price + miss_tokens * miss_price print(f" - 预估费用: {cost:.6f}元") return result # 使用示例 # 第一次调用:构建缓存 result1 = call_with_cache_check("中国的首都是哪里?", "your-api-key") # 第二次调用:相同前缀,应该命中缓存 result2 = call_with_cache_check("中国的首都是哪里?请详细介绍一下", "your-api-key")代码解析:
prompt_cache_hit_tokens:命中的Token数,按低价计费prompt_cache_miss_tokens:未命中的Token数,按高价计费命中率 = 命中Token ÷ 总输入Token
四、怎么提高缓存命中率?——实战技巧
4.1 黄金法则:稳定的放前面,变化的放后面
这是最核心、最有效的一条原则:
❌ 错误做法(动态内容在前):
[当前时间: 2026-07-07 10:30] + [系统提示词] + [用户问题]
→ 时间每分每秒都在变 → 缓存永远失效
✅ 正确做法(稳定内容在前):
[系统提示词] + [工具定义] + [项目规则] + [用户问题]
→ 前三项长期不变 → 缓存持续命中
对于Coding Agent来说,请求结构通常是这样:
[tools] 工具定义 ← 长期稳定
[system prompt] 系统提示 ← 长期稳定
[project rules] 项目规则 ← 长期稳定
[conversation history] ← 相对稳定(追加内容)
[tool results] 工具结果 ← 每轮不同
[new user message] "继续" ← 真正新增的只有这几个字
越稳定越靠前,缓存越管用;越动态越靠前,缓存越容易废。
4.2 避免动态内容破坏缓存
以下内容只要出现在请求前缀位置,就会破坏缓存命中:
| 动态内容 | 为什么破坏缓存 |
|---|---|
| 当前时间戳 | 每分每秒都在变 |
| 随机Request ID | 每次请求都不同 |
| 重排过的工具列表 | 顺序一变,前缀就变了 |
| 动态拼接的System Prompt | 每次可能多一句话 |
解决方案:把这些动态内容放到请求的最后面,不要放在开头。
4.3 使用缓存Key(OpenAI)
OpenAI提供了prompt_cache_key参数,可以手动控制路由:
# 相同cache_key的请求会被路由到同一台机器 response = client.chat.completions.create( model="gpt-4o", messages=messages, prompt_cache_key="my-app-v1" # ← 手动指定缓存Key )这样即使前缀略有不同,只要prompt_cache_key相同,请求也会被路由到同一台机器,提高命中概率。
4.4 多轮对话中保持上下文连贯
在多轮对话中,把历史对话放在前面,新问题放在最后:
# ✅ 好的做法:历史在前,新问题在后 messages = [ {"role": "system", "content": system_prompt}, # 稳定 {"role": "user", "content": "第一轮问题"}, # 已固化 {"role": "assistant", "content": "第一轮回答"}, # 已固化 {"role": "user", "content": "第二轮问题"}, # 已固化 {"role": "assistant", "content": "第二轮回答"}, # 已固化 {"role": "user", "content": "新问题"} # ← 只有这个是新增的 ]前面的内容因为已经处理过并被缓存,新增的只有最后一小段,大部分Token都能命中缓存。
五、总结:一张表看懂全部
| 维度 | 缓存命中 | 缓存未命中 |
|---|---|---|
| 发生了什么 | 复用之前计算好的中间结果 | 从头开始计算 |
| 价格 | 极低(通常为未命中的10%-20%) | 正常价格 |
| 速度 | 快(首Token延迟降低80%) | 慢 |
| DeepSeek V4 Flash | 0.02元/百万Token | 1元/百万Token |
| DeepSeek V4 Pro | 0.025元/百万Token | 3元/百万Token |
| 判断方式 | 请求前缀精确匹配缓存 | 请求前缀没有匹配项 |
| 命中条件 | 稳定内容在前 + 动态内容在后 | 动态内容破坏了前缀匹配 |
小编建议
检查API返回的usage字段:看
prompt_cache_hit_tokens和prompt_cache_miss_tokens,知道自己到底命中率多少重排你的Prompt结构:稳定内容放前面,动态内容放后面
避免在请求前缀插入时间戳、随机ID:这些会让缓存完全失效
多轮对话中保持历史上下文顺序:新问题追加在最后
善用缓存Key(如有):把相同类型的请求路由到同一台机器
小编团队自主研发 大型文件翻译、润色文本链接
https://blog.csdn.net/weixin_54524403/article/details/162078447?spm=1011.2415.3001.10575&sharefrom=mp_manage_link
https://blog.csdn.net/weixin_54524403/article/details/162078447?spm=1011.2415.3001.10575&sharefrom=mp_manage_link