// Package api — ModelConfigStore manages LLM provider/model/routing configuration. package api import ( "encoding/json" "sync" ) // ModelProvider represents an LLM API provider. type ModelProvider struct { Name string `json:"name"` BaseURL string `json:"base_url"` APIKey string `json:"api_key,omitempty"` Timeout int `json:"timeout_sec"` Retries int `json:"max_retries"` } // ModelEntry represents a configured LLM model. type ModelEntry struct { ID string `json:"id"` Name string `json:"name"` Provider string `json:"provider"` Description string `json:"description"` Tags []string `json:"tags"` Params map[string]any `json:"params"` Enabled bool `json:"enabled"` } // RoutingRule defines fallback chain for a purpose. type RoutingRule struct { Purpose string `json:"purpose"` FallbackChain []string `json:"fallback_chain"` Required bool `json:"required"` } // ModelConfigStore is an in-memory store for model configuration. type ModelConfigStore struct { mu sync.RWMutex providers map[string]*ModelProvider models map[string]*ModelEntry routing map[string]*RoutingRule } // NewModelConfigStore creates a store with sensible defaults. func NewModelConfigStore() *ModelConfigStore { s := &ModelConfigStore{ providers: make(map[string]*ModelProvider), models: make(map[string]*ModelEntry), routing: make(map[string]*RoutingRule), } // Default provider s.providers["openai"] = &ModelProvider{ Name: "openai", BaseURL: "https://api.openai.com/v1", Timeout: 120, Retries: 3, } // Default models s.models["primary"] = &ModelEntry{ ID: "primary", Name: "gpt-4o", Provider: "openai", Description: "主对话模型", Tags: []string{"chat"}, Params: map[string]any{"temperature": 0.8}, Enabled: true, } s.models["fallback"] = &ModelEntry{ ID: "fallback", Name: "gpt-4o-mini", Provider: "openai", Description: "备用模型", Tags: []string{"chat"}, Params: map[string]any{"temperature": 0.8}, Enabled: true, } // Default routing s.routing["chat"] = &RoutingRule{ Purpose: "chat", FallbackChain: []string{"primary", "fallback"}, Required: true, } return s } func (s *ModelConfigStore) GetProviders() []*ModelProvider { s.mu.RLock(); defer s.mu.RUnlock() result := make([]*ModelProvider, 0, len(s.providers)) for _, p := range s.providers { result = append(result, p) } return result } func (s *ModelConfigStore) GetModels() []*ModelEntry { s.mu.RLock(); defer s.mu.RUnlock() result := make([]*ModelEntry, 0, len(s.models)) for _, m := range s.models { result = append(result, m) } return result } func (s *ModelConfigStore) GetRouting() []*RoutingRule { s.mu.RLock(); defer s.mu.RUnlock() result := make([]*RoutingRule, 0, len(s.routing)) for _, r := range s.routing { result = append(result, r) } return result } func (s *ModelConfigStore) SaveProvider(p *ModelProvider) { s.mu.Lock(); defer s.mu.Unlock() s.providers[p.Name] = p } func (s *ModelConfigStore) SaveModel(m *ModelEntry) { s.mu.Lock(); defer s.mu.Unlock() s.models[m.ID] = m } func (s *ModelConfigStore) SaveRouting(r *RoutingRule) { s.mu.Lock(); defer s.mu.Unlock() s.routing[r.Purpose] = r } func (s *ModelConfigStore) DeleteProvider(name string) { s.mu.Lock(); defer s.mu.Unlock() delete(s.providers, name) } func (s *ModelConfigStore) DeleteModel(id string) { s.mu.Lock(); defer s.mu.Unlock() delete(s.models, id) } func (s *ModelConfigStore) DeleteRouting(purpose string) { s.mu.Lock(); defer s.mu.Unlock() delete(s.routing, purpose) } // Import/Export for models.json compatibility func (s *ModelConfigStore) ExportJSON() ([]byte, error) { s.mu.RLock(); defer s.mu.RUnlock() export := map[string]any{ "providers": s.providers, "models": s.models, "routing": s.routing, } return json.MarshalIndent(export, "", " ") }