mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-10-15 03:42:43 +00:00
feat: AUTO chain rewrite — silent failover+busy skip+pref round-robin+503 tier summary; chain edits reset slot cooldowns (P0/P1); stats by_status + audit jsonl rotation; UI priority-page health badges & status-code card; ctx-menu capture-phase close (outside-press guard); main.go ops warnings; local bundled-Lua verified tests (3 latent bugs fixed); plan.md
This commit is contained in:
@ -75,63 +75,39 @@ func (r *Registry) ModelList() []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// Resolve returns the ordered candidate providers to try for a request,
|
||||
// honoring explicit model selection or AUTO (priority order, healthy first).
|
||||
// Resolve returns the provider (or providers) serving a requested model,
|
||||
// owning no AUTO scheduling logic anymore: AUTO chat scheduling is driven by
|
||||
// the scheduler chain built from the runtime rules (see core/SaveAutoRules
|
||||
// and scheduler.Chain).
|
||||
//
|
||||
// model "" or "AUTO" -> all sources sorted by (priority desc, healthy first).
|
||||
// Otherwise the owning provider, if healthy; else its source anyway.
|
||||
// model "" or "AUTO" -> every provider in configured order. Used only by the
|
||||
// image path (which then filters to image-capable sources) and tool-call
|
||||
// anchoring; chat AUTO requests go through the chain instead.
|
||||
// Otherwise the owning provider; "source-model"/"source:model"/"source/model"
|
||||
// pinning resolves first; an unknown model resolves to nil (gateway answers
|
||||
// 404) instead of silently falling back to the AUTO chain.
|
||||
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)
|
||||
}
|
||||
out := make([]*Provider, len(r.providers))
|
||||
copy(out, r.providers)
|
||||
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}
|
||||
}
|
||||
// "source-model" / "source:model" / "source/model" pinning — disambiguates
|
||||
// duplicate model ids across sources.
|
||||
if p := r.ResolvePinned(model); p != nil {
|
||||
return []*Provider{p}
|
||||
}
|
||||
// unknown model -> fall back to default/AUTO chain
|
||||
return r.AUTOChain()
|
||||
// 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}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EffectiveModel strips a "source-model" / "source:model" / "source/model"
|
||||
@ -174,11 +150,6 @@ func (r *Registry) ResolvePinned(model string) *Provider {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AUTOChain returns the priority-sorted providers for AUTO.
|
||||
func (r *Registry) AUTOChain() []*Provider {
|
||||
return r.Resolve("AUTO")
|
||||
}
|
||||
|
||||
// ProviderForModel returns the provider owning the model id (nil if unknown).
|
||||
func (r *Registry) ProviderForModel(model string) *Provider {
|
||||
r.mu.RLock()
|
||||
@ -220,15 +191,6 @@ func (r *Registry) ProviderForSlot(model, source string) *Provider {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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"`
|
||||
@ -241,6 +203,9 @@ type SourceStatus struct {
|
||||
LiveAvailable bool `json:"live_available"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
LastChecked int64 `json:"last_checked,omitempty"`
|
||||
FailCount int `json:"fail_count,omitempty"`
|
||||
BackoffUntil int64 `json:"backoff_until,omitempty"`
|
||||
Permanent bool `json:"permanent,omitempty"`
|
||||
}
|
||||
|
||||
// ProbeAll runs a live reachability check for every provider (in parallel).
|
||||
@ -267,19 +232,27 @@ func (r *Registry) Status() []SourceStatus {
|
||||
out := make([]SourceStatus, 0, len(r.providers))
|
||||
for _, p := range r.providers {
|
||||
live, lastErr, lastAt := p.LastProbe()
|
||||
s := SourceStatus{
|
||||
Name: p.Name(),
|
||||
Adapter: p.Adapter(),
|
||||
BaseURL: p.Config().BaseURL,
|
||||
Models: p.Models(),
|
||||
Available: p.Available(),
|
||||
Healthy: p.Available(),
|
||||
MaxConcurrent: p.MaxConcurrent(),
|
||||
LiveAvailable: live,
|
||||
LastError: lastErr,
|
||||
LastChecked: lastAt,
|
||||
}
|
||||
out = append(out, s)
|
||||
fails, until, perm := p.HealthInfo()
|
||||
backoffUntil := int64(0)
|
||||
if !until.IsZero() {
|
||||
backoffUntil = until.Unix()
|
||||
}
|
||||
s := SourceStatus{
|
||||
Name: p.Name(),
|
||||
Adapter: p.Adapter(),
|
||||
BaseURL: p.Config().BaseURL,
|
||||
Models: p.Models(),
|
||||
Available: p.Available(),
|
||||
Healthy: p.Available(),
|
||||
MaxConcurrent: p.MaxConcurrent(),
|
||||
LiveAvailable: live,
|
||||
LastError: lastErr,
|
||||
LastChecked: lastAt,
|
||||
FailCount: fails,
|
||||
BackoffUntil: backoffUntil,
|
||||
Permanent: perm,
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user