引言

2026年3月15日,OpenAI API 全球性宕机 47 分钟。那些没有降级方案的 Agent 应用全部显示"服务不可用",而准备好容灾方案的产品几乎无感知地度过了这次故障。LLM 是 Agent 的核心,但它不是 100% 可靠的基础设施。本文将系统化构建 Agent 的多级降级体系。

一、故障分类与影响分析

1.1 LLM 服务故障类型

┌──────────────────────────────────────────────────┐
│              LLM 故障分类                         │
├──────────────┬───────────┬───────────────────────┤
│   故障类型    │ 持续时间  │      影响范围          │
├──────────────┼───────────┼───────────────────────┤
│ API 完全宕机  │ 5-60min   │ 所有请求失败           │
│ 限流(429)    │ 1-10min   │ 超额请求失败           │
│ 超时         │ 30-120s   │ 单请求受影响           │
│ 模型退版     │ 永久      │ 特定版本不可用         │
│ 区域故障     │ 5-30min   │ 特定区域不可用         │
│ 质量退化     │ 未知      │ 输出质量下降           │
└──────────────┴───────────┴───────────────────────┘

1.2 故障影响评估

@dataclass
class FaultImpact:
    fault_type: str
    user_visible: bool       # 用户是否感知
    data_loss: bool          # 是否丢数据
    recovery_time: str       # 恢复时间
    business_impact: str     # 低/中/高/严重

LLM_FAULT_IMPACTS = [
    FaultImpact("API宕机", True, False, "5-60min", "严重"),
    FaultImpact("限流", True, False, "1-10min", "中"),
    FaultImpact("超时", True, False, "30-120s", "低"),
    FaultImpact("质量退化", False, False, "未知", "中"),
]

二、多级降级架构

┌──────────────────────────────────────────────────────┐
│                   请求入口                            │
│                 ┌──────┴──────┐                      │
│                 │  请求路由    │                      │
│                 └──────┬──────┘                      │
│                        │                             │
│  ┌─────────────────────▼──────────────────────┐     │
│  │          Level 0: 主模型 (GPT-5)           │     │
│  │          延迟 < 2s, 成功率 99.5%           │     │
│  └────────────────┬───────────────────────────┘     │
│            失败 ↓                                   │
│  ┌─────────────────▼──────────────────────────┐     │
│  │       Level 1: 备用模型 (Claude Opus)      │     │
│  │          延迟 < 3s, 成功率 99.5%           │     │
│  └────────────────┬───────────────────────────┘     │
│            失败 ↓                                   │
│  ┌─────────────────▼──────────────────────────┐     │
│  │      Level 2: 降级模型 (GPT-5-mini)        │     │
│  │         延迟 < 1s, 功能受限                │     │
│  └────────────────┬───────────────────────────┘     │
│            失败 ↓                                   │
│  ┌─────────────────▼──────────────────────────┐     │
│  │      Level 3: 缓存/预计算结果              │     │
│  │         精度下降,但可用                   │     │
│  └────────────────┬───────────────────────────┘     │
│            失败 ↓                                   │
│  ┌─────────────────▼──────────────────────────┐     │
│  │      Level 4: 规则引擎/模板回复             │     │
│  │         基本功能,无AI能力                  │     │
│  └────────────────┬───────────────────────────┘     │
│            失败 ↓                                   │
│  ┌─────────────────▼──────────────────────────┐     │
│  │      Level 5: 优雅错误页面                  │     │
│  │         引导用户重试或联系人工              │     │
│  └────────────────────────────────────────────┘     │

三、各级降级实现

Level 0-1:模型切换

from enum import Enum
import asyncio

class ModelTier(Enum):
    PRIMARY = "gpt-5"
    FALLBACK = "claude-opus-4-2026"
    DEGRADED = "gpt-5-mini"
    EMERGENCY = "gpt-4o-mini"

class LLMFailoverChain:
    """LLM 多级故障转移链"""
    
    def __init__(self):
        self.chain = [
            {"model": "gpt-5", "timeout": 30, "retries": 2},
            {"model": "claude-opus-4-2026", "timeout": 45, "retries": 1},
            {"model": "gpt-5-mini", "timeout": 15, "retries": 2},
            {"model": "gpt-4o-mini", "timeout": 10, "retries": 3},
        ]
        self.circuit_breakers = {m["model"]: CircuitBreaker() for m in self.chain}
        self.health_checker = HealthChecker()
    
    async def invoke(self, messages: list, **kwargs) -> str:
        errors = []
        
        for tier in self.chain:
            model = tier["model"]
            cb = self.circuit_breakers[model]
            
            # 检查熔断器状态
            if not cb.can_call():
                errors.append(f"{model}: circuit breaker open")
                continue
            
            # 检查健康状态
            if not await self.health_checker.is_healthy(model):
                errors.append(f"{model}: unhealthy")
                continue
            
            try:
                response = await self._call_with_retry(
                    model, messages, 
                    timeout=tier["timeout"],
                    retries=tier["retries"],
                    **kwargs
                )
                cb.record_success()
                
                # 如果降级了,通知调用方
                if model != self.chain[0]["model"]:
                    logger.warning(f"Degraded to {model}")
                
                return response
                
            except (TimeoutError, RateLimitError) as e:
                cb.record_failure()
                errors.append(f"{model}: {e}")
                continue
            except Exception as e:
                cb.record_failure()
                errors.append(f"{model}: {e}")
                continue
        
        # 所有模型都失败,进入更深层的降级
        raise AllModelsFailedError(errors)

