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 工程化的第一课。
更多推荐




所有评论(0)