// Package llms — bench.go: the mort-flavored facade over majordomo's // health tracker for the `.failover` Discord commands and the failover // web UI. // // Why a facade (vs exposing health.Tracker directly): the admin surfaces // want the historical shape — a benched-only list with a manual/auto // flag. majordomo's tracker treats manual benches (Bench) and automatic // backoffs identically, so the manual marker is kept mort-side. package model import ( "sync" "time" ) // BenchedModel is one currently-benched model for admin display. type BenchedModel struct { // Model is the "provider/model" target key. Model string // Until is the end of the bench window. Until time.Time // ConsecutiveFails is the failure count since the last success. ConsecutiveFails int // Manual reports the bench was placed by an operator (BenchModel) // rather than the automatic failure threshold. Manual bool } var ( manualMu sync.Mutex manualBenches = map[string]time.Time{} ) // ListBenched returns the currently-benched models, manual and automatic, // from the live health tracker. func ListBenched() []BenchedModel { now := time.Now() pruneManual(now) var out []BenchedModel for _, st := range Health().Snapshot() { if !st.Until.After(now) { continue } out = append(out, BenchedModel{ Model: st.Key, Until: st.Until, ConsecutiveFails: st.ConsecutiveFailures, Manual: isManual(st.Key, st.Until), }) } return out } // BenchModel manually benches a model spec until the given time. The // chain executor skips benched targets until the window expires (or // UnbenchModel clears it). func BenchModel(model string, until time.Time) { Health().Bench(model, until) manualMu.Lock() manualBenches[model] = until manualMu.Unlock() } // UnbenchModel clears the bench on a model. Returns true when the model // was actually benched. func UnbenchModel(model string) bool { now := time.Now() wasBenched := Health().BackedOffUntil(model).After(now) Health().Unbench(model) manualMu.Lock() delete(manualBenches, model) manualMu.Unlock() return wasBenched } // isManual reports whether the bench window for key matches a manual // bench placed via BenchModel. An automatic backoff that outlives the // manual window supersedes the marker. func isManual(key string, until time.Time) bool { manualMu.Lock() defer manualMu.Unlock() manualUntil, ok := manualBenches[key] return ok && !until.After(manualUntil) } // pruneManual drops expired manual markers so the map can't grow // unbounded across a long uptime. func pruneManual(now time.Time) { manualMu.Lock() defer manualMu.Unlock() for k, until := range manualBenches { if !until.After(now) { delete(manualBenches, k) } } }