Level 2:功能降级

class DegradedMode:
    """降级模式:功能减少但核心可用"""
    
    DEGRADATION_RULES = {
        "gpt-5-mini": {
            "disable_tools": False,          # 仍可用工具
            "max_iterations": 3,             # 减少迭代次数
            "disable_multi_step": True,      # 禁用多步推理
            "simplified_prompt": True,       # 简化 System Prompt
            "max_context_messages": 5,       # 减少上下文
        },
        "gpt-4o-mini": {
            "disable_tools": True,           # 禁用工具
            "max_iterations": 1,             # 单轮回复
            "disable_multi_step": True,
            "simplified_prompt": True,
            "max_context_messages": 3,
        }
    }
    
    def apply(self, request: dict, model: str) -> dict:
        rules = self.DEGRADATION_RULES.get(model, {})
        degraded_request = request.copy()
        
        if rules.get("simplified_prompt"):
            degraded_request["system"] = self._simplify_prompt(request.get("system", ""))
        
        if rules.get("disable_tools"):
            degraded_request.pop("tools", None)
        
        if rules.get("max_context_messages"):
            messages = degraded_request.get("messages", [])
            degraded_request["messages"] = messages[-rules["max_context_messages"]:]
        
        degraded_request["max_tokens"] = min(
            degraded_request.get("max_tokens", 4096),
            1024  # 降级时限制输出长度
        )
        
        return degraded_request

Level 3:缓存降级

class CacheFallback:
    """缓存降级:使用历史相似查询的结果"""
    
    async def try_cached_response(self, query: str) -> CachedResponse | None:
        # 1. 精确匹配
        exact = await self.cache.get(query)
        if exact:
            return CachedResponse(
                content=exact,
                source="exact_cache",
                warning="This response is from cache due to AI service degradation."
            )
        
        # 2. 语义匹配
        similar = await self.semantic_cache.search(query, threshold=0.85)
        if similar:
            return CachedResponse(
                content=similar.response,
                source="semantic_cache",
                warning="This response is based on a similar historical query."
            )
        
        # 3. 预计算回答(高频问题)
        precomputed = await self.precomputed_db.get(query)
        if precomputed:
            return CachedResponse(
                content=precomputed,
                source="precomputed",
                warning=None  # 预计算结果质量有保证
            )
        
        return None

Level 4:规则引擎降级

class RuleEngineFallback:
    """规则引擎:LLM 不可用时的最终保底"""
    
    def __init__(self):
        self.rules = self._load_rules()
    
    def _load_rules(self) -> list[Rule]:
        """加载预定义规则"""
        return [
            # 意图匹配规则
            Rule(
                pattern=r"(.*)价格(.*)",
                action=lambda m: f"我们的产品价格请参考:{self._get_pricing_table()}",
                intent="pricing_inquiry"
            ),
            Rule(
                pattern=r"(.*)退款(.*)",
                action=lambda m: f"退款申请已记录。您的退款将在3-5个工作日内处理。"
                                 f"工单号:{self._generate_ticket()}",
                intent="refund_request"
            ),
            Rule(
                pattern=r"(hello|hi|你好|嗨)",
                action=lambda m: "您好!我是智能助手。"
                                 "AI服务暂时受限,但我可以处理基础请求。请说明您需要什么帮助。",
                intent="greeting"
            ),
        ]
    
    def handle(self, user_input: str) -> str:
        for rule in self.rules:
            match = re.match(rule.pattern, user_input, re.IGNORECASE)
            if match:
                return rule.action(match)
        
        # 兜底回复
        return (
            "AI 服务暂时不可用,我无法处理您的复杂请求。\n"
            "您可以:\n"
            "1. 稍后重试\n"
            "2. 联系人工客服:400-xxx-xxxx\n"
            "3. 访问帮助中心:https://help.example.com"
        )

四、降级决策器

