Agent 降级策略:模型挂了,业务不能跟着停

📅 2026/7/12 15:20:46 👁️ 阅读次数 📝 编程学习
Agent 降级策略:模型挂了,业务不能跟着停

Agent 降级策略:模型挂了,业务不能跟着停

一、一个凌晨三点的告警:模型超时引发订单积压

凌晨三点,监控面板上订单队列的红色数字在跳动。12 分钟内积压了 3000 多单,原因很直接——大模型 API 返回了 503。Agent 在等待模型响应时同步阻塞,整个任务链卡死。

这不是模型质量问题,这是架构设计缺陷。把 Agent 的生命周期和大模型可用性强绑定,等于把业务的命脉交给了一个你无法控制的外部服务。任何依赖外部 API 的系统,默认假设都应该是"它随时可能挂"。

生产环境中模型不可用的原因比想象中多:服务商限流、网关超时、内容安全拦截、模型版本回滚。每一条都足以让你的 Agent 停摆。所以降级策略不是"锦上添花",而是上线前的必选项。

二、降级决策的核心模型:条件判断 + 路由分发

Agent 降级的本质是"在模型不可用时,系统如何优雅地继续运作"。设计上分三层:

检测层:捕获错误类型(超时、限流、内容过滤、服务不可用),区分可重试和不可重试错误。

决策层:根据错误类型和上下文,选择降级路径——重试、切换备选模型、使用缓存结果、回退到规则引擎,或者直接返回兜底响应。

执行层:执行降级动作,并记录决策链路用于后续分析。

flowchart TD A[用户请求] --> B[Agent 接收任务] B --> C{调用大模型} C -->|成功| D[返回结果] C -->|失败| E{错误类型判断} E -->|超时 5xx| F[指数退避重试] F -->|重试耗尽| G{降级决策} E -->|限流 429| H{降级决策} E -->|内容过滤| G H --> G G -->|缓存命中| I[返回缓存结果] G -->|备选模型| J[切换到 GPT-4o-mini] G -->|规则引擎| K[规则匹配返回] G -->|兜底响应| L[返回预设回复] I --> M[记录降级日志] J --> M K --> M L --> M M --> D

这个流程的关键在于:决策层不是固定的 if-else,而是可配置的。不同业务场景对"可接受的降级"定义不同。客服场景可以返回预设话术,但金融交易场景必须保证数据一致性,宁可排队也不返回不确定的结果。

三、Go 实现:带重试和降级的 Agent 调用器

