- Prompt-Caching 核心原理 ======================
1.1 缓存机制
技术原理
- Cache Key:对 Prompt 中指定的 Prefix 做 SHA-256 Hash,作为 Cache Key。
-
Cache Hit :若存在匹配缓存,直接读取缓存内容。
-
Cache Creation:针对首次请求,或 Cache Miss 的场景,则需要根据指定的 Prompt Prefix 构建缓存。
-
Hit Ratio 监控:如果是原生的 Claude API,可以根据 response 中的
cache\_read\_input\_tokens
字段,监控缓存命中率。
缓存有效期
缓存有效期 TTL 为5分钟。 该 TTL 为固定值,不支持自定义设置。
可缓存项
-
System Prompt
-
对话历史
-
图片
-
视频
-
Tool-Calling 定义
使用限制
1. 最小长度:不同模型要求的最小缓存长度不同,Claude 3.7 Sonnet 为 1024 tokens。
2. 并发限制:Cache Item 需等待首次响应生成后才能被其他请求使用。
1.2 适用场景
1. 多轮长文本对话
2. 代码助手
3. 固定知识库问答
1.3 核心优势
1. 成本
- 如果命中缓存,input token 的成本会减少 90%。
- 需注意:缓存的首次构建和更新的写入成本会增加 125%。
- 性能
延迟降低:如果命中缓存,预期可以一定程度上提升响应性能。
- 实战 =====
以我们实际聊天的 System Prompt 为例,我选择了一个背景介绍非常长的角色,最终构造好的 System Prompt 长度可以超过 1024 token。
Python 版本的代码如下:
import json
import os
import time
import dotenv
import requests
# 红楼梦第一回内容
HONG_LOU_MENG_CHAR_1 = """
以下是四大名著之一——《红楼梦》的第一回和第二回内容。
第一回 甄士隐梦幻识通灵 贾雨村风尘怀闺秀(清)曹雪芹,(清)高鹗著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_CHAR_1,
"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")
最终结果如下:
-
注意事项 =======
-
实际使用时,需要对缓存命中率进行有效监控,可以通过 response 中的
cache\_read\_input\_tokens
字段来查看缓存 token 的使用情况。 ======================================================================================= -
要特别关注下 token 费用消耗,因为缓存的创建和更新的成本会高于正常情况。 =======================================