class DegradationOrchestrator:
    """降级编排器:协调各级降级策略"""
    
    def __init__(self):
        self.llm_chain = LLMFailoverChain()
        self.cache = CacheFallback()
        self.rules = RuleEngineFallback()
        self.degraded_mode = DegradedMode()
        self.health_monitor = HealthMonitor()
    
    async def handle_request(
        self,
        messages: list,
        tools: list | None = None,
        user_id: str = "",
        priority: str = "normal"
    ) -> Response:
        
        # 检查系统健康状态
        health = await self.health_monitor.get_status()
        
        # 根据健康状态选择策略
        if health.llm_available:
            try:
                return await self.llm_chain.invoke(messages, tools=tools)
            except AllModelsFailedError:
                health.llm_available = False
        
        if health.cache_available:
            cached = await self.cache.try_cached_response(
                messages[-1]["content"]
            )
            if cached:
                return Response(
                    content=cached.content,
                    degraded=True,
                    source=cached.source,
                    warning=cached.warning
                )
        
        # 规则引擎兜底
        rule_response = self.rules.handle(messages[-1]["content"])
        return Response(
            content=rule_response,
            degraded=True,
            source="rule_engine",
            warning="AI 服务暂时不可用,正在使用规则引擎处理。"
        )
    
    async def health_check_loop(self):
        """持续健康检查,自动恢复"""
        while True:
            await asyncio.sleep(30)  # 每30秒检查一次
            
            if not self.health_monitor.llm_available:
                # 尝试恢复
                test_result = await self._test_llm_health()
                if test_result:
                    self.health_monitor.llm_available = True
                    logger.info("LLM service recovered!")
                    await self._notify_recovery()

五、降级通知与用户体验

class DegradedUX:
    """降级时的用户体验设计"""
    
    MESSAGES = {
        "model_switch": {
            "inline": "(响应速度可能略有不同)",
            "banner": "⚠️ AI 服务正在切换到备用节点,响应可能略有延迟。"
        },
        "cache_fallback": {
            "inline": "(以下为缓存回复)",
            "banner": "⚠️ AI 服务暂时繁忙,正在使用历史缓存回复您。"
        },
        "rule_engine": {
            "inline": "(基础模式)",
            "banner": "⚠️ AI 服务暂时不可用,正在使用基础模式处理您的请求。"
        },
        "full_failure": {
            "banner": "❌ AI 服务暂时不可用。您可以通过以下方式获取帮助:\n"
                      "📞 客服热线:400-xxx-xxxx\n"
                      "💬 在线客服:点击右下角\n"
                      "📧 邮件:support@example.com\n"
                      "我们正在紧急修复中,请稍后重试。"
        }
    }
    
    def render(self, response: Response) -> dict:
        if not response.degraded:
            return {"content": response.content, "banner": None}
        
        ux = self.MESSAGES.get(response.source, {})
        return {
            "content": response.content,
            "banner": ux.get("banner"),
            "inline_note": ux.get("inline"),
            "retry_available": True,
        }

六、降级演练

class DegradationDrill:
    """降级演练:定期验证降级策略有效性"""
    
    async def run_drill(self):
        """模拟 LLM 故障,验证降级链"""
        results = []
        
        # 测试 1: 主模型超时
        with mock_llm_timeout(ModelTier.PRIMARY):
            result = await self.orchestrator.handle_request(
                messages=[{"role": "user", "content": "What is 2+2?"}]
            )
            results.append(DrillResult(
                test="primary_timeout",
                passed=result.degraded == True,
                response_source=result.source
            ))
        
        # 测试 2: 所有模型宕机
        with mock_all_llm_down():
            result = await self.orchestrator.handle_request(
                messages=[{"role": "user", "content": "你好"}]
            )
            results.append(DrillResult(
                test="all_llm_down",
                passed=result.source == "rule_engine",
                response_source=result.source
            ))
        
        # 测试 3: 缓存命中
        with mock_all_llm_down():
            # 先正常请求一次建立缓存
            await self.orchestrator.handle_request(
                messages=[{"role": "user", "content": "What is your return policy?"}]
            )
            # 再次请求相同问题
            result = await self.orchestrator.handle_request(
                messages=[{"role": "user", "content": "What is your return policy?"}]
            )
            results.append(DrillResult(
                test="cache_hit",
                passed=result.source in ("exact_cache", "semantic_cache"),
                response_source=result.source
            ))
        
        return DrillReport(results=results)

七、降级 Checklist

□ 多 LLM Provider 热备,自动切换 < 5s
□ 降级模型已配置,功能限制明确
□ 语义缓存层已部署,命中率 > 30%
□ 高频问题预计算,离线生成
□ 规则引擎已覆盖核心业务意图
□ 降级通知 UX 已设计,用户可感知
□ 健康检查每 30s 执行,自动恢复
□ 降级演练每月执行,结果归档
□ 人工客服兜底渠道畅通
□ 降级期间数据不丢失(请求排队)

结语

降级策略的本质是"接受不完美,但永不不可用"。在 LLM 依赖度越来越高的今天,没有降级方案的 Agent 系统就像没有安全网的走钢丝。投资降级不是悲观——它是工程成熟度的标志。最好的降级是用户从未注意到的降级。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。