- Backend: ModelConfigStore with Provider/Model/Routing CRUD - Backend: /api/v1/admin/models/config endpoints - Frontend: ModelsPage with 3-tab editor (providers/models/routing) - Frontend: BridgePage showing adapter status + group chat rules - Admin nav expanded to 9 items
136 lines
3.9 KiB
Go
136 lines
3.9 KiB
Go
// 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, "", " ")
|
|
}
|