mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-19 16:39:15 +00:00
172 lines
4.3 KiB
Go
172 lines
4.3 KiB
Go
// Package provider also provides the top-level registry that owns all sources,
|
|
// routes model requests (explicit or AUTO), and supports hot reload.
|
|
package provider
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Registry holds all configured providers and routes model requests.
|
|
type Registry struct {
|
|
mu sync.RWMutex
|
|
providers []*Provider
|
|
byModel map[string]*Provider // modelID -> provider
|
|
defaultM string // default model id ("" means AUTO)
|
|
}
|
|
|
|
func NewRegistry(providers []*Provider, defaultModel string) *Registry {
|
|
r := &Registry{byModel: map[string]*Provider{}, defaultM: defaultModel}
|
|
r.set(providers)
|
|
return r
|
|
}
|
|
|
|
// Replace atomically swaps the provider set (hot reload).
|
|
func (r *Registry) Replace(providers []*Provider) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.set(providers)
|
|
}
|
|
|
|
func (r *Registry) set(providers []*Provider) {
|
|
r.providers = providers
|
|
r.byModel = map[string]*Provider{}
|
|
for _, p := range providers {
|
|
for _, m := range p.Models() {
|
|
r.byModel[strings.ToLower(m)] = p
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Registry) Providers() []*Provider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
out := make([]*Provider, len(r.providers))
|
|
copy(out, r.providers)
|
|
return out
|
|
}
|
|
|
|
func (r *Registry) Count() int {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return len(r.providers)
|
|
}
|
|
|
|
// ModelList returns all exposed model ids (chat + image).
|
|
func (r *Registry) ModelList() []string {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
seen := map[string]bool{}
|
|
for _, p := range r.providers {
|
|
for _, m := range p.Models() {
|
|
if !seen[m] {
|
|
seen[m] = true
|
|
}
|
|
}
|
|
}
|
|
out := make([]string, 0, len(seen))
|
|
for m := range seen {
|
|
out = append(out, m)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// Resolve returns the ordered candidate providers to try for a request,
|
|
// honoring explicit model selection or AUTO (priority order, healthy first).
|
|
//
|
|
// model "" or "AUTO" -> all sources sorted by (priority desc, healthy first).
|
|
// Otherwise the owning provider, if healthy; else its source anyway.
|
|
func (r *Registry) Resolve(model string) []*Provider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
model = strings.TrimSpace(model)
|
|
if model == "" || strings.EqualFold(model, "AUTO") {
|
|
// priority chain across all models
|
|
type cand struct {
|
|
prov *Provider
|
|
priority int
|
|
}
|
|
var cands []cand
|
|
seen := map[string]bool{}
|
|
for _, p := range r.providers {
|
|
prio := -1
|
|
for _, m := range p.cfg.Models {
|
|
if m.Priority > prio {
|
|
prio = m.Priority
|
|
}
|
|
}
|
|
if prio < 0 {
|
|
prio = 0
|
|
}
|
|
cands = append(cands, cand{p, prio})
|
|
seen[p.Name()] = true
|
|
}
|
|
sort.SliceStable(cands, func(i, j int) bool {
|
|
if cands[i].priority != cands[j].priority {
|
|
return cands[i].priority > cands[j].priority
|
|
}
|
|
// healthy preferred at same priority
|
|
return cands[i].prov.Available() && !cands[j].prov.Available()
|
|
})
|
|
out := make([]*Provider, 0, len(cands))
|
|
for _, c := range cands {
|
|
out = append(out, c.prov)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// explicit model
|
|
if p, ok := r.byModel[strings.ToLower(model)]; ok {
|
|
// switch to the owning source but pin the model via request
|
|
return []*Provider{p}
|
|
}
|
|
// unknown model -> fall back to default/AUTO chain
|
|
return r.AUTOChain()
|
|
}
|
|
|
|
// AUTOChain returns the priority-sorted providers for AUTO.
|
|
func (r *Registry) AUTOChain() []*Provider {
|
|
return r.Resolve("AUTO")
|
|
}
|
|
|
|
// Default returns the highest-priority available provider.
|
|
func (r *Registry) Default() *Provider {
|
|
chain := r.AUTOChain()
|
|
if len(chain) == 0 {
|
|
return nil
|
|
}
|
|
return chain[0]
|
|
}
|
|
|
|
// ModelStatus is a web-UI friendly snapshot per source.
|
|
type SourceStatus struct {
|
|
Name string `json:"name"`
|
|
Adapter string `json:"adapter"`
|
|
BaseURL string `json:"base_url"`
|
|
Models []string `json:"models"`
|
|
Available bool `json:"available"`
|
|
Healthy bool `json:"healthy"`
|
|
MaxConcurrent int `json:"max_concurrent"`
|
|
}
|
|
|
|
func (r *Registry) Status() []SourceStatus {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
out := make([]SourceStatus, 0, len(r.providers))
|
|
for _, p := range r.providers {
|
|
s := SourceStatus{
|
|
Name: p.Name(),
|
|
Adapter: p.Adapter(),
|
|
BaseURL: p.Config().BaseURL,
|
|
Models: p.Models(),
|
|
Available: p.Available(),
|
|
Healthy: p.Available(),
|
|
MaxConcurrent: p.MaxConcurrent(),
|
|
}
|
|
out = append(out, s)
|
|
}
|
|
return out
|
|
} |