mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-21 01:17:59 +00:00
fix: AUTO tier order ascending + stats/CSV export completeness + UI key-view toggle & exit affordance
- scheduler: BuildChain sorts tiers ascending so tier 1 (highest priority) is tried first; previously descending inverted the chain (P10-1..P10-5) - stats/api: handleStatsAPI limit→20000; CSV reads full audit via new Stats.AuditRecords (includes *.old rotation); key_names mapped by keyID() masked key; non-admin filter and keys-csv use masked keys - server: ring buffer NewStats(10000) - ui: dash-row card scroll area moved to table container (fix overflow below card); key view toggle (re-click same key returns to global) + kpi-exit affordance; rec-exit span - chat: chain-failure record now surfaces first failed tier; AUTO comment sync
This commit is contained in:
@ -92,7 +92,7 @@ func (tn *TierNode) NextStart() int64 {
|
||||
// requests (rotation state resets when the chain is rebuilt, e.g. after
|
||||
// editing the rules — acceptable, the swap also resets cooldowns).
|
||||
type Chain struct {
|
||||
Tiers []*TierNode // descending tier order
|
||||
Tiers []*TierNode // ascending tier order (tier 1 = highest priority, tried first)
|
||||
}
|
||||
|
||||
// TierErrors is the per-tier failure summary carried by ChainErr. Errors
|
||||
@ -133,10 +133,10 @@ func (e *ChainErr) Error() string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// BuildChain groups rules into descending tiers and resolves each slot's
|
||||
// provider via prov. Rules whose provider resolves to nil are dropped (the
|
||||
// source no longer serves the model). Slot order within a tier follows the
|
||||
// configured rule order.
|
||||
// BuildChain groups rules into ascending tiers (tier 1 = highest priority,
|
||||
// tried first) and resolves each slot's provider via prov. Rules whose
|
||||
// provider resolves to nil are dropped (the source no longer serves the
|
||||
// model). Slot order within a tier follows the configured rule order.
|
||||
func BuildChain(rules []Rule, prov func(model, source string) Provider) *Chain {
|
||||
byTier := map[int][]*Slot{}
|
||||
var tiers []int
|
||||
@ -157,7 +157,7 @@ func BuildChain(rules []Rule, prov func(model, source string) Provider) *Chain {
|
||||
Prov: p,
|
||||
})
|
||||
}
|
||||
sort.Slice(tiers, func(i, j int) bool { return tiers[i] > tiers[j] })
|
||||
sort.Slice(tiers, func(i, j int) bool { return tiers[i] < tiers[j] })
|
||||
ch := &Chain{}
|
||||
for _, t := range tiers {
|
||||
tn := &TierNode{Tier: t, Slots: byTier[t]}
|
||||
@ -221,12 +221,12 @@ func runTier(ctx context.Context, tn *TierNode, cands []*Slot, base int64, req *
|
||||
return tierResult{hard: hard}
|
||||
}
|
||||
|
||||
// chainDrive runs a request down the chain (plan 2.3): tiers descending,
|
||||
// per-tier round-robin starting at the tier cursor, same-tier runs ordered by
|
||||
// preference (negative prefs sink but stay reachable). Quota-exhausted and
|
||||
// cooling slots are filtered up front; a fully busy tier is polled for a
|
||||
// bounded time before falling through. Failures are summarized in *ChainErr
|
||||
// for the caller to map to HTTP 503.
|
||||
// chainDrive runs a request down the chain (plan 2.3): tiers ascending (tier
|
||||
// 1, the highest priority, first), per-tier round-robin starting at the tier
|
||||
// cursor, same-tier runs ordered by preference (negative prefs sink but stay
|
||||
// reachable). Quota-exhausted and cooling slots are filtered up front; a
|
||||
// fully busy tier is polled for a bounded time before falling through.
|
||||
// Failures are summarized in *ChainErr for the caller to map to HTTP 503.
|
||||
func (s *Scheduler) chainDrive(ctx context.Context, chain *Chain, req *types.ChatRequest, exhausted func(*Slot) bool, stream bool) (*types.UnifiedResponse, <-chan types.UnifiedChunk, string, string, error) {
|
||||
if chain == nil || len(chain.Tiers) == 0 {
|
||||
return nil, nil, "", "", fmt.Errorf("no auto slot configured")
|
||||
|
||||
@ -97,12 +97,12 @@ func TestBuildChainTierOrdering(t *testing.T) {
|
||||
if len(ch.Tiers) != 3 {
|
||||
t.Fatalf("tiers = %d, want 3", len(ch.Tiers))
|
||||
}
|
||||
if ch.Tiers[0].Tier != 3 || ch.Tiers[1].Tier != 2 || ch.Tiers[2].Tier != 1 {
|
||||
t.Fatalf("tier order = %d,%d,%d, want 3,2,1", ch.Tiers[0].Tier, ch.Tiers[1].Tier, ch.Tiers[2].Tier)
|
||||
if ch.Tiers[0].Tier != 1 || ch.Tiers[1].Tier != 2 || ch.Tiers[2].Tier != 3 {
|
||||
t.Fatalf("tier order = %d,%d,%d, want 1,2,3", ch.Tiers[0].Tier, ch.Tiers[1].Tier, ch.Tiers[2].Tier)
|
||||
}
|
||||
// same-tier slots keep configured order, unresolvable rule is dropped
|
||||
if len(ch.Tiers[2].Slots) != 2 || ch.Tiers[2].Slots[0].Model != "a" || ch.Tiers[2].Slots[1].Model != "d" {
|
||||
t.Fatalf("tier 1 slots = %+v", ch.Tiers[2].Slots)
|
||||
if len(ch.Tiers[0].Slots) != 2 || ch.Tiers[0].Slots[0].Model != "a" || ch.Tiers[0].Slots[1].Model != "d" {
|
||||
t.Fatalf("tier 1 slots = %+v", ch.Tiers[0].Slots)
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,9 +182,9 @@ func TestChainAllBusyBoundedWaitThenNextTier(t *testing.T) {
|
||||
a.busy.Store(true)
|
||||
b.busy.Store(true)
|
||||
ch := BuildChain([]Rule{
|
||||
{Tier: 5, Model: "a", Source: "s1"},
|
||||
{Tier: 5, Model: "b", Source: "s2"},
|
||||
{Tier: 4, Model: "c", Source: "s3"},
|
||||
{Tier: 1, Model: "a", Source: "s1"},
|
||||
{Tier: 1, Model: "b", Source: "s2"},
|
||||
{Tier: 2, Model: "c", Source: "s3"},
|
||||
}, bySource(a, b, c))
|
||||
s := New(0)
|
||||
t0 := time.Now()
|
||||
|
||||
Reference in New Issue
Block a user