package agent import ( "context" "errors" "fmt" "math" "sync" "time" "github.com/sony/gobreaker" ) // FallbackStrategy 降级策略枚举 type FallbackStrategy int const ( FallbackCache FallbackStrategy = iota // 使用缓存 FallbackAltModel // 切换备选模型 FallbackRule // 规则引擎 FallbackDefault // 兜底响应 ) // DegradeDecision 降级决策结果 type DegradeDecision struct { Strategy FallbackStrategy Reason string Latency time.Duration } // ModelCaller 模型调用器,内置重试+降级 type ModelCaller struct { primary LLMClient // 主模型客户端 fallback LLMClient // 备选模型客户端(如 GPT-4o-mini) cache *ResponseCache // 响应缓存 rules *RuleEngine // 规则引擎 breaker *gobreaker.CircuitBreaker maxRetries int baseBackoff time.Duration mu sync.RWMutex } // CallWithFallback 带降级策略的模型调用 func (m *ModelCaller) CallWithFallback( ctx context.Context, prompt string, msgHistory []Message, ) (string, *DegradeDecision, error) { // 先查缓存——零成本降级的第一选择 if cached, ok := m.cache.Get(prompt); ok { return cached, &DegradeDecision{ Strategy: FallbackCache, Reason: "cache_hit", }, nil } // 熔断器检查:如果主模型已经被熔断,直接降级 if m.breaker.State() == gobreaker.StateOpen { return m.degrade(ctx, prompt, msgHistory, "circuit_open") } // 尝试主模型调用,带指数退避重试 result, err := m.callWithRetry(ctx, prompt, msgHistory) if err == nil { // 成功:异步写入缓存 go m.cache.Set(prompt, result, 5*time.Minute) return result, nil, nil } // 主模型失败,触发降级决策 return m.degrade(ctx, prompt, msgHistory, err.Error()) } // callWithRetry 指数退避重试 func (m *ModelCaller) callWithRetry( ctx context.Context, prompt string, history []Message, ) (string, error) { var lastErr error for attempt := 0; attempt < m.maxRetries; attempt++ { // 每次重试前检查 context 是否已取消 select { case <-ctx.Done(): return "", fmt.Errorf("context cancelled: %w", ctx.Err()) default: } result, err := m.primary.Call(ctx, prompt, history) if err == nil { return result, nil } // 不可重试的错误直接返回 if isNonRetryable(err) { return "", fmt.Errorf("non-retryable error: %w", err) } lastErr = err // 指数退避:100ms, 200ms, 400ms... backoff := m.baseBackoff * time.Duration(math.Pow(2, float64(attempt))) // 加上随机抖动,避免惊群效应 backoff += time.Duration(randInt63n(int64(backoff / 2))) select { case <-ctx.Done(): return "", ctx.Err() case <-time.After(backoff): } } return "", fmt.Errorf("max retries exceeded: %w", lastErr) } // degrade 执行降级决策 func (m *ModelCaller) degrade( ctx context.Context, prompt string, history []Message, reason string, ) (string, *DegradeDecision, error) { // 策略 1:尝试备选模型 if m.fallback != nil { if result, err := m.fallback.Call(ctx, prompt, history); err == nil { return result, &DegradeDecision{ Strategy: FallbackAltModel, Reason: fmt.Sprintf("primary_failed: %s", reason), }, nil } } // 策略 2:规则引擎匹配 if m.rules != nil { if result, ok := m.rules.Match(prompt); ok { return result, &DegradeDecision{ Strategy: FallbackRule, Reason: fmt.Sprintf("rule_match: %s", reason), }, nil } } // 策略 3:兜底响应(根据业务场景定制) defaultResp := "系统繁忙,请稍后重试。您的问题已记录,客服将在工作时间回复。" return defaultResp, &DegradeDecision{ Strategy: FallbackDefault, Reason: fmt.Sprintf("all_fallback_exhausted: %s", reason), }, nil } // isNonRetryable 判断是否不可重试 func isNonRetryable(err error) bool { // 内容安全过滤、参数错误等不应重试 return errors.Is(err, ErrContentFilter) || errors.Is(err, ErrInvalidParam) }

四、降级策略的边界与权衡

不是所有场景都适合降级。比如支付确认、合同签署这类强一致性场景,返回一个"可能对"的结果比返回错误更危险。对于这类场景,降级策略应该是"快速失败 + 异步补偿":立即返回失败,同时把请求写入死信队列,等服务恢复后重放。

缓存是把双刃剑。缓存能大幅降低延迟和成本,但过期数据可能导致过时回答。必须设置合理的 TTL,并且在知识库更新时主动失效相关缓存。

备选模型有隐形成本。GPT-4o-mini 比 GPT-4o 便宜,但如果降级频率过高,总额外成本可能超预期。建议对降级调用量做独立计费和告警。

熔断器阈值需要调优。太敏感会导致误降级,太迟钝则失去保护作用。建议基于历史错误率设定阈值,并在运行时动态调整。半开状态的探测间隔也需要根据恢复速度灵活配置。

五、总结

Agent 的降级策略本质上是"外部依赖不可靠"这一前提下的系统工程。三层架构(检测→决策→执行)提供了清晰的关注点分离。核心原则:缓存优先、备选兜底、规则保底、快速失败留给强一致性场景。生产部署时务必监控降级率、降级延迟和降级类型分布,这些指标直接反映系统的韧性边界。模型会挂,业务不能停——这是 AI 工程化的第一课。