先算一笔账。同样是往 DeepSeek-V4-Flash 里塞 100 万 token 的输入,如果你命中了上下文缓存,按 2026 年 8 月的官方定价,缓存命中价是 0.007 美元(非高峰),没命中就是 0.22 美元。中间差多少?31 倍。
换句话说,你那个每天早上 7 点自动跑的 Agent,如果每次都老老实实把整段 system prompt、工具定义、示例文档原封不动地重传一遍,等于每次都在付 31 倍于"本可以付"的价格。而真正会省钱的人,只是把 prompt 的顺序换了一下,其余代码一行没改。
这篇文章不讲玄学,只讲四件事:缓存的原理、怎么让缓存命中、怎么验证它真的命中了、以及写一个"命中率稳定在 90% 以上"的 Agent 要避开哪些坑。所有价格和机制都来自 DeepSeek 官方文档(2026 年 8 月 19 日核对),代码可直接跑。
一、先看懂原理:为什么缓存能省这么多
大模型处理你的输入时,会先做一件事:把每个 token 都算出一个向量(KV cache),然后才进入真正的生成阶段。对于一段完全相同的开头——比如你精心写的 3000 字 system prompt,加上 500 字的工具 JSON Schema 定义——模型每一次请求都要重新计算这一整段的向量。
这纯粹是重复劳动,而且是对算力和真金白银的双重浪费。提示词缓存(Prompt Caching)的思路就是:把这段重复计算的结果存到磁盘上,下次请求遇到一模一样的前缀,直接读缓存,不重新算。
这里藏着两个决定你能不能省钱的关键约束。第一,缓存是按"前缀"匹配的,而且要求完全一致。你的输入是一个 token 序列,缓存系统只认"从第 1 个 token 开始,连续相同的那一段"。只要第 1 个 token 变了,或者中间某个 token 变了,整段前缀都可能失效。第二,缓存有生命周期。它不是永久存着,写进磁盘后如果一段时间没人再用,就会被清掉。
所以省钱的本质,不是"加个开关",而是"把不变的内容稳定地放在最前面,把会变的内容放到最后面"。这句话就是全文的核心,剩下的都是它的展开。
二、DeepSeek 的缓存:自动开启,但命中规则有讲究
DeepSeek 的上下文缓存(Context Caching on Disk)对全体用户默认开启,不需要改任何代码,也不用加任何参数。官方文档里写得很清楚:每一次请求都会触发硬盘缓存的构建,后续请求只要前缀与之前重叠,重叠部分就从缓存里取,计为"缓存命中"。
但"自动"不等于"随便写都能命中"。官方明确给出了三条命中规则,这是很多人省不到钱的原因:
第一,请求边界持久化。每一轮请求会在"用户输入的末尾"和"模型输出的末尾"各生成一个缓存前缀单元。下一轮请求只有完整复用这个前缀单元,才算命中。
第二,公共前缀检测持久化。系统检测到多个请求共享同一个前缀时,会把这段公共前缀单独固化为一个缓存单元,供后续请求复用。
第三,固定 token 间隔切分。对于超长的输入或输出,系统会按固定 token 间隔切出缓存单元,避免"因为一直没走到末尾而整段都无法缓存"。
官方给的两个例子非常能说明问题。例一:第一轮请求是 A+B,第二轮是 A+B+C,第二轮能完整匹配 A+B 这个缓存单元,命中。例二:第一轮是 A+B,第二轮是 A+C,因为 A+C 没法完整匹配 A+B,这一轮不命中——但系统会检测到两轮共享前缀 A,把 A 固化为缓存单元,等第三轮 A+D 到来时,就能命中 A 了。
翻译成人话就是:多轮对话天然利于缓存,但前提是你别乱动历史;单轮任务里,越靠前的公共内容越容易被固化成缓存单元。 这就是为什么"稳定前缀放前面"能直接决定你省多少钱。
这里有个值得单独拎出来的细节:DeepSeek-V4 支持思考模式(默认开启),思考过程会产生很长的推理输出。上面第三条"固定 token 间隔切分"规则,意味着超长的输出(包括超长的思考过程)同样会被切成缓存单元。所以如果你的 Agent 每次都要跑一大段推理,把这段推理原样留在历史里,下一轮也能吃到它的缓存红利——这再次印证了"历史不要乱整理"的重要性。
再补一个容易被忽略的点:DeepSeek 的价格是分时段的。官方定价表里,缓存命中的输入价非高峰 0.007 美元/百万 token,高峰 0.014 美元;未命中非高峰 0.22 美元,高峰 0.44 美元。高峰时段是 UTC 01:00–04:00 和 06:00–10:00,换算成北京时间就是 09:00–12:00 和 14:00–18:00。也就是说,同样的 Agent,你在国内白天跑,比晚上跑贵一倍。把重活挪到非高峰时段,本身就是零成本的省钱。
三、三家对比:谁最省、怎么开
把主流三家的缓存机制摆在一起看,差异一目了然。
DeepSeek:自动开启,无需参数。命中价约为未命中的 1/30(Flash 非高峰 0.007 对 0.22 美元)。V4-Flash 和 V4-Pro 都支持,上下文窗口都是 100 万 token,最大输出 384K。这套机制对国内用户最省心——你什么都不用做,只要 prompt 顺序对,它就自己命中。
Anthropic(Claude):需要手动埋点。在 prompt 里用 cache_control 标记"从这里开始缓存",缓存读价是基础输入价的 0.1 倍(省 90%),但缓存写价是 1.25 倍(写比正常贵 25%)。缓存有效期 5 分钟,超时失效,且要求被缓存的前缀至少 1024 token(Sonnet/Opus,Haiku 是 2048)。适合"前缀很长且重复率极高"的场景,但要自己管理过期。
OpenAI:自动缓存,命中价省 50%,要求前缀至少 1024 token。省得不如前两家多,但胜在零配置,你只要保持 prompt 前缀稳定,它就自动生效。
结论很直接:如果你是 DeepSeek 用户,你手上已经握着一张 30 倍的优惠券,只是很多人没意识到它的触发条件。 而且这不是什么"新用户专享"或"限时活动",它是长期、自动、对所有账号生效的。你过去几个月多付的那部分钱,不是模型贵,而是 prompt 顺序没摆对。下面的实操就以 DeepSeek 为主线。
四、实操:把命中率从 0 拉到 90% 的三种写法
写法一:多轮对话,历史不要"改",只能"追加"
这是命中率最高的场景。官方例一已经说明:只要第二轮完整复用第一轮的前缀(system + 第一轮 user + 第一轮 assistant),就命中。
import requests
API = "https://api.deepseek.com/chat/completions"
KEY = "sk-你的key"
def chat(messages):
r = requests.post(API, headers={"Authorization": f"Bearer {KEY}"},
json={"model": "deepseek-v4-flash", "messages": messages})
return r.json()["choices"][0]["message"]["content"]
# 第一轮
messages = [
{"role": "system", "content": "你是一个严格的代码审查员,逐条列出问题。"},
{"role": "user", "content": "审查这段代码:def add(a,b): return a+b"},
]
reply = chat(messages)
messages.append({"role": "assistant", "content": reply})
messages.append({"role": "user", "content": "重点看有没有类型问题"})
# 第二轮:前缀(system+第一轮user+第一轮assistant)完全复用 → 缓存命中
print(chat(messages))
关键动作:把 assistant 的回复原样 append 回 messages,再把新问题 append 到最后。如果你在第二轮里"顺手改写了"第一轮的措辞,或者重新排序,前缀就断了,缓存失效。
写法二:单轮任务,把静态材料钉在最前面
如果你的 Agent 是"每次只问一个问题"的单轮任务(比如定时抓取、批量打分),命中全靠公共前缀检测。正确顺序是:
① 固定 system prompt(角色 + 输出规范)
② 工具/函数的 JSON Schema 定义
③ 固定的 few-shot 示例
④ 固定的参考文档片段
⑤ ← 缓存分割线,以上全部不变
⑥ 本轮才变化的部分:用户真正的输入
import requests
SYSTEM = "你是数据分析助手,只输出 JSON,不要多余文字。"
TOOLS = [{"type":"function","function":{"name":"query_db",
"parameters":{"type":"object","properties":{"sql":{"type":"string"}},"required":["sql"]}}}]
DOCS = "产品表字段:id, name, price, created_at。价格单位是分。"
def analyze(question):
r = requests.post("https://api.deepseek.com/chat/completions",
headers={"Authorization": f"Bearer {KEY}"},
json={"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": SYSTEM + DOCS},
{"role": "user", "content": question},
],
"tools": TOOLS})
return r.json()
# 问题随便换,前缀(SYSTEM+DOCS+TOOLS)始终不变 → 持续命中
for q in ["本月营收多少", "客单价多少", "复购率多少"]:
print(analyze(q)["choices"][0]["message"])
注意我把 DOCS 直接拼进了 system 字段,而不是单开一条 user 消息。因为缓存匹配的是"从第 1 个 token 开始的连续前缀",把静态内容全部压进最前面的 system 里,能保证前缀稳定。
写法三:Anthropic 手动埋 cache_control(如果你在 Claude 上跑)
Claude 需要显式告诉它"从这里缓存"。放的位置越靠后,缓存读价越便宜(因为缓存的是该断点之后到内容末尾这段)。
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system=[{"type":"text",
"text":"你是法律助手...(这里放够 1024 token 的固定内容)",
"cache_control":{"type":"ephemeral"}}], # ← 在这里缓存
messages=[{"role":"user","content":"合同里这条合法吗?"}],
)
print(resp.content)
同一个 system 内容在 5 分钟内重复调用,第二次起就是缓存读价(省 90%)。代价是每次写缓存多付 25%,所以低频调用别埋点,高频且前缀极长的场景才划算。
五、怎么确认缓存真的命中了:看 usage 字段
写了半天,你怎么知道缓存到底命中没有?答案藏在 API 返回的 usage 字段里。DeepSeek 每次响应都会带回两组数字:prompt_cache_hit_tokens(命中的 token 数)和 prompt_cache_miss_tokens(未命中的 token 数)。
import requests, json
r = requests.post("https://api.deepseek.com/chat/completions",
headers={"Authorization": f"Bearer {KEY}"},
json={"model":"deepseek-v4-flash",
"messages":[{"role":"system","content":SYSTEM},
{"role":"user","content":"本月营收多少"}]})
u = r.json()["usage"]
hit = u.get("prompt_cache_hit_tokens", 0)
miss = u.get("prompt_cache_miss_tokens", 0)
total = hit + miss
print(json.dumps(u, indent=2, ensure_ascii=False))
print(f"缓存命中率: {hit/total*100:.1f}%")
典型输出长这样:
{
"prompt_tokens": 50234,
"completion_tokens": 812,
"total_tokens": 51046,
"prompt_cache_hit_tokens": 48000,
"prompt_cache_miss_tokens": 2234
}
缓存命中率: 95.6%
这个命中率就是你省钱的"体检报告"。每次上线一个 Agent,先跑十次,看命中率:如果稳定在 90% 以上,说明 prompt 结构对了;如果在 30% 以下甚至直接是 0,别急着调模型,先去查前缀——八成是 system 开头混进了会变的东西,或者多轮历史被整理了。把"命中率"当成和"报错率"一样重要的监控指标,是省钱的第一步。有条件的话,把这个数字写进你的日志或监控面板,让它每天和你打个照面。
六、账单实测:命中前后差多少
用 DeepSeek-V4-Flash 非高峰价做一个 30 天账单推演,最能说明问题。假设一个 Agent 每天跑 100 次,每次输入 50000 token,其中 48000 token 是固定的 system + 工具定义 + 文档,只有 2000 token 是真正的用户输入。
写法错误(每次都重传整段,缓存命中率 0%):
每天输入成本 = 100 次 × 50000 token × 0.22 美元 / 1M
= 100 × 50000 × 0.00000022
= 1.10 美元/天
30 天 = 33 美元
写法正确(固定前缀命中缓存,只有 2000 token 走未命中价):
每天输入成本 = 100 × (48000 × 0.007 + 2000 × 0.22) / 1M
= 100 × (48000 × 0.000000007 + 2000 × 0.00000022)
= 100 × (0.000336 + 0.00044)
= 0.078 美元/天
30 天 = 2.33 美元
一个月从 33 美元降到 2.33 美元,省下 93%。同样的代码,同样的模型,只差一个 prompt 顺序。如果你的输入更长、调用更频繁,这个差距还会更大——因为它是个乘数,不是个减法。
七、多步 Agent 循环:一次任务里缓存会反复命中
如果你写的不是"一问一答",而是一个真正会调用工具的 Agent,那缓存的价值更大——因为一次任务里,缓存会命中很多次。
一个典型的 Agent 循环是这样的:模型先看 system + 用户问题,决定调用某个工具;你把工具执行结果 append 回消息列表,模型再看一遍,决定继续调用下一个工具还是收尾;如此往复,直到它输出最终答案。这里的关键在于:每一轮,模型都会重新接收整段历史。
这意味着什么?第一轮,模型收到的是 system + user,这段被写进缓存。第二轮,模型收到的是 system + user + assistant(tool_call) + tool_result——前三个元素和上一轮完全一致,命中缓存,只有新 append 的 tool_result 走未命中价。第三轮、第四轮同理,固定的前缀被反复命中,越到后面的循环,缓存省得越多。
import requests, json
def run_agent(user_query):
messages = [
{"role":"system", "content": SYSTEM + DOCS}, # 固定前缀
{"role":"user", "content": user_query},
]
for step in range(5): # 最多 5 轮工具调用
r = requests.post("https://api.deepseek.com/chat/completions",
headers={"Authorization": f"Bearer {KEY}"},
json={"model":"deepseek-v4-flash",
"messages": messages, "tools": TOOLS}).json()
msg = r["choices"][0]["message"]
messages.append(msg) # 原样追加,别改动
if not msg.get("tool_calls"):
return msg["content"]
for tc in msg["tool_calls"]:
result = execute_tool(tc["function"]["name"],
json.loads(tc["function"]["arguments"]))
messages.append({"role":"tool",
"tool_call_id": tc["id"],
"content": json.dumps(result, ensure_ascii=False)})
return messages[-1]["content"]
写这个循环时有两个动作是省钱的命门,也是大多数人写错的点:第一,messages 用一个列表从头到尾持续 append,绝不在中途重建——重建等于把前缀清零,前面攒的缓存全废;第二,tool_result 用 ensure_ascii=False 存成可读中文,但不要动它的位置——它天然就是"最新的、会变的部分",就该待在列表末尾。只要这两个动作守住,一个五步的 Agent 任务,固定前缀能命中四次,只有每轮新增的 tool_result 按未命中价结算。
八、踩坑清单:这六个坑,命中率杀手
坑一:把时间戳、随机数、用户昵称塞进 system 开头。 这是最常见的自杀式写法。只要前缀第一个 token 变了,后面全废。任何"每次都会变"的东西——当前时间、请求 ID、会话编号——统统放到最后面,或者干脆不进 prompt。
坑二:中途改工具定义。 你今天给函数加了个参数,明天删了个字段,JSON Schema 的 token 序列就变了,之前固化的缓存单元全部失效。工具定义要当"生产代码"对待,改动要批量、要克制。
坑三:多轮对话里"整理"历史。 有人为了省 token,第二轮时把第一轮的对话总结成一句再发。看似省了输入,实则把前缀打断了——总结出来的那句话和原始历史 token 对不上,缓存全丢。要么原样追加,要么干脆重开一轮,不要两头不靠。
坑四:忽略 Anthropic 的 5 分钟 TTL 和 1024 下限。 你埋了 cache_control,但两次调用隔了 10 分钟,或者被缓存的前缀不足 1024 token,缓存早就失效了,你以为省了钱其实一直在付写价 1.25 倍。埋点前先确认"前缀够长 + 调用够密"。
坑五:高峰期硬跑重活。 DeepSeek 的 peak 时段(北京时间 09:00–12:00、14:00–18:00)价格是 off-peak 的两倍。批量任务、数据回填、大规模打分这些"跑一次很久"的活,排到晚上或凌晨,成本直接砍半,和缓存叠加起来就是双重省钱。
坑六:只调 prompt 不看 usage。 有人改了 prompt 顺序,就默认"应该命中了吧",结果跑了三个月也不知道命中率是多少。没有 usage 字段做验证的优化,都是自我感动。上线前跑十次看命中率,上线后定期抽查,才谈得上真正在省钱。省钱这件事,最怕的就是"感觉省了"。
九、上线前五分钟自查清单
这套东西不需要什么高级工程能力,上线前花五分钟过一遍就行:
- 打开你 Agent 的代码,找到 messages 的组装逻辑,问一句:system 的第一个 token 每次一样吗? 如果开头拼了时间、随机数、用户名,把它挪到 user 消息末尾。
- 工具定义(JSON Schema)是不是最近动过?动一次就清空一次缓存,改动前想清楚,一次改到位。
- 多轮对话的代码里,有没有对历史做总结、截断、重排?有的话,衡量一下省的那点 token 和丢掉的缓存哪个更值。
- 跑十次真实请求,打印
usage里的prompt_cache_hit_tokens,算命中率。低于 90% 就是前缀有问题,回到第 1 步。 - 看你的定时任务排期:重活有没有落在北京时间的 09:00–12:00 或 14:00–18:00?挪到晚上,成本直接减半。
这五条里,第四条是"仪表盘",前面三条是"发动机"。没有仪表盘,你永远不知道自己在裸奔。
结语
Prompt 缓存是 AI Agent 时代最被低估的一项省钱技术,因为它不需要你换模型、换框架、写新功能——它只要求你改变一个习惯:把不变的东西放前面,把会变的东西放后面。 就这一条,配合 DeepSeek 默认开启的磁盘缓存,输入成本能差出 30 倍。
对一人公司来说,省下的每一块钱都是利润。你花在调 prompt 顺序上的这五分钟,可能是你今年在基础设施上回报率最高的一笔投入——因为它是一次性的,收益却是持续复利的:只要 Agent 还在跑,每个月的账单都会少一块。
如果你已经在用 DeepSeek 跑生产 Agent,欢迎把"缓存命中前后"的账单截图发到评论区,我们看看谁省得最狠。省下来的钱,比任何融资都实在。毕竟,融资要还,成本降下来却是自己的。
