问答机器人上线一周,token账单吓我一跳:30行缓存省掉94.5%调用,Python实战第8期
前七期下来问答机器人已经是个完整产品了50行检索引擎第2期接大模型做问答第3期健康监控第4期重试降级熔断第5期SSE流式输出第6期多轮记忆第7期令牌桶限流。上周我把它推上线跑了一周账单一出来我人傻了。调用量比我预想的高出一大截。拉日志一看气笑了排前20的问题占了总请求的一大半全是退货运费谁承担优惠券怎么叠加这种固定答案的问题。每个都要调一次大模型花钱等延迟答案还一字不差。这钱花得冤。解决办法就两个字缓存。命中缓存的问题直接返回存好的答案不调大模型。但有个细节用户问法五花八门退货运费谁承担和请问退货运费谁承担是一个问题精确匹配缓存认不出来。所以缓存之前先做一层归一化转小写、去掉标点、去掉多余空格归一化之后再算key。环境很简单Python 3标准库就够hashlib和re不用装任何第三方包。核心代码30行贴出来直接能跑importhashlibimportre calls0# 统计真实调了几次大模型deffake_llm(q):模拟大模型调用实际用的时候换成你的API调用globalcalls calls1return答案:q[:20]defnormalize(q):qq.lower()qre.sub(r[^\w\u4e00-\u9fa5],,q)# 去标点、去空格returnq cache{}defanswer_cached(q):keyhashlib.md5(normalize(q).encode()).hexdigest()ifkeyincache:returncache[key],True# 命中不调模型afake_llm(q)cache[key]areturna,False光写缓存不够得验证它真省钱。我写了个模拟流量20个高频问题每个随机加请问前缀、加问号、“怎么换如何”凑200个请求跑一遍看实际调了几次模型importrandom base[退货运费谁承担,优惠券怎么叠加使用,发票在哪里开,会员积分什么时候到账,支持哪些支付方式]*4random.seed(7)queries[]for_inrange(200):qrandom.choice(base)vrandom.random()ifv0.3:qqelifv0.5:q请问qelifv0.65:qq.replace(怎么,如何)queries.append(q)hitssum(1forqinqueriesifanswer_cached(q)[1])print(总请求:,len(queries))print(实际调大模型:,calls)print(缓存命中:,hits)我机器上真实跑出来的输出总请求: 200实际调大模型: 11缓存命中: 189200个请求只调了11次模型省掉94.5%的调用。注意这是模拟流量的结果你的业务里重复率没这么高但只要有三成重复问题缓存就值回票价。三个坑都是我真实踩过的。第一个坑缓存key千万别直接用问题原文当key。大小写、标点、全半角任何一个字符不同就是两次调用缓存形同虚设。归一化这一步不能省。第二个坑不是所有答案都适合缓存。价格、政策、库存这种会变的东西缓存久了就是在发过期答案。我的做法是问题分级高频且答案稳定的进缓存时效性强的走实时调用。简单起见也可以给缓存加TTL存的时候记个过期时间取的时候检查一下importtime store{}defcache_set(key,value,ttl3600):store[key](value,time.time()ttl)defcache_get(key):ifkeyinstore:value,expirestore[key]iftime.time()expire:returnvalue,Truedelstore[key]# 过期了删掉returnNone,FalseTTL设多长看业务客服话术这种一天变不了几次设24小时价格库存类设5分钟。别拍脑袋翻一下这类数据的历史变更频率再定。第三个坑跟第6期的多轮记忆有关。带上下文的问题不能直接缓存它多少钱里的它指什么脱离上下文根本不知道。正确做法是把上下文拼进key归一化(历史问题当前问题)再算key。key拼错了缓存会一本正经地返回牛头不对马嘴的答案。收藏这篇下次你的问答服务账单爆炸直接照着这30行加缓存半小时回本。下期预告第9期讲机器人上线后的可观测性——调用量、延迟、缓存命中率三个指标怎么埋点用50行代码搭个极简监控面板。这个系列在专栏《Python实战》里连着更新从检索引擎一路写到上线运维关注后专栏里能连着看。讨论你线上服务的重复请求占比大概有多少缓存key你是怎么拼的踩过什么坑—— Cyrus