Claude Prompt-Caching 方案调研
1. Prompt-Caching 核心原理
在模型推理领域中,Prompt-Caching/Context-Caching/Prefix-Cache 等等概念,其背后的核心思想都类似,就是根据指定的 Prompt Prefix 来构建缓存,这样在下次接收到相同前缀的请求时,可以快速返回响应,减少模型编解码、推理和网络传输等成本。
主流的大模型如 Gemini、Claude 和 Moonshot 等,和推理框架 vLLM 等,都支持该功能。
下面以 Claude 为例,介绍下 Prompt-Caching 的核心原理。
1.1 缓存机制
技术原理
- Cache Key:对 Prompt 中指定的 Prefix 做 SHA-256 Hash,作为 Cache Key。
- Cache Hit:若存在匹配缓存,直接读取缓存内容(cache_read_input_tokens)。
- Cache Creation:针对首次请求,或 Cache Miss 的场景,则需要根据指定的 Prompt Prefix 构建缓存。(cache_creation_input_tokens)。
- Hit Ratio 监控:
- 如果是原生的 Claude API,可以根据 response 中的 cache_read_input_tokens 字段,监控缓存命中率。
- 如果使用的是类似 OpenRouter 的三方代理,可能 response 中不会返回该字段,需要确认下 Dashboard 中是否可以监控缓存命中率。
缓存有效期
5分钟。该 TTL 为固定值,不支持自定义设置。
可缓存项
- System Prompt
- 对话历史
- 图片
- 视频
- Tool-Calling 定义
使用限制
- 最小长度:不同模型要求的最小缓存长度不同,Claude 3.7 Sonnet 为 1024 tokens
- 并发限制:Cache Item 需等待首次响应生成后才能被其他请求使用。
1.2 适用场景
- 多轮长文本对话:针对我们的场景,需要 System Prompt 非常长的角色才能生效。
- 代码助手
- 固定知识库问答
1.3 核心优势
成本
- 如果命中缓存,input token 的成本会减少 90%。
- 需注意:缓存的首次构建和更新的写入成本会增加 125%。
性能
如果命中缓存,预期可以一定程度上提升响应性能,但可能不会太明显,因为聊天的主要耗时还是在模型推理上。
2. 实战
我们实现一个简单的红楼梦问答小助手,基于《红楼梦》前2回的内容进行提问。因为文本长度超过了 1024 个 token,因此可以开启缓存功能。
代码如下:
import json
import os
import timeimport dotenv
import requests# 红楼梦前两回内容
HONG_LOU_MENG = """
以下是四大名著之一——《红楼梦》的第一回和第二回内容。
第一回 甄士隐梦幻识通灵 贾雨村风尘怀闺秀(清)曹雪芹,(清)高鹗著2019-10-10
远古时候,女娲娘娘采来三万六千五百零一块顽石用来炼石补天。所有的石头都用上了,最后还剩下一块,女娲娘娘把这块石头扔在大荒山青埂峰下。这块石头觉得自己没能派上用场,因此在山下长吁短叹,觉得自己没有用武之地。这天,有个和尚和道士来到青埂峰下,坐在石头边上高谈阔论。石头听见和尚说起世间繁华种种,于是就跟和尚说:“大师若能携带弟子得入红尘,在那富贵场中、温柔乡里受享几年,自当感恩不尽!”和尚同意了,于是念咒书符,大展幻术,将一块大石登时变成一块鲜明莹洁的美玉,又刻上些字。石头想要追问究竟,和尚却只笑不说。就这样,和尚把石头放在袖中,与道士飘然离去。又不知过了多少万年,有个空空道人路过大荒山无稽崖青埂峰下,见到一块巨石巍然耸立,上面刻着许多字,原来上面刻的是石头被和尚携入红尘,投胎人世间的一番经历。上面什么事情都有,只是不知道发生在什么朝代年月,后面还有一首诗:无材可去补苍天,枉入红尘若许年。此系身前身后事,倩谁记去作奇传?空空道人看了就将石头上的文字抄下来,定名为《石头记》。他因受了故事的影响,就改名为情僧,把《石头记》改为《情僧录》。东鲁孔梅溪则题名为《风月宝鉴》。后来,曹雪芹在悼红轩中,披阅十载,增删五次,编纂成目录,分出章回,又题名为《金陵十二钗》,并题一首绝句:满纸荒唐言,一把辛酸泪。都云作者痴,谁解其中味!那块奇石上记录的故事是这样的:姑苏城的阊门,是人间一处繁华热闹的地方。阊门外有个仁清巷,巷里有座葫芦庙,庙旁住着一家人,家主人姓甄,名费,字士隐,娶妻封氏,性情贤淑。甄家虽不是豪富之家,在这一带也是数得上名字的。老夫妻只一个女儿,名叫英莲,年方三岁。盛夏的一天,甄士隐在书房读书读累了,迷迷糊糊地打瞌睡,梦见有个和尚与道士说话。原来警幻仙子那里有株仙草感念赤霞宫神瑛侍者用甘露浇灌,因此化为女儿体,趁神瑛侍者下凡,也要跟到凡间去,并常说:“如果我下世为人,要用一生的眼泪来报答他。”为此事,勾引出许多风流冤家陪他们去了结此案。甄士隐闻听和尚与道士携带个蠢物要去警幻仙子宫中交割清楚,不由得心里好奇,于是要求看看。二仙拗不过他,和尚就递过一块晶莹的美玉给他瞧瞧。美玉正面刻着“通灵宝玉”四个字,背面还刻着几行小字,正欲细看时,那和尚便说已到幻境,强从手中夺了去,与道士过了一大石牌坊,上书四个大字,乃是“太虚幻境”。两旁是一副对联:假作真时真亦假,无为有处有还无。甄士隐想跟进去,刚一抬脚,忽听山崩地裂般一声响,忽然惊醒,原来是梦,梦中的事已忘了大半。这时乳母抱着英莲走来,士隐伸手接过来,抱到门口看热闹。这时,街上过来一个和尚、一个道士,蓬着头,赤着脚,疯疯癫癫的。和尚忽然大哭起来,说他女儿有命无运,道士也插话说:“舍给我吧。”士隐不愿理会他们,转身要走,和尚大笑着念了四句诗:惯养娇生笑你痴,菱花空对雪澌澌。好防佳节元宵后,便是烟消火灭时。士隐心中一动,正想问他们来历,二人已不见了踪影。且说葫芦庙里寄住的一个穷儒,姓贾名化,字时飞,别号雨村,湖州人士,出身诗书官宦人家。这人家境落败,心中颇有一些锦绣文章。因此孤身一人暂居于此,和甄士隐很是相投。士隐看到他,便招呼雨村来叙谈。没说两句严老爷来访,于是士隐且去相迎,没想到这却引出一段姻缘。原来雨村在士隐处翻弄书籍解闷,忽听得窗外有女子嗽声,雨村遂起身往窗外一看,是个仪容不俗的丫鬟在那里撷花。雨村不觉看呆了。那甄家丫鬟撷了花,猛抬头见窗内有人,敝巾旧服,虽是贫窘,却腰圆背厚,面阔口方,更兼剑眉星眼,直鼻权腮,心道:这就是我家主人看重的贾雨村了!如此想来,不免又回头两次。雨村见丫鬟回了头,自为这女子心中有意于他,狂喜不尽:此女子必是个巨眼英雄,风尘中之知己也。后来小童进来,雨村打听得前面留饭,不可久待,遂从夹道中自便出门去了。回到葫芦庙后,雨村因此对丫鬟念念不忘。这年中秋节,士隐在书房备了一席酒,两人相谈甚欢,酒过半酣,雨村做了一首诗:时逢三五便团圆,满把晴光护玉栏。天上一轮才捧出,人间万姓仰头看。士隐说此诗可见得雨村有飞腾之兆,端得文采非凡。雨村又说起自己其实颇有文采,奈何时运不济,欲要赶考却没有盘缠。士隐当即命小童封五十两银子,取两套棉衣,资助他进京赴试。光阴如梭,转眼又是元宵节。晚上,士隐让家人霍启抱英莲去看花灯,岂料英莲被拐子拐走了。老夫妻几乎哭死,相继患病,卧床不起。可谓“屋漏偏逢连夜雨”,三月十五日,葫芦庙的和尚炸祭神的供品不小心泼了油锅,将一条街烧得如火焰山一般,连累得甄家也烧成一堆碎砖烂瓦。甄士隐夫妇家道渐渐中落,只得投奔岳父封肃家去。封肃看士隐越来越穷,逐渐没有好言语。几年下来,好端端一个读书人就贫病交加,渐渐地,竟露出不久于世的样子。这天,他拄着拐杖到街上散心,忽见一个跛道人,脚蹬烂草鞋,身穿破道袍,如疯如狂地唱着:世人都晓神仙好,惟有功名忘不了!古今将相在何方?荒冢一堆草没了。世人都晓神仙好,只有金银忘不了!终朝只恨聚无多,及到多时眼闭了。世人都晓神仙好,只有娇妻忘不了!君生日日说恩情,君死又随人去了。世人都晓神仙好,只有儿孙忘不了!痴心父母古来多,孝顺儿孙谁见了?士隐心中一动,迎上去问:“你满口说些什么?只听见些‘好’‘了’‘好’‘了’。”道人笑着说:“我这歌儿就叫《好了歌》。你且解解看。”士隐已大彻大悟,应声而解说:陋室空堂,当年笏满床;衰草枯杨,曾为歌舞场。蛛丝儿结满雕梁,绿纱今又糊在蓬窗上。说什么脂正浓、粉正香,如何两鬓又成霜?昨日黄土陇头送白骨,今宵红灯帐底卧鸳鸯。金满箱,银满箱,展眼乞丐人皆谤。正叹他人命不长,哪知自己归来丧!训有方,保不定日后作强梁。择膏粱,谁承望流落在烟花巷!因嫌纱帽小,致使锁枷扛;昨怜破袄寒,今嫌紫蟒长:乱烘烘你方唱罢我登场,反认他乡是故乡。甚荒唐,到头来都是为他人作嫁衣裳!道人拍掌大笑,说:“解得贴切!”士隐当下说声“走吧”,也不回家,与道人飘然而去。第二回 贾夫人仙逝扬州城 冷子兴演说荣国府(清)曹雪芹,(清)高鹗著2019-10-10
一日,许多公差来封家门前叫门,封肃急忙出来问:“各位大爷有什么事?”没想到公人不由分说,竟推拥他走了。封家人个个惊恐,不知是何凶兆。那天约二更时分,封肃方回来,欢天喜地地说:“你们道是何原因?原来本府新升的太爷姓贾名化,此乃女婿旧日相交。方才他叫我说了一回话,临走还送了我二两银子。”甄家娘子听了,想到士隐,不免心中伤感。次日一早,雨村遣人送两封银子、四匹锦缎过来,答谢甄家娘子;又寄一封密函与封肃,原来是托他向甄家娘子要以前看上的丫鬟娇杏做二房。封肃巴不得去奉承雨村,便在女儿前说好说歹,一力撺掇成了。乘夜只用一乘小轿,便把娇杏送雨村府里去了。雨村欢喜,自不必说,另封百金赠封肃,外又谢甄家娘子许多物事,令其好生养赡。却说娇杏这丫鬟自到雨村身边,只一年,便生了一子;又半载,雨村嫡妻忽染疾下世,雨村便将她扶侧作正室夫人了。雨村因何如此官运亨达?原来那年士隐赠银之后,他本已升了本府知府,却又被上司弹劾,幸得结识了林如海,便做了他家的西宾。这林如海乃是前科的探花,不但是钟鼎之家,亦是书香之族。今只有嫡妻贾氏,生得一女,乳名黛玉,年方五岁。夫妻无子,故爱黛玉如珍宝一般。雨村设帐西席,只得黛玉一个女学生并两个伴读丫鬟,黛玉年龄小,身体又弱,功课不限多寡,故十分省力。转眼间一年过去了,谁知黛玉之母贾氏夫人因疾而终。黛玉本是孝女,侍汤奉药,守丧尽哀,雨村于是想将馆辞了另谋打算。可林如海意欲令女守制读书,故又将他留下。近日来黛玉哀痛过伤,本就怯弱多病的,不想触犯旧症,遂连日不曾上学。雨村闲居无聊,常常在饭后出来闲步。这日雨村去智通寺游玩,在村肆中遇到旧相识冷子兴,于是就问他道:“近日都中可有新闻没有?”子兴回答道:“倒没有什么新闻,倒是您的同姓宗家出了一件小小的异事。”然后就说起与贾雨村同宗的宁、荣二宅里的一番往事。子兴道:“宁国公与荣国公本是一母同胞弟兄两个。宁公是长子,生了四个儿子。他死后,贾代化袭了官,也养了两个儿子:长子名贾敷,八九岁上便死了,次子贾敬一味好道,只爱烧丹炼汞,其他一概不在心上。贾珍是贾敬早年得的儿子,因此贾敬的官倒让他袭了。贾珍倒生了一个儿子,今年才十六岁,名叫贾蓉。因敬老爹一概不管家事,贾珍一味高乐不已,把宁国府竟翻了过来,也没有人敢来管他。出了异事的不在他家,在荣府。自荣公死后,长子贾代善袭了官,娶金陵世勋史侯家的小姐为妻,生两子:长子贾赦,次子贾政。代善虽身故,太夫人尚在,长子贾赦袭官,次子贾政也已升了员外郎了。奇事就出在政老爹家小公子身上。这小公子一落胎胞,嘴里便衔下一块五彩晶莹的玉来,玉上还有许多字迹,就取名叫作宝玉。等周岁时,政老爹便要他抓周。这衔玉生的孩子伸手只把些脂粉钗环抓来。政老爹便大怒了,说:‘将来酒色之徒耳!’因此便大不喜悦。独那史老太君还是命根一样。现如今长了七八岁,虽然淘气异常,但其聪明乖觉处,百个不及他一个。说起孩子话来也奇怪,他说:‘女儿是水作的骨肉,男人是泥作的骨肉。我见了女儿,我便清爽;见了男子,便觉浊臭逼人。’你道好笑不好笑?将来定色鬼无疑了!”雨村不以为然地回答道:“这却不然。你还不知,我自革职以来,这两年遍游各省,也曾遇见两个异样孩子。所以方才你一说这宝玉,我就猜着了八九亦是这一派人物。无需惊扰,日后自有道理。”子兴又继续言道:“贾府中四个姑娘也不错。政老爹长女名元春,因她贤孝,才德兼备,选入皇宫做女史去了;二小姐是赦老爹姨娘所生,名叫迎春;三小姐是政老爹庶出,名探春;四小姐是宁府珍爷的妹妹,名惜春。因史老太君极爱孙女,都跟着祖母,一处读书。”雨村奇怪地说:“贾府的小姐,取名怎么听上去如此朴素?”子兴说:“还不是因为大小姐是大年初一生的,叫个‘元春’,其余的都跟着叫个‘春’。上一辈的排行也是跟着弟兄走的,就如贵东家林公的夫人,名叫贾敏。”雨村点点头,于是又问:“政公有个衔玉之子,赦公家就没一个?”子兴说:“政公有了玉儿,他的妾又生了一个,还没听说是好是歹。赦公也有二子,长子是二十多岁的贾琏,娶的政老爹夫人王氏的娘家侄女为妻,亲上加亲。这位琏爷的夫人却没有不称赞的,模样儿标致,言谈爽利,心机极深细,竟是一万个男人也抵不上她一个。”两人言笑晏晏,不觉又多吃了几杯酒。正待走时,雨村又听得后面有人叫道:“雨村兄,恭喜了!特来报个喜信的。”雨村忙回头看时,要知是何人,且听下回分解。"""# 加载环境变量
dotenv.load_dotenv()def send_msg(msg: str) -> str:"""发送消息"""resp = requests.post(url=os.getenv("OPEN_ROUTER_API_BASE"),headers={"Authorization": f"Bearer {os.getenv("OPEN_ROUTER_API_KEY")}","Content-Type": "application/json",},data=json.dumps({"model": "anthropic/claude-3.7-sonnet","messages": [{"role": "system","content": [{"type": "text","text": "你是一个智能助理,善于回答用户的各种问题。",},]},{"role": "system","content": [{"type": "text","text": HONG_LOU_MENG,"cache_control": {"type": "ephemeral"}, # 设置缓存},]},{"role": "user","content": [{"type": "text","text": msg,},]}],}))# 返回AI回复return resp.json()["choices"][0]["message"]["content"]if __name__ == '__main__':start_time = time.time()reply = send_msg("《红楼梦》第一回的主要内容是什么?")print(f"第1次回复: {reply} \n 耗时: {time.time() - start_time:.2f}s")start_time = time.time()reply = send_msg("《红楼梦》第二回的主要内容是什么?")print(f"第2次回复: {reply} \n 耗时: {time.time() - start_time:.2f}s")
最终结果如下:
3. 参考资料
3.1 Claude Prompt-Caching 官方文档
Prompt caching - Anthropic
3.2 OpenRouter FAQ
Yes, OpenRouter supports Claude's prompt caching feature through the cache_control
parameter in API requests. This allows you to cache parts of the input prompt to reduce latency and costs when reusing the same context. Here's what you need to know:
Key Details:
- Supported Models:Prompt caching is available for Claude 3.x models (e.g.,
claude-3-sonnet
,claude-3-opus
) via OpenRouter, as this feature was introduced in Anthropic's Claude 3 series.
- How to Use It:Include the
cache_control
parameter in your API request to specify which parts of the prompt should be cached. For example:
-
- Use
"type": "ephemeral"
to mark content for caching. Subsequent requests reusing this cached content will be faster and cheaper.
- Use
- OpenRouter Integration:OpenRouter passes the
cache_control
parameter directly to Anthropic's API, so caching works as described in Anthropic's documentation.
- Pricing:Cached input tokens are billed at a lower rate (e.g., **$0.0008/1K tokens** for cached input vs. $0.003/1K for uncached input with Claude 3 Sonnet via OpenRouter). Check OpenRouter's pricing page for exact rates.
- Limitations:
- Caching is ephemeral and scoped to a single API request. It does not persist across sessions.
- Ensure your prompt structure explicitly marks cacheable sections with
cache_control
.
Example Use Case:
Notes:
- Always verify the latest feature support in OpenRouter's documentation.
- Prompt caching is ideal for scenarios like multi-turn conversations or repeated analysis of static documents.
Let me know if you need further clarification!
3.3 Moonshot Context-Caching
https://platform.moonshot.cn/docs/api/caching