// Package config 的模型角色系统. // // 设计目标: // - 不硬编码任何模型 ID 到业务逻辑中 // - 通过角色(role)抽象不同用途的模型选择 // - 所有默认值可被用户配置覆盖 // - 支持注册第三方模型 // // 角色: // - main: 主对话模型(用户直接交互) // - fast: 快速/廉价模型(摘要,压缩,分类) // - thinking: 深度思考模型(复杂推理) // - embed: 嵌入模型(语义搜索,未来扩展) package config import ( "fmt" "sync" "git.flytoex.net/yuanwei/flyto-agent/pkg/flyto" ) // ModelRole 是模型角色枚举. // 通过角色抽象,业务逻辑不直接引用具体的模型 ID. type ModelRole string const ( // RoleMain 主对话模型(用户直接交互) RoleMain ModelRole = "main" // RoleFast 快速/廉价模型(摘要,压缩,分类) RoleFast ModelRole = "fast" // RoleThinking 深度思考模型(复杂推理) RoleThinking ModelRole = "thinking" // RoleEmbed 嵌入模型(语义搜索,未来用) RoleEmbed ModelRole = "embed" ) // 升华思考(ELEVATED-REVIEWED): 当前 4 个角色已足够覆盖 Agent 的核心场景. // 考虑过增加 RoleCodeReview(代码审查专用模型)和 RoleGuardrail(安全检查专用模型), // 但反向思考后决定不加: // - 角色越多,配置复杂度指数增长(用户需要为每个角色选择模型) // - 当前 RoleMain/RoleThinking 已覆盖高质量推理场景 // - 专用角色可以通过消费层在调用时用 WithModel() 临时指定,无需固化到角色系统 // 保持精简,让角色系统只描述"模型能力档次"而非"业务用途". // ModelConfig 是 flyto.ModelInfo 的类型别名. // 升华改进(ELEVATED): 消除 ModelConfig/ModelInfo 类型分裂-- // 两套类型描述同一概念(模型规格),统一到公共契约类型 flyto.ModelInfo. // 替代方案:<保留两套类型 + 转换函数> - 否决:维护两份字段定义,新增字段必须改两处. type ModelConfig = flyto.ModelInfo // ModelRegistry 管理所有模型配置和角色映射. // 线程安全,支持运行时动态修改. type ModelRegistry struct { mu sync.RWMutex models map[string]*ModelConfig // model_id -> config roles map[ModelRole]string // role -> model_id } // DefaultModels 预置的模型配置. // 升华改进(ELEVATED): 引擎不预设任何供应商的模型信息-- // 各 provider 通过 RegisterModels(registry) 注册自己的模型. // 例如 anthropic.RegisterModels(reg) 注册 Anthropic 模型的定价和 context window. // 替代方案:<预填 Anthropic 模型> - 否决:引擎核心耦合 Anthropic 定价. var DefaultModels = map[string]*ModelConfig{} // DefaultRoles 默认角色映射. // 升华改进(ELEVATED): 引擎不预设任何模型--角色映射由消费层通过 Config.Model 和 // ModelRegistry.SetRole() 显式配置.空 map 确保引擎对供应商完全无感知. // 替代方案:<预填 Anthropic 模型> - 否决:引擎核心耦合 Anthropic. var DefaultRoles = map[ModelRole]string{} // NewModelRegistry 用默认配置初始化模型注册表. // 复制所有默认模型和角色映射,后续修改不影响默认值. func NewModelRegistry() *ModelRegistry { r := &ModelRegistry{ models: make(map[string]*ModelConfig, len(DefaultModels)), roles: make(map[ModelRole]string, len(DefaultRoles)), } // 精妙之处(CLEVER): 深拷贝默认配置--如果直接赋值指针,运行时修改某个实例的 ModelInfo // 会污染全局 DefaultModels,影响后续创建的所有注册表. // Go 的 `copied := *cfg` 是值拷贝,切断了共享关系. for id, cfg := range DefaultModels { copied := *cfg r.models[id] = &copied } // 复制默认角色映射 for role, modelID := range DefaultRoles { r.roles[role] = modelID } return r } // SetRole 设置角色对应的模型 ID. // 如果 modelID 未注册,仍然可以设置(允许使用未预置的模型). func (r *ModelRegistry) SetRole(role ModelRole, modelID string) { r.mu.Lock() defer r.mu.Unlock() r.roles[role] = modelID } // GetRole 获取角色对应的模型 ID. // 如果角色未映射,返回空字符串. func (r *ModelRegistry) GetRole(role ModelRole) string { r.mu.RLock() defer r.mu.RUnlock() return r.roles[role] } // GetConfig 获取指定模型的配置. // 如果模型未注册,返回 nil. func (r *ModelRegistry) GetConfig(modelID string) *ModelConfig { r.mu.RLock() defer r.mu.RUnlock() return r.models[modelID] } // GetConfigForRole 获取指定角色对应的模型配置. // 便捷方法,等价于 GetConfig(GetRole(role)). // 如果角色未映射或模型未注册,返回 nil. func (r *ModelRegistry) GetConfigForRole(role ModelRole) *ModelConfig { r.mu.RLock() defer r.mu.RUnlock() modelID := r.roles[role] if modelID == "" { return nil } return r.models[modelID] } // ContextWindow 获取指定模型的上下文窗口大小. // 如果模型未注册,返回默认值 200000. func (r *ModelRegistry) ContextWindow(modelID string) int { r.mu.RLock() defer r.mu.RUnlock() if cfg, ok := r.models[modelID]; ok { return cfg.ContextWindow } return 200000 } // Register 注册新模型或更新已有模型的配置. // 支持注册第三方模型. func (r *ModelRegistry) Register(modelID string, cfg *ModelConfig) { r.mu.Lock() defer r.mu.Unlock() copied := *cfg copied.ID = modelID r.models[modelID] = &copied } // EstimateCost 估算 API 调用成本(美元). // 考虑输入,输出,缓存读取和缓存写入四种价格. func (r *ModelRegistry) EstimateCost(modelID string, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens int) float64 { r.mu.RLock() cfg := r.models[modelID] r.mu.RUnlock() if cfg == nil { return 0 // 未注册模型不估算成本 } inputCost := float64(inputTokens) * cfg.InputPricePer1M / 1_000_000 outputCost := float64(outputTokens) * cfg.OutputPricePer1M / 1_000_000 cacheReadCost := float64(cacheReadTokens) * cfg.CacheReadPricePer1M / 1_000_000 cacheWriteCost := float64(cacheWriteTokens) * cfg.CacheWritePricePer1M / 1_000_000 return inputCost + outputCost + cacheReadCost + cacheWriteCost } // EstimateSimpleCost 简化版成本估算(不含缓存),向后兼容. func (r *ModelRegistry) EstimateSimpleCost(modelID string, inputTokens, outputTokens int) float64 { return r.EstimateCost(modelID, inputTokens, outputTokens, 0, 0) } // AllModels 返回所有已注册模型的 ID 列表. func (r *ModelRegistry) AllModels() []string { r.mu.RLock() defer r.mu.RUnlock() ids := make([]string, 0, len(r.models)) for id := range r.models { ids = append(ids, id) } return ids } // String 返回角色映射的可读字符串表示. func (r *ModelRegistry) String() string { r.mu.RLock() defer r.mu.RUnlock() return fmt.Sprintf("ModelRegistry{main=%s, fast=%s, thinking=%s}", r.roles[RoleMain], r.roles[RoleFast], r.roles[RoleThinking]) }