主模型一挂全站就瘫?40行写个自动降级,备用模型顶上,Python实战第10期
第9期给机器人装上prompt注入防护之后,我以为能消停一阵。结果上周三半夜,主模型供应商挂了四十分钟,机器人跟着一起瘫。用户问什么都返回500,缓存只能顶住重复问题,新问题一个都答不上来。那一刻我才反应过来:第4期做了熔断,第7期做了限流,第8期做了缓存,全是机器人自己不出问题的前提下才管用。模型供应商一挂,这些全白搭。
问题就一个:单点依赖。机器人从头到尾只认一个模型,这个模型就是整条链上最脆的一环。它挂了,你的代码写得再稳也没用。解决办法也不复杂:再备一个模型,主模型不行就自动切备用,主模型好了再切回来。整个逻辑就三件套:主备两个客户端、一个路由、一个健康探测。
环境还是老样子,Python 3.10以上,标准库就够。下面这段代码直接能跑,MockClient模拟两个模型,主模型前3次调用故意失败,备用模型一直正常:
import time
class ModelDown(Exception):
pass
class MockClient:
def __init__(self, name, fail_times=0, latency=0.05):
self.name, self.fail_times, self.latency = name, fail_times, latency
self.calls = 0
def chat(self, q):
self.calls += 1
time.sleep(self.latency)
if self.calls <= self.fail_times:
raise ModelDown(f"{self.name} 第{self.calls}次调用失败")
return f"[{self.name}] {q} 的答案"
class ModelRouter:
def __init__(self, primary, fallback, max_fail=2):
self.primary, self.fallback = primary, fallback
self.max_fail, self.fail_count = max_fail, 0
self.primary_down = False
def chat(self, q):
if not self.primary_down:
try:
ans = self.primary.chat(q)
self.fail_count = 0
return ans
except ModelDown as e:
self.fail_count += 1
print("主模型异常:", e)
if self.fail_count >= self.max_fail:
self.primary_down = True
print("主模型连续失败,切换到备用模型")
return self.fallback.chat(q)
def probe(self):
# 每隔一段时间探一次主模型,好了就切回来
try:
self.primary.chat("ping")
self.primary_down = False
self.fail_count = 0
print("主模型恢复,切回主模型")
except ModelDown:
print("主模型还没好,继续用备用")
if __name__ == "__main__":
main = MockClient("主模型A", fail_times=3)
back = MockClient("备用模型B")
router = ModelRouter(main, back)
for q in ["退货运费谁承担", "怎么查订单", "能开发票吗", "明天几点开门"]:
print("问:", q, "->", router.chat(q))
print("调用统计:主模型", main.calls, "次,备用模型", back.calls, "次")
router.probe()
print("恢复后:", router.chat("退货要运费吗"))
跑一遍,结果很直观:主模型前两次调用失败,连续失败满2次就切走,后面4个问题全是备用模型答的。调用统计主模型2次、备用模型4次。收尾时再probe一次,主模型第3次调用还是失败,继续用备用,没切回去。这个行为就是想要的:切走要果断,回来要确认。
这里有个数字要讲清楚:连续失败阈值我设的是2次,不是1次。模型接口偶尔抖一下很正常,超时一次就切走,切回来又抖,来回横跳比直接扛着更糟。2到3次是个实在的数:一次是抖动,两次以上大概率是真挂了。切走之后也别再调主模型了,每次请求都先撞一次失败再走备用,等于每次请求白白多一次超时,延迟直接翻倍。
第一个坑:阈值设成1。我第一版就是这么写的,结果主模型一次超时就切备用,备用刚接手两分钟,主模型好了,probe切回来,刚切回来又抖一次,又切走。一晚上来回切了十几回,日志里全是切换记录,用户倒没感觉,账单上两个模型都在计费。改成连续失败2次才切,横跳就没了。
第二个坑:切到备用就忘了回来。很多降级代码只写了切走,没写回来。主模型半夜挂了,早上八点就好了,你的服务还在用备用模型扛,备用一般比主模型贵或者弱,扛一天账单先翻倍。probe就是干这个的:定时发个ping探一次,通了就切回来。探活间隔别太密,5分钟一次够了,探太密等于拿ping去压一个刚恢复的服务。
第三个坑:备用模型能力弱一截,还偷偷降级。备用一般是便宜的小模型,回答质量差一档。用户问个复杂问题,主模型能答好,备用答得磕磕绊绊,用户不会知道你降级了,只会觉得你变笨了。降级的时候打一条日志,顺手记个指标,监控上能看到"当前在备用",出了问题先查这一条。能告诉用户更好,一句"当前使用备用模型,回答可能慢一点"顶十句事后解释。
这篇建议收藏。主模型挂掉不是会不会发生的问题,是什么时候发生的问题。40行路由加两个客户端,半小时就能接进第8期的机器人里。下次半夜供应商挂了,你的机器人还能站着回答问题。
下期是Python实战第11期,讲知识库热更新:不用重启服务就能往机器人里加新文档,顺带把第1期的检索引擎改成能动态加载的版本。这个系列从第1期检索引擎开始,把问答机器人从0搭到上线再到防攻击、做降级,一期接一期,都收在专栏《Python实战》里。关注之后去专栏里能连着看,省得一篇篇翻。
你上次服务挂掉,是模型供应商挂了,还是自己代码出bug?修了多久?
Cyrus
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)