AI产品灰度发布实战:从“全量硬推”到“5%灰度”

模型迭代了,不敢全量推。万一新模型效果回退,所有用户都会受影响。灰度发布不是大厂专利,个人项目也能做。

背景

灵析的LSTM模型从V1.0迭代到V2.0,离线测试RMSE下降了31%,但离线测试≠线上效果。如果直接全量替换,万一线上数据分布和测试集不一样,所有用户都会看到偏差的分析结果。

坑1:灰度发布不知道怎么配

问题: 新模型不敢全量推,但没有灰度机制,只能二选一——要么全量,要么不回。

解决方案:环境变量控制流量比例

python
import os
import random

NEW_MODEL_RATIO = int(os.getenv("NEW_MODEL_RATIO", 5))  # 默认5%流量走新模型

def get_prediction(data):
    # 根据用户ID或随机数决定走哪套模型
    use_new_model = random.randint(1, 100) <= NEW_MODEL_RATIO
    
    if use_new_model:
        result = new_model.predict(data)
        model_version = "v2.0"
    else:
        result = old_model.predict(data)
        model_version = "v1.0"
    
    return {
        "result": result,
        "model_version": model_version,
        "is_gray": use_new_model
    }

启动时配置:

bash
# 5%流量走新模型
docker run -e NEW_MODEL_RATIO=5 lingxi-backend

# 灰度验证通过后,逐步放量
docker run -e NEW_MODEL_RATIO=20 lingxi-backend
docker run -e NEW_MODEL_RATIO=50 lingxi-backend
docker run -e NEW_MODEL_RATIO=100 lingxi-backend

坑2:合规拦截计数重复

表象: 用户问了一次问题,系统显示拦截了2次。

根本原因: 前端按钮没有防抖,用户快速点击两次,后端收到两个请求,各拦截一次,计数重复。

解决方案:

javascript
// 前端:发送后禁用按钮
function sendQuestion() {
    const btn = document.getElementById('send-btn');
    btn.disabled = true;
    btn.textContent = '发送中...';
    
    api.chat(question)
        .then(response => {
            // 处理响应
        })
        .finally(() => {
            btn.disabled = false;
            btn.textContent = '发送';
        });
}
python
# 后端:分布式防抖(基于用户+问题去重)
from functools import lru_cache
import hashlib

def deduplicate(user_id, question):
    key = hashlib.md5(f"{user_id}:{question}".encode()).hexdigest()
    if key in recent_requests:
        return False  # 重复请求
    recent_requests.add(key)
    return True

坑3:模型回滚接口——解决“怎么回滚”,只有函数没有接口

问题: “模型出问题了怎么回滚”,“执行脚本切换”,“有HTTP接口”

解决方案:加管理接口

python
from fastapi import APIRouter

admin_router = APIRouter(prefix="/api/admin")

@admin_router.post("/model/rollback")
def rollback_model(target_version: str):
    """一键回滚到指定版本"""
    if target_version not in available_versions:
        return {"error": f"版本 {target_version} 不存在"}
    
    # 切换模型
    current_version = switch_model(target_version)
    
    return {
        "status": "success",
        "current_version": current_version,
        "rollback_at": datetime.now().isoformat()
    }

@admin_router.get("/model/versions")
def list_versions():
    """查看所有可用版本"""
    return {
        "available": available_versions,
        "current": current_model_version,
        "metrics": get_model_metrics(current_model_version)
    }

使用方式:

bash
# 查看当前版本
curl /api/admin/model/versions

# 一键回滚
curl -X POST /api/admin/model/rollback?target_version=v1.0
posted @ 2026-06-26 16:42  竹雨禅月  阅读(7)  评论(0)    收藏  举报