Merge pull request #3476 from zeyugao/main

Detect OpenAI overloaded errors by structured code
This commit is contained in:
Wesley Liddick
2026-06-26 09:42:22 +08:00
committed by GitHub
3 changed files with 65 additions and 0 deletions
@@ -1144,6 +1144,21 @@ func isOpenAIInstructionsRequiredError(upstreamStatusCode int, upstreamMsg strin
}
func isOpenAITransientProcessingError(upstreamStatusCode int, upstreamMsg string, upstreamBody []byte) bool {
if upstreamStatusCode != http.StatusBadRequest && upstreamStatusCode != http.StatusServiceUnavailable {
return false
}
hasOpenAIServerOverloadedCode := func(payload []byte) bool {
code := strings.ToLower(strings.TrimSpace(gjson.GetBytes(payload, "error.code").String()))
if code == "" {
code = strings.ToLower(strings.TrimSpace(gjson.GetBytes(payload, "response.error.code").String()))
}
return code == "server_is_overloaded" || code == "slow_down"
}
if len(upstreamBody) > 0 && hasOpenAIServerOverloadedCode(upstreamBody) {
return true
}
if upstreamStatusCode != http.StatusBadRequest {
return false
}
@@ -228,6 +228,18 @@ func TestIsOpenAITransientProcessingError(t *testing.T) {
[]byte(`{"error":{"message":"Selected model is at capacity. Please try a different model.","type":"invalid_request_error"}}`),
))
require.True(t, isOpenAITransientProcessingError(
http.StatusBadRequest,
"",
[]byte(`{"error":{"code":"server_is_overloaded","message":"Please retry later.","type":"invalid_request_error"}}`),
))
require.True(t, isOpenAITransientProcessingError(
http.StatusServiceUnavailable,
"",
[]byte(`{"error":{"code":"slow_down","message":"Please retry later."}}`),
))
require.True(t, isOpenAITransientProcessingError(
http.StatusBadRequest,
"",
@@ -1330,6 +1330,44 @@ func TestOpenAIStreamingResponseFailedBeforeOutputCapacityErrorReturnsFailover(t
require.Empty(t, rec.Body.String())
}
func TestOpenAIStreamingResponseFailedBeforeOutputServerOverloadedCodeReturnsFailover(t *testing.T) {
gin.SetMode(gin.TestMode)
cfg := &config.Config{
Gateway: config.GatewayConfig{
StreamDataIntervalTimeout: 0,
StreamKeepaliveInterval: 0,
MaxLineSize: defaultMaxLineSize,
},
}
svc := &OpenAIGatewayService{cfg: cfg}
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest(http.MethodPost, "/", nil)
resp := &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(strings.Join([]string{
"event: response.created",
`data: {"type":"response.created","response":{"id":"resp_1"}}`,
"",
"event: response.failed",
`data: {"type":"response.failed","response":{"id":"resp_1","error":{"code":"server_is_overloaded","message":"Please retry later."}}}`,
"",
}, "\n"))),
Header: http.Header{"X-Request-Id": []string{"rid-overloaded-failed"}},
}
_, err := svc.handleStreamingResponse(c.Request.Context(), resp, c, &Account{ID: 1, Platform: PlatformOpenAI, Name: "acc"}, time.Now(), "model", "model")
require.Error(t, err)
var failoverErr *UpstreamFailoverError
require.ErrorAs(t, err, &failoverErr)
require.Equal(t, http.StatusBadGateway, failoverErr.StatusCode)
require.Contains(t, string(failoverErr.ResponseBody), "Please retry later")
require.False(t, c.Writer.Written())
require.Empty(t, rec.Body.String())
}
func TestOpenAIStreamingPreambleOnlyMissingTerminalReturnsFailover(t *testing.T) {
gin.SetMode(gin.TestMode)
cfg := &config.Config{