上下文工程这个系列写到今天,该收尾了。前几篇我一直在算账:KV Cache 那篇算显存账,提示缓存+压缩那篇算钱账,Lost in the Middle 那篇算准确率账。三本账算完,我却发现账是分开记的——缓存开关在配置里,压缩阈值在代码深处,检索条数在另一个函数里。真被问到“一次请求到底花了多少钱、哪些 token 本可以不花”,我答不上来。
所以这篇我干的事很直接:把三本账合成一张“上下文预算表”,给每次请求做预算。先把要装进窗口的内容拆成几块,标清每块多少 token、能不能缓存、该不该压缩、要不要检索;请求前估算,请求后拿官方返回的 usage 对账。下面把表的结构、算账代码、对账方法一次给全,读完能直接照抄一份。
一、预算第一步:把一次请求拆成四块
先上结论:别一上来就想着“省 token”,先搞清楚这次的 token 都花在了哪些内容上。我每次请求的 messages 固定拆成四块:
| 内容块 | 我这边典型量级 | 变不变 | 默认动作 | 放哪 |
|---|---|---|---|---|
| 固定前缀(系统提示+工具定义) | 2000 token 左右 | 基本不变 | 原样保留,吃前缀缓存 | 消息最前 |
| 知识材料(检索文档) | 3~5 条共 3000 上下 | 每次不同 | 重排精减,smart_order 摆位 | 中间 |
| 对话历史 | 1000 起步,随轮次涨 | 一直涨 | 截断+总结压缩 | 中间靠后 |
| 本次问题 | 200 左右 | 每次不同 | 不处理,交给路由 | 消息最后 |
量级是我自己的典型值,关键是分类:分清哪些内容几乎不变、哪些每次都在变,后面的决策都围绕它展开。
二、先量后省:token 怎么数、钱怎么算
结论:不要凭感觉估 token,把“测量”做成流水线的一部分。每次响应的 usage 里就带着这些数:DeepSeek 官方文档定义 prompt_tokens = prompt_cache_hit_tokens + prompt_cache_miss_tokens,另有 completion_tokens、total_tokens。我测单块 token 的办法:先发只带固定前缀的请求,再把内容块一块块加回去,两次的 prompt_tokens 相减,就是新增那块的大小。
算钱公式就一行:费用 = 命中token×命中单价 + 未命中token×未命中单价 + 输出token×输出单价,单价以官方定价页为准。按 2026-09 定价页 deepseek-v4-flash 的 off-peak 价:每百万输入 token,缓存命中 0.007 美元、未命中 0.22 美元,输出 0.66 美元;工作日部分时段是 peak,价格翻倍,其余时段半价。
拿上面的量级算:输入共 6200 token,输出按 500。第一次请求缓存还没建起来,6200 全算未命中:6200×0.22/100万=0.001364 美元,加输出 500×0.66/100万=0.00033 美元,合计约 0.001694 美元。第二次起固定前缀 2000 token 命中:2000×0.007/100万=0.000014 美元,剩下 4200 未命中约 0.000924 美元,加输出合计约 0.001268 美元——单次省约 25%。这套逻辑我写成了个小类,本地跑过,输出与上面一致:
from dataclasses import dataclass
@dataclass
class Block:
name: str
tokens: int
fixed: bool # True=属于固定前缀,可命中缓存
class ContextBudget:
def __init__(self, hit=0.007, miss=0.22, out=0.66):
self.hit, self.miss, self.out = hit, miss, out # 美元/百万token
def estimate(self, blocks, output_tokens=500):
fixed = sum(b.tokens for b in blocks if b.fixed)
dynamic = sum(b.tokens for b in blocks if not b.fixed)
def usd(hit_tok, miss_tok):
return (hit_tok * self.hit + miss_tok * self.miss
+ output_tokens * self.out) / 1_000_000
first = usd(0, fixed + dynamic) # 首次:缓存未建立,全 miss
steady = usd(fixed, dynamic) # 之后:固定前缀命中
return first, steady
blocks = [Block("system+tools", 2000, True),
Block("history", 1000, False),
Block("retrieved_docs", 3000, False),
Block("question", 200, False)]
first, steady = ContextBudget().estimate(blocks)
print(f"首次请求 {first:.6f} 美元,之后每次 {steady:.6f} 美元,省 {(first-steady)/first*100:.1f}%")
三、预算表四块怎么填:缓存、精减、压缩、路由
结论:预算表不只是记账工具,每块内容都要有一个默认动作。我的顺序:固定前缀→缓存;知识材料→检索精减;对话历史→压缩截断;本次问题→路由分流。
1. 固定前缀:让它稳定,缓存才认你
DeepSeek 的上下文缓存默认对所有用户开启,不用改代码——但它是前缀缓存,命中规则很严格:后续请求必须完整匹配某个已持久化的缓存前缀单元,中间多一条消息、改一个字,整条前缀就断了。官方文档的例子:两个请求结尾不同就都 miss,公共前缀被识别持久化后,第三个请求才命中。所以我的铁律:固定内容永远放最前面且顺序不变,检索结果、本轮问题这类动态内容一律往后放,绝不插进固定前缀中间。
缓存也不是立刻生效:前缀持久化要几秒,我基本按“首次请求 miss、之后才命中”来估;缓存几小时到几天不用会被自动清掉,长尾低频场景别指望它。值不值:off-peak 下每百万 token 命中 0.007 美元、未命中 0.22 美元,差 30 倍出头。上面例子只靠 2000 token 固定前缀就省 25%,固定资料越多的应用省得越多。
2. 知识材料:检索精减,别全文硬塞
这块在 Lost in the Middle 那篇讲过:检索只留 3~5 条、重排、smart_order 把关键文档钉头尾。它是准确率账,更是钱账——少塞一万 token 就少花一万 token 的钱,答案还更准。细节见上篇,链接在文末。
3. 对话历史:压缩优先于截断
长会话直接砍历史会丢信息。我的做法沿提示缓存那篇:早几轮先总结成固定格式摘要放历史开头(内容不变,能跟着固定前缀吃缓存),最近 N 轮原样保留;还不够就上 LLMLingua 这类 prompt 压缩。
4. 本次问题:省不了,但能决定带不带前面三块
问题本身没法压缩,但能决定其他三块要不要出场:简单问答直接走 short 路径,不挂文档也不带历史——整个输入都省了,这笔省得最狠。路由规则上篇给过,直接复用。
四、对账:钱花完要看得见
结论:预算表要闭环,最后一步是把每次请求的真实花费记下来。usage 字段是官方返回的,我写了个小函数直接落日志:
def log_usage(resp):
u = resp["usage"]
return {
"prompt_tokens": u["prompt_tokens"],
"cache_hit": u.get("prompt_cache_hit_tokens", 0),
"cache_miss": u.get("prompt_cache_miss_tokens", 0),
"completion_tokens": u["completion_tokens"],
}
每周汇总只看三个数:命中率(hit/(hit+miss))、单请求平均输入 token、费用趋势。命中率长期上不去,八成是固定内容没放稳或被动态内容打断;输入 token 涨得快,基本是历史块失控。我给自己定的线:命中率 50% 以上、周费用环比不涨,超了就当周排查。
总结
一句话:上下文预算不是“少给模型看”,而是让每块内容都知道自己为什么在这里、值多少钱——先分类(固定/材料/历史/问题),再测量(usage 对账),后决策(缓存/精减/压缩/路由),最后复盘(命中率、费用趋势)。上下文工程的三本账——显存、钱、准确率——到这篇算是合成一本了。
下期预告:上下文工程这条线到这篇就收官了。账算完,我发现还有一类 token 没算进去——图片。DeepSeek 已上线视觉模型 deepseek-v4-flash-vision-exp,图片按像素折算 token 计费,和文本不是一套账,一张截图可能比几千字还贵。下个系列写多模态:视觉模型怎么选、一张图折算多少 token、图片能不能进 RAG。想一起折腾的,下篇见。
参考文献
- DeepSeek API Docs:Context Caching(前缀缓存的命中与持久化规则):https://api-docs.deepseek.com/guides/kv_cache
- DeepSeek API Docs:Models & Pricing(deepseek-v4-flash 定价与峰谷时段):https://api-docs.deepseek.com/quick_start/pricing
- DeepSeek API Docs:Create Chat Completion(usage 字段定义):https://api-docs.deepseek.com/api/create-chat-completion
- Anthropic:Effective context engineering for AI agents(attention budget 与 context rot):https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
- 上一篇:Lost in the Middle:为什么上下文越长,模型越容易“迷路”?我的三层解法(位置管理+检索精减+路由分流):https://www.fedte.cc/p/837.html