From a397724f6953d300179c59bdb242c4a7aefc4142 Mon Sep 17 00:00:00 2001 From: IanShaw027 Date: Fri, 7 Aug 2026 15:03:04 +0800 Subject: [PATCH] =?UTF-8?q?refactor(grok):=20=E7=A7=BB=E9=99=A4=E6=9C=AA?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E7=9A=84=E5=85=8D=E8=B4=B9=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/service/grok_free_recovery.go | 87 ------------------- backend/internal/service/grok_p2_test.go | 11 --- .../internal/service/grok_upstream_failure.go | 2 - 3 files changed, 100 deletions(-) delete mode 100644 backend/internal/service/grok_free_recovery.go diff --git a/backend/internal/service/grok_free_recovery.go b/backend/internal/service/grok_free_recovery.go deleted file mode 100644 index ee811f818..000000000 --- a/backend/internal/service/grok_free_recovery.go +++ /dev/null @@ -1,87 +0,0 @@ -package service - -import ( - "strings" - "sync" - "time" -) - -// Lightweight free-usage recovery hint: after free cool ends, keep a short -// "probe preferred" window so the next scheduling pass can prefer accounts that -// have waited out free-usage without immediately re-entering a tight 429 loop. -// Full async quota recovery queues (grok2api) are intentionally not ported. -type grokFreeRecoveryHint struct { - CoolUntil time.Time - ProbeUntil time.Time -} - -type grokFreeRecoveryStore struct { - mu sync.Mutex - items map[int64]grokFreeRecoveryHint -} - -var globalGrokFreeRecovery = &grokFreeRecoveryStore{ - items: make(map[int64]grokFreeRecoveryHint), -} - -const grokFreeRecoveryProbeWindow = 30 * time.Minute - -// markGrokFreeUsageRecovery records that account left free-usage cool and should -// be treated carefully until ProbeUntil. -func markGrokFreeUsageRecovery(accountID int64, coolUntil time.Time) { - if accountID <= 0 { - return - } - now := time.Now() - if coolUntil.IsZero() || !coolUntil.After(now) { - coolUntil = now.Add(2 * time.Hour) - } - globalGrokFreeRecovery.mu.Lock() - defer globalGrokFreeRecovery.mu.Unlock() - globalGrokFreeRecovery.items[accountID] = grokFreeRecoveryHint{ - CoolUntil: coolUntil, - ProbeUntil: coolUntil.Add(grokFreeRecoveryProbeWindow), - } -} - -// isGrokFreeUsageCooling is true while still inside the free-usage cool window. -func isGrokFreeUsageCooling(accountID int64, now time.Time) bool { - if accountID <= 0 { - return false - } - globalGrokFreeRecovery.mu.Lock() - defer globalGrokFreeRecovery.mu.Unlock() - h, ok := globalGrokFreeRecovery.items[accountID] - if !ok { - return false - } - if now.After(h.ProbeUntil) { - delete(globalGrokFreeRecovery.items, accountID) - return false - } - return now.Before(h.CoolUntil) -} - -// isGrokFreeUsageProbePreferred is true after cool ends but before probe window -// expires — account is schedulable but may still be near free-usage edges. -func isGrokFreeUsageProbePreferred(accountID int64, now time.Time) bool { - if accountID <= 0 { - return false - } - globalGrokFreeRecovery.mu.Lock() - defer globalGrokFreeRecovery.mu.Unlock() - h, ok := globalGrokFreeRecovery.items[accountID] - if !ok { - return false - } - if now.After(h.ProbeUntil) { - delete(globalGrokFreeRecovery.items, accountID) - return false - } - return !now.Before(h.CoolUntil) && now.Before(h.ProbeUntil) -} - -// reasonIsGrokFreeUsage detects free-usage cool reasons written by our handlers. -func reasonIsGrokFreeUsage(reason string) bool { - return strings.Contains(strings.ToLower(reason), "free usage") -} diff --git a/backend/internal/service/grok_p2_test.go b/backend/internal/service/grok_p2_test.go index a110e1587..a3b4daa1b 100644 --- a/backend/internal/service/grok_p2_test.go +++ b/backend/internal/service/grok_p2_test.go @@ -57,17 +57,6 @@ func TestGrokStickyAffinitySeed_ScopesByModel(t *testing.T) { require.Contains(t, a, "grok-affinity:v1:") } -func TestGrokFreeUsageRecoveryHints(t *testing.T) { - id := int64(9001) - coolUntil := time.Now().Add(time.Hour) - markGrokFreeUsageRecovery(id, coolUntil) - require.True(t, isGrokFreeUsageCooling(id, time.Now())) - require.False(t, isGrokFreeUsageProbePreferred(id, time.Now())) - // After cool: probe preferred - require.True(t, isGrokFreeUsageProbePreferred(id, coolUntil.Add(time.Minute))) - require.False(t, isGrokFreeUsageCooling(id, coolUntil.Add(time.Minute))) -} - func TestExtractGrokModelIDsFromModelsBody(t *testing.T) { body := []byte(`{"object":"list","data":[{"id":"grok-4.5"},{"id":"grok-4.3"},{"id":"grok-4.5"}]}`) ids := extractGrokModelIDsFromModelsBody(body) diff --git a/backend/internal/service/grok_upstream_failure.go b/backend/internal/service/grok_upstream_failure.go index 6dba87650..04075b4a7 100644 --- a/backend/internal/service/grok_upstream_failure.go +++ b/backend/internal/service/grok_upstream_failure.go @@ -485,8 +485,6 @@ func (s *OpenAIGatewayService) applyGrokUpstreamFailureDecision( // of rotation. return true } - coolUntil := time.Now().Add(decision.Cooldown) - markGrokFreeUsageRecovery(account.ID, coolUntil) case GrokFailureBilling: low := strings.ToLower(decision.Reason) if strings.Contains(low, "spending") || strings.Contains(low, "credits") {