| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- package model
- import (
- "os"
- "strings"
- "sync"
- "time"
- "github.com/labring/aiproxy/core/common/config"
- "github.com/labring/aiproxy/core/relay/mode"
- log "github.com/sirupsen/logrus"
- "gopkg.in/yaml.v3"
- )
- // ChannelItem wraps Channel for YAML configuration
- // Adds TypeName field for human-readable channel type specification
- type ChannelItem struct {
- Channel ` yaml:",inline"` // Embed Channel to inherit all fields
- TypeName string `yaml:"type_name,omitempty"` // Alternative to Type (e.g., "openai", "claude")
- }
- // GetChannelType returns the channel type, converting from TypeName if Type is not set
- func (c *ChannelItem) GetChannelType() ChannelType {
- if c.Type != 0 {
- return c.Type
- }
- // Convert TypeName to Type
- return ChannelType(ChannelTypeNameToType(c.TypeName))
- }
- // ModelConfigItem wraps ModelConfig for YAML configuration
- // Adds TypeName field for human-readable model type specification
- type ModelConfigItem struct {
- ModelConfig ` yaml:",inline"` // Embed ModelConfig to inherit all fields
- TypeName string `yaml:"type_name,omitempty"` // Alternative to Type (e.g., "chat", "embedding")
- }
- // GetModelType returns the model type, converting from TypeName if Type is not set
- func (m *ModelConfigItem) GetModelType() mode.Mode {
- if m.Type != 0 {
- return m.Type
- }
- // Convert TypeName to Type
- return ModelTypeNameToType(m.TypeName)
- }
- // ChannelTypeNameToType converts a channel type name to its numeric type
- func ChannelTypeNameToType(typeName string) int {
- typeName = strings.ToLower(strings.TrimSpace(typeName))
- typeMap := map[string]int{
- "openai": 1,
- "azure": 3,
- "azure2": 4,
- "google gemini (openai)": 12,
- "gemini-openai": 12,
- "baidu v2": 13,
- "baiduv2": 13,
- "anthropic": 14,
- "claude": 14,
- "baidu": 15,
- "zhipu": 16,
- "ali": 17,
- "aliyun": 17,
- "xunfei": 18,
- "ai360": 19,
- "360": 19,
- "openrouter": 20,
- "tencent": 23,
- "google gemini": 24,
- "gemini": 24,
- "moonshot": 25,
- "baichuan": 26,
- "minimax": 27,
- "mistral": 28,
- "groq": 29,
- "ollama": 30,
- "lingyiwanwu": 31,
- "stepfun": 32,
- "aws": 33,
- "coze": 34,
- "cohere": 35,
- "deepseek": 36,
- "cloudflare": 37,
- "doubao": 40,
- "novita": 41,
- "vertexai": 42,
- "vertex": 42,
- "siliconflow": 43,
- "doubao audio": 44,
- "doubaoaudio": 44,
- "xai": 45,
- "doc2x": 46,
- "jina": 47,
- "huggingface text-embeddings-inference": 48,
- "text-embeddings-inference": 48,
- "tei": 48,
- "qianfan": 49,
- "sangfor aicp": 50,
- "aicp": 50,
- "streamlake": 51,
- "zhipu coding": 52,
- "zhipucoding": 52,
- }
- if typ, ok := typeMap[typeName]; ok {
- return typ
- }
- return 0
- }
- // ModelTypeNameToType converts a model type name to its numeric type
- func ModelTypeNameToType(typeName string) mode.Mode {
- typeName = strings.ToLower(strings.TrimSpace(typeName))
- typeMap := map[string]mode.Mode{
- "chat": mode.ChatCompletions,
- "chatcompletion": mode.ChatCompletions,
- "chatcompletions": mode.ChatCompletions,
- "completion": mode.Completions,
- "completions": mode.Completions,
- "embedding": mode.Embeddings,
- "embeddings": mode.Embeddings,
- "moderation": mode.Moderations,
- "moderations": mode.Moderations,
- "image": mode.ImagesGenerations,
- "imagegeneration": mode.ImagesGenerations,
- "imagegenerations": mode.ImagesGenerations,
- "imageedit": mode.ImagesEdits,
- "imageedits": mode.ImagesEdits,
- "audio": mode.AudioSpeech,
- "audiospeech": mode.AudioSpeech,
- "speech": mode.AudioSpeech,
- "audiotranscription": mode.AudioTranscription,
- "transcription": mode.AudioTranscription,
- "audiotranslation": mode.AudioTranslation,
- "translation": mode.AudioTranslation,
- "rerank": mode.Rerank,
- "parsepdf": mode.ParsePdf,
- "pdf": mode.ParsePdf,
- "anthropic": mode.Anthropic,
- "videogeneration": mode.VideoGenerationsJobs,
- "videogenerationsjobs": mode.VideoGenerationsJobs,
- "videogenerationsgetjobs": mode.VideoGenerationsGetJobs,
- "videogenerationscontent": mode.VideoGenerationsContent,
- "responses": mode.Responses,
- "responsesget": mode.ResponsesGet,
- "responsesdelete": mode.ResponsesDelete,
- "responsescancel": mode.ResponsesCancel,
- "responsesinputitems": mode.ResponsesInputItems,
- }
- if typ, ok := typeMap[typeName]; ok {
- return typ
- }
- return mode.Unknown
- }
- // YAMLConfig represents the complete configuration with proper types
- type YAMLConfig struct {
- Channels []ChannelItem `yaml:"channels,omitempty"`
- ModelConfigs []ModelConfigItem `yaml:"modelconfigs,omitempty"`
- Options map[string]string `yaml:"options,omitempty"`
- }
- var (
- yamlConfigCache *YAMLConfig
- yamlConfigCacheTime time.Time
- yamlConfigCacheMutex sync.RWMutex
- yamlConfigCacheTTL = 60 * time.Second
- )
- // LoadYAMLConfig loads and parses YAML configuration with proper types
- // Uses a 60-second cache with double-check locking for performance
- func LoadYAMLConfig() *YAMLConfig {
- yamlConfigCacheMutex.RLock()
- if yamlConfigCache != nil && time.Since(yamlConfigCacheTime) < yamlConfigCacheTTL {
- cache := yamlConfigCache
- yamlConfigCacheMutex.RUnlock()
- return cache
- }
- yamlConfigCacheMutex.RUnlock()
- // Acquire write lock to update cache
- yamlConfigCacheMutex.Lock()
- defer yamlConfigCacheMutex.Unlock()
- // Double check: another goroutine might have updated the cache
- if yamlConfigCache != nil && time.Since(yamlConfigCacheTime) < yamlConfigCacheTTL {
- return yamlConfigCache
- }
- // Load raw YAML data from file
- data, err := config.LoadYAMLConfigData()
- if err != nil {
- if os.IsNotExist(err) {
- yamlConfigCache = nil
- yamlConfigCacheTime = time.Now()
- return nil
- }
- log.Errorf("load config: %v", err)
- yamlConfigCache = nil
- yamlConfigCacheTime = time.Now()
- return nil
- }
- // Parse YAML directly into our types
- var yamlConfig YAMLConfig
- //nolint:musttag
- if err := yaml.Unmarshal(data, &yamlConfig); err != nil {
- log.Errorf("unmarshal config: %v", err)
- yamlConfigCache = nil
- yamlConfigCacheTime = time.Now()
- return nil
- }
- // Update cache
- yamlConfigCache = &yamlConfig
- yamlConfigCacheTime = time.Now()
- return yamlConfigCache
- }
- // applyYAMLConfigToModelConfigCache applies YAML model configs to the model config cache
- // Creates a wrapper cache that checks YAML first, then falls back to database cache
- func applyYAMLConfigToModelConfigCache(
- cache ModelConfigCache,
- ) ModelConfigCache {
- yamlConfig := LoadYAMLConfig()
- if yamlConfig == nil || len(yamlConfig.ModelConfigs) == 0 {
- // No YAML model configs, use existing cache from database
- return cache
- }
- // Build YAML model config map
- yamlModelConfigMap := make(map[string]ModelConfig)
- for i := range yamlConfig.ModelConfigs {
- modelConfigItem := &yamlConfig.ModelConfigs[i]
- // Convert ModelConfigItem to ModelConfig
- modelConfig := modelConfigItem.ModelConfig
- // Convert TypeName to Type if Type is not set
- if modelConfigItem.TypeName != "" && modelConfig.Type == 0 {
- modelConfig.Type = modelConfigItem.GetModelType()
- }
- if modelConfig.Model != "" {
- yamlModelConfigMap[modelConfig.Model] = modelConfig
- }
- }
- log.Infof("loaded %d model configs from config", len(yamlModelConfigMap))
- // Create wrapper cache: YAML configs override database configs
- wrappedCache := &yamlModelConfigCache{
- yamlConfigs: yamlModelConfigMap,
- dbCache: cache,
- }
- return wrappedCache
- }
- // yamlModelConfigCache wraps database cache with YAML overrides
- // When looking up a model config:
- // 1. First check YAML configs (high priority)
- // 2. If not found, fall back to database cache (low priority)
- var _ ModelConfigCache = (*yamlModelConfigCache)(nil)
- type yamlModelConfigCache struct {
- yamlConfigs map[string]ModelConfig
- dbCache ModelConfigCache
- }
- func (y *yamlModelConfigCache) GetModelConfig(model string) (ModelConfig, bool) {
- // First check YAML configs
- if config, ok := y.yamlConfigs[model]; ok {
- return config, true
- }
- // Fall back to database cache
- return y.dbCache.GetModelConfig(model)
- }
- // NewConfigChannels merges YAML channels with database channels
- // YAML channels are assigned negative IDs to distinguish them from database channels
- // Note: YAML channels are NOT persisted to the database
- func NewConfigChannels(yamlConfig *YAMLConfig, status int) []*Channel {
- if yamlConfig == nil || len(yamlConfig.Channels) == 0 {
- return nil
- }
- newChannels := make([]*Channel, 0, len(yamlConfig.Channels))
- // Start negative IDs from -1000 to avoid conflicts
- nextNegativeID := -1
- // Add all YAML channels with negative IDs (they don't override database channels)
- for _, yamlChannelItem := range yamlConfig.Channels {
- // Convert ChannelItem to Channel
- channel := &yamlChannelItem.Channel
- if status != 0 && channel.Status != status {
- continue
- }
- // Convert TypeName to Type if Type is not set
- if yamlChannelItem.TypeName != "" && channel.Type == 0 {
- channel.Type = yamlChannelItem.GetChannelType()
- }
- // Assign negative ID to distinguish from database channels
- channel.ID = nextNegativeID
- nextNegativeID--
- initializeChannelModels(channel)
- initializeChannelModelMapping(channel)
- newChannels = append(newChannels, channel)
- }
- return newChannels
- }
|