package engine // 模型降级(Model Fallback)系统. // // 当主模型 API 调用失败(非重试性错误,如 model_not_found,quota_exceeded)时, // 自动降级到备用模型继续工作,保证 Agent 运行不中断. // // 设计思路: // - FallbackConfig 描述降级策略(备用模型,最大降级次数,触发条件) // - FallbackTracker 在运行期间跟踪降级状态 // - 与 runLoop 集成:API 调用失败时检查是否应该降级 // - 降级次数有上限,防止无限循环 import ( "strings" "sync" ) // FallbackConfig 是模型降级配置. type FallbackConfig struct { // FallbackModel 是备用模型 ID(从 ModelRegistry 获取). // 如果为空,不做降级. FallbackModel string // MaxFallbacks 单次 Run 最大降级次数(防止无限循环). // 默认值为 1. MaxFallbacks int // FallbackOnErrors 触发降级的错误类型. // 例如 ["model_not_found", "quota_exceeded", "overloaded"]. FallbackOnErrors []string } // DefaultFallbackConfig 返回默认的降级配置(不启用降级). func DefaultFallbackConfig() *FallbackConfig { return &FallbackConfig{ FallbackModel: "", MaxFallbacks: 1, FallbackOnErrors: []string{ "model_not_found", "quota_exceeded", "overloaded", }, } } // FallbackTracker 追踪模型降级状态. // 在单次 Run 中使用,记录当前模型和降级历史. type FallbackTracker struct { mu sync.Mutex config *FallbackConfig fallbackCount int // 已降级次数 currentModel string // 当前使用的模型 originalModel string // 原始模型 } // NewFallbackTracker 创建一个新的降级追踪器. // // config 为降级配置,originalModel 为初始使用的模型 ID. // 如果 config 为 nil,使用默认配置(不启用降级). func NewFallbackTracker(config *FallbackConfig, originalModel string) *FallbackTracker { if config == nil { config = DefaultFallbackConfig() } if config.MaxFallbacks <= 0 { config.MaxFallbacks = 1 } return &FallbackTracker{ config: config, currentModel: originalModel, originalModel: originalModel, } } // ShouldFallback 判断在遇到指定错误时是否应该降级. // // 返回备用模型 ID 和是否应该降级. // 以下情况不降级: // - 未配置备用模型 // - 已达到最大降级次数 // - 错误类型不在触发列表中 func (ft *FallbackTracker) ShouldFallback(err error) (fallbackModel string, should bool) { ft.mu.Lock() defer ft.mu.Unlock() // 未配置备用模型 if ft.config.FallbackModel == "" { return "", false } // 已达到最大降级次数 if ft.fallbackCount >= ft.config.MaxFallbacks { return "", false } // 检查错误类型是否匹配触发条件 if err == nil { return "", false } // 历史包袱(LEGACY): 用 strings.Contains 做错误类型匹配--依赖错误消息的文本内容, // 如果 API 修改了错误消息措辞就会失效.更健壮的做法是解析 API 错误响应的 JSON 结构, // 提取 error.type 字段做精确匹配.但当前 api.Client 只返回格式化的字符串错误. // 未来改进:让 api.Client 返回结构化的 APIError 类型. errStr := strings.ToLower(err.Error()) matched := false for _, errType := range ft.config.FallbackOnErrors { if strings.Contains(errStr, strings.ToLower(errType)) { matched = true break } } if !matched { return "", false } return ft.config.FallbackModel, true } // RecordFallback 记录一次降级事件. // 将当前模型切换为备用模型,并增加降级计数. func (ft *FallbackTracker) RecordFallback() { ft.mu.Lock() defer ft.mu.Unlock() ft.fallbackCount++ ft.currentModel = ft.config.FallbackModel } // CurrentModel 返回当前使用的模型 ID. // 如果已降级,返回备用模型;否则返回原始模型. func (ft *FallbackTracker) CurrentModel() string { ft.mu.Lock() defer ft.mu.Unlock() return ft.currentModel } // WasFallback 返回是否发生过降级. func (ft *FallbackTracker) WasFallback() bool { ft.mu.Lock() defer ft.mu.Unlock() return ft.fallbackCount > 0 } // FallbackCount 返回已降级次数. func (ft *FallbackTracker) FallbackCount() int { ft.mu.Lock() defer ft.mu.Unlock() return ft.fallbackCount } // OriginalModel 返回原始模型 ID. func (ft *FallbackTracker) OriginalModel() string { ft.mu.Lock() defer ft.mu.Unlock() return ft.originalModel }