在教程里,AI调用总是成功的。导入SDK,粘贴API密钥,等待一次补全,渲染结果,然后发布。
在生产环境里,同一行代码是对别人基础设施的网络调用,运行在别人的容量规划下,受别人的事故响应影响。有时它很慢,有时它对你限流,有时它干脆挂了。
![]()
问题不在于你的AI供应商会不会宕机。问题在于宕机发生时,你的用户看到的是什么。如果答案是“卡死的屏幕”或者“出错了”,那问题不在供应商,在架构。
失败模式一:整个页面都在等模型
示例场景:一个项目管理应用在每张工单顶部加了AI生成的摘要。摘要在页面渲染前由服务端拉取。某天下午供应商延迟从2秒跳到40秒。于是每张工单页面都要加载40秒——包括那些从不看摘要的用户。
AI部分本来是可选的,是架构把它变成了必选项。
修复方式:永远不要把模型调用放在关键渲染路径上。先加载核心页面,异步获取AI输出,并给它一个硬性超时。如果超时没返回,那个位置就留空,或者安静地显示“摘要暂不可用”。工单照样能打开。
失败模式二:一家供应商,硬编码
如果你的代码在二十个地方直接调用同一家供应商的SDK,你就有二十个单点故障,而且没有开关可以切换。
解法是在应用和供应商之间放一层薄路由,把降级做成一等公民,而不是try/catch的事后补丁:
const chain = [{ name: "primary", call: callPrimaryModel, timeoutMs: 8000 },{ name: "secondary", call: callSecondaryProvider, timeoutMs: 8000 },{ name: "local", call: callLocalModel, timeoutMs: 4000 },async function complete(task: Task): Promise {for (const step of chain) {if (breaker.isOpen(step.name)) continue;try {const out = await withTimeout(step.call(task), step.timeoutMs);breaker.recordSuccess(step.name);return { ...out, servedBy: step.name };} catch (err) {breaker.recordFailure(step.name);return null; // caller must handle "no AI available"}两个细节比循环本身更重要。熔断器能阻止你反复捶打一个明显已经挂掉的供应商,避免每个请求都付满超时的代价。而null是一个合法的返回值,它强迫每个调用方去决定:没有AI的时候,这里该怎么办。
失败模式三:降级模型干同样的活
通过Ollama或llama.cpp跑的本地小模型是很好的最后一道防线,但它不是前沿模型的开箱替代品。为前者调好的提示词,放到后者身上经常产出垃圾。
需要提前决定哪些任务足够重要、值得降级到本地:分类、短提取、简单改写。给这些任务单独准备提示词并测试。其余任务应该降级为“暂时不可用”,而不是降级为一个自信但更差的答案。
失败模式四:核心功能依赖AI路径
这一条会把供应商的故障变成你自己的故障。只靠向量检索才能用的搜索。AI校验器不响应就提交不了的表单。因为欢迎语要生成而卡住的新手引导流程。
每个AI功能都应该有一条非AI路径,让用户仍然能完成他的事:语义搜索背后留一个关键词搜索,AI校验背后留一个规则校验,生成式模板背后留一个静态模板。
韧性是第一天就要做的决定
优雅降级其实就是把AI供应商当成任何一个外部依赖来对待——超时、降级、熔断,以及对“它没了怎么办”有一个明确答案。做得吃力的团队,都是把模型当成了自己代码的一部分,而不是一项租来的服务。
事后补这套东西,意味着要动每一个调用点。一开始就设计进去,只需要一层路由和几个决定。
你的应用今天在AI供应商宕机时,实际会做什么?你测过,还是在假设?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.