on main help sign in
fix(openai): 429 insuffice not retry (#546)
107a823nguyen
internal/llm/provider/openai.go+10−2
@@ -529,11 +529,19 @@ func (o *openaiClient) shouldRetry(attempts int, err error) (bool, int64, error)
529 return true, 0, nil
529 return true, 0, nil
530 }
530 }
531
531
532− if apiErr.StatusCode != http.StatusTooManyRequests && apiErr.StatusCode != http.StatusInternalServerError {
532+ if apiErr.StatusCode == http.StatusTooManyRequests {
533+ // Check if this is an insufficient quota error (permanent)
534+ if apiErr.Type == "insufficient_quota" || apiErr.Code == "insufficient_quota" {
535+ return false, 0, fmt.Errorf("OpenAI quota exceeded: %s. Please check your plan and billing details", apiErr.Message)
536+ }
537+ // Other 429 errors (rate limiting) can be retried
538+ } else if apiErr.StatusCode != http.StatusInternalServerError {
533 return false, 0, err
539 return false, 0, err
534 }
540 }
535
541
542+ if apiErr.Response != nil {
536− retryAfterValues = apiErr.Response.Header.Values("Retry-After")
543+ retryAfterValues = apiErr.Response.Header.Values("Retry-After")
544+ }
537 }
545 }
538
546
539 if apiErr != nil {
547 if apiErr != nil {
internal/llm/provider/openai_test.go+76
@@ -6,6 +6,7 @@ import (
6 "net/http"
6 "net/http"
7 "net/http/httptest"
7 "net/http/httptest"
8 "os"
8 "os"
9+ "strings"
9 "testing"
10 "testing"
10 "time"
11 "time"
11
12
@@ -88,3 +89,78 @@ func TestOpenAIClientStreamChoices(t *testing.T) {
88 }
89 }
89 }
90 }
90}
91}
92+
93+func TestOpenAIClient429InsufficientQuotaError(t *testing.T) {
94+ client := &openaiClient{
95+ providerOptions: providerClientOptions{
96+ modelType: config.SelectedModelTypeLarge,
97+ apiKey: "test-key",
98+ systemMessage: "test",
99+ config: config.ProviderConfig{
100+ ID: "test-openai",
101+ APIKey: "test-key",
102+ },
103+ model: func(config.SelectedModelType) catwalk.Model {
104+ return catwalk.Model{
105+ ID: "test-model",
106+ Name: "test-model",
107+ }
108+ },
109+ },
110+ }
111+
112+ // Test insufficient_quota error should not retry
113+ apiErr := &openai.Error{
114+ StatusCode: 429,
115+ Message: "You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors.",
116+ Type: "insufficient_quota",
117+ Code: "insufficient_quota",
118+ }
119+
120+ retry, _, err := client.shouldRetry(1, apiErr)
121+ if retry {
122+ t.Error("Expected shouldRetry to return false for insufficient_quota error, but got true")
123+ }
124+ if err == nil {
125+ t.Error("Expected shouldRetry to return an error for insufficient_quota, but got nil")
126+ }
127+ if err != nil && !strings.Contains(err.Error(), "quota") {
128+ t.Errorf("Expected error message to mention quota, got: %v", err)
129+ }
130+}
131+
132+func TestOpenAIClient429RateLimitError(t *testing.T) {
133+ client := &openaiClient{
134+ providerOptions: providerClientOptions{
135+ modelType: config.SelectedModelTypeLarge,
136+ apiKey: "test-key",
137+ systemMessage: "test",
138+ config: config.ProviderConfig{
139+ ID: "test-openai",
140+ APIKey: "test-key",
141+ },
142+ model: func(config.SelectedModelType) catwalk.Model {
143+ return catwalk.Model{
144+ ID: "test-model",
145+ Name: "test-model",
146+ }
147+ },
148+ },
149+ }
150+
151+ // Test regular rate limit error should retry
152+ apiErr := &openai.Error{
153+ StatusCode: 429,
154+ Message: "Rate limit reached for requests",
155+ Type: "rate_limit_exceeded",
156+ Code: "rate_limit_exceeded",
157+ }
158+
159+ retry, _, err := client.shouldRetry(1, apiErr)
160+ if !retry {
161+ t.Error("Expected shouldRetry to return true for rate_limit_exceeded error, but got false")
162+ }
163+ if err != nil {
164+ t.Errorf("Expected shouldRetry to return nil error for rate_limit_exceeded, but got: %v", err)
165+ }
166+}