revert(providers): revert cooldown tracker per-model changes

This reverts commit cd13f45dcb.

It introduced new errors.
This commit is contained in:
muava12 2026-02-24 10:14:20 +08:00
parent 1fde2d35b9
commit 673280ce2f
3 changed files with 30 additions and 32 deletions

View file

@ -37,14 +37,14 @@ func NewCooldownTracker() *CooldownTracker {
}
}
// MarkFailure records a failure for a specific model key and sets appropriate cooldown.
// MarkFailure records a failure for a provider and sets appropriate cooldown.
// Resets error counts if last failure was more than failureWindow ago.
func (ct *CooldownTracker) MarkFailure(key string, reason FailoverReason) {
func (ct *CooldownTracker) MarkFailure(provider string, reason FailoverReason) {
ct.mu.Lock()
defer ct.mu.Unlock()
now := ct.nowFunc()
entry := ct.getOrCreate(key)
entry := ct.getOrCreate(provider)
// 24h failure window reset: if no failure in failureWindow, reset counters.
if !entry.LastFailure.IsZero() && now.Sub(entry.LastFailure) > ct.failureWindow {
@ -65,12 +65,12 @@ func (ct *CooldownTracker) MarkFailure(key string, reason FailoverReason) {
}
}
// MarkSuccess resets all counters and cooldowns for a specific model key.
func (ct *CooldownTracker) MarkSuccess(key string) {
// MarkSuccess resets all counters and cooldowns for a provider.
func (ct *CooldownTracker) MarkSuccess(provider string) {
ct.mu.Lock()
defer ct.mu.Unlock()
entry := ct.entries[key]
entry := ct.entries[provider]
if entry == nil {
return
}
@ -82,12 +82,12 @@ func (ct *CooldownTracker) MarkSuccess(key string) {
entry.DisabledReason = ""
}
// IsAvailable returns true if the specific model key is not in cooldown or disabled.
func (ct *CooldownTracker) IsAvailable(key string) bool {
// IsAvailable returns true if the provider is not in cooldown or disabled.
func (ct *CooldownTracker) IsAvailable(provider string) bool {
ct.mu.RLock()
defer ct.mu.RUnlock()
entry := ct.entries[key]
entry := ct.entries[provider]
if entry == nil {
return true
}
@ -107,13 +107,13 @@ func (ct *CooldownTracker) IsAvailable(key string) bool {
return true
}
// CooldownRemaining returns how long until the specific model key becomes available.
// CooldownRemaining returns how long until the provider becomes available.
// Returns 0 if already available.
func (ct *CooldownTracker) CooldownRemaining(key string) time.Duration {
func (ct *CooldownTracker) CooldownRemaining(provider string) time.Duration {
ct.mu.RLock()
defer ct.mu.RUnlock()
entry := ct.entries[key]
entry := ct.entries[provider]
if entry == nil {
return 0
}
@ -138,37 +138,37 @@ func (ct *CooldownTracker) CooldownRemaining(key string) time.Duration {
return remaining
}
// ErrorCount returns the current error count for a specific model key.
func (ct *CooldownTracker) ErrorCount(key string) int {
// ErrorCount returns the current error count for a provider.
func (ct *CooldownTracker) ErrorCount(provider string) int {
ct.mu.RLock()
defer ct.mu.RUnlock()
entry := ct.entries[key]
entry := ct.entries[provider]
if entry == nil {
return 0
}
return entry.ErrorCount
}
// FailureCount returns the failure count for a specific reason and model key.
func (ct *CooldownTracker) FailureCount(key string, reason FailoverReason) int {
// FailureCount returns the failure count for a specific reason.
func (ct *CooldownTracker) FailureCount(provider string, reason FailoverReason) int {
ct.mu.RLock()
defer ct.mu.RUnlock()
entry := ct.entries[key]
entry := ct.entries[provider]
if entry == nil {
return 0
}
return entry.FailureCounts[reason]
}
func (ct *CooldownTracker) getOrCreate(key string) *cooldownEntry {
entry := ct.entries[key]
func (ct *CooldownTracker) getOrCreate(provider string) *cooldownEntry {
entry := ct.entries[provider]
if entry == nil {
entry = &cooldownEntry{
FailureCounts: make(map[FailoverReason]int),
}
ct.entries[key] = entry
ct.entries[provider] = entry
}
return entry
}

View file

@ -122,11 +122,9 @@ func (fc *FallbackChain) Execute(
return nil, context.Canceled
}
key := ModelKey(candidate.Provider, candidate.Model)
// Check cooldown.
if !fc.cooldown.IsAvailable(key) {
remaining := fc.cooldown.CooldownRemaining(key)
if !fc.cooldown.IsAvailable(candidate.Provider) {
remaining := fc.cooldown.CooldownRemaining(candidate.Provider)
result.Attempts = append(result.Attempts, FallbackAttempt{
Provider: candidate.Provider,
Model: candidate.Model,
@ -147,7 +145,7 @@ func (fc *FallbackChain) Execute(
if err == nil {
// Success.
fc.cooldown.MarkSuccess(key)
fc.cooldown.MarkSuccess(candidate.Provider)
result.Response = resp
result.Provider = candidate.Provider
result.Model = candidate.Model
@ -193,7 +191,7 @@ func (fc *FallbackChain) Execute(
}
// Retriable error: mark failure and continue to next candidate.
fc.cooldown.MarkFailure(key, failErr.Reason)
fc.cooldown.MarkFailure(candidate.Provider, failErr.Reason)
result.Attempts = append(result.Attempts, FallbackAttempt{
Provider: candidate.Provider,
Model: candidate.Model,

View file

@ -164,7 +164,7 @@ func TestFallback_CooldownSkip(t *testing.T) {
fc := NewFallbackChain(ct)
// Put openai in cooldown
ct.MarkFailure("openai/gpt-4", FailoverRateLimit)
ct.MarkFailure("openai", FailoverRateLimit)
candidates := []FallbackCandidate{
makeCandidate("openai", "gpt-4"),
@ -202,8 +202,8 @@ func TestFallback_AllInCooldown(t *testing.T) {
fc := NewFallbackChain(ct)
// Put all providers in cooldown
ct.MarkFailure("openai/gpt-4", FailoverRateLimit)
ct.MarkFailure("anthropic/claude", FailoverBilling)
ct.MarkFailure("openai", FailoverRateLimit)
ct.MarkFailure("anthropic", FailoverBilling)
candidates := []FallbackCandidate{
makeCandidate("openai", "gpt-4"),
@ -284,7 +284,7 @@ func TestFallback_SuccessResetsCooldown(t *testing.T) {
run := func(ctx context.Context, provider, model string) (*LLMResponse, error) {
attempt++
if attempt == 1 {
ct.MarkFailure("openai/gpt-4", FailoverRateLimit) // simulate failure tracked elsewhere
ct.MarkFailure("openai", FailoverRateLimit) // simulate failure tracked elsewhere
}
return &LLMResponse{Content: "ok", FinishReason: "stop"}, nil
}
@ -293,7 +293,7 @@ func TestFallback_SuccessResetsCooldown(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ct.IsAvailable("openai/gpt-4") {
if !ct.IsAvailable("openai") {
t.Error("success should reset cooldown")
}
}