mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-21 14:19:18 +08:00
fix(openai): fail over on chat transport errors
This commit is contained in:
@@ -241,18 +241,7 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
|
||||
}
|
||||
resp, err := s.httpUpstream.Do(upstreamReq, proxyURL, account.ID, account.Concurrency)
|
||||
if err != nil {
|
||||
safeErr := sanitizeUpstreamErrorMessage(err.Error())
|
||||
setOpsUpstreamError(c, 0, safeErr, "")
|
||||
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
|
||||
Platform: account.Platform,
|
||||
AccountID: account.ID,
|
||||
AccountName: account.Name,
|
||||
UpstreamStatusCode: 0,
|
||||
Kind: "request_error",
|
||||
Message: safeErr,
|
||||
})
|
||||
writeChatCompletionsError(c, http.StatusBadGateway, "upstream_error", "Upstream request failed")
|
||||
return nil, fmt.Errorf("upstream request failed: %s", safeErr)
|
||||
return nil, s.handleOpenAIUpstreamTransportError(ctx, c, account, err, false)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
|
||||
@@ -171,18 +171,7 @@ func (s *OpenAIGatewayService) forwardAsRawChatCompletions(
|
||||
}
|
||||
resp, err := s.httpUpstream.Do(upstreamReq, proxyURL, account.ID, account.Concurrency)
|
||||
if err != nil {
|
||||
safeErr := sanitizeUpstreamErrorMessage(err.Error())
|
||||
setOpsUpstreamError(c, 0, safeErr, "")
|
||||
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
|
||||
Platform: account.Platform,
|
||||
AccountID: account.ID,
|
||||
AccountName: account.Name,
|
||||
UpstreamStatusCode: 0,
|
||||
Kind: "request_error",
|
||||
Message: safeErr,
|
||||
})
|
||||
writeChatCompletionsError(c, http.StatusBadGateway, "upstream_error", "Upstream request failed")
|
||||
return nil, fmt.Errorf("upstream request failed: %s", safeErr)
|
||||
return nil, s.handleOpenAIUpstreamTransportError(ctx, c, account, err, false)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -37,6 +39,21 @@ func newOpenAITransportErrTestContext() (*gin.Context, *httptest.ResponseRecorde
|
||||
return c, rec
|
||||
}
|
||||
|
||||
type failingOpenAIHTTPUpstream struct {
|
||||
err error
|
||||
calls int
|
||||
}
|
||||
|
||||
func (u *failingOpenAIHTTPUpstream) Do(_ *http.Request, _ string, _ int64, _ int) (*http.Response, error) {
|
||||
u.calls++
|
||||
return nil, u.err
|
||||
}
|
||||
|
||||
func (u *failingOpenAIHTTPUpstream) DoWithTLS(_ *http.Request, _ string, _ int64, _ int, _ *tlsfingerprint.Profile) (*http.Response, error) {
|
||||
u.calls++
|
||||
return nil, u.err
|
||||
}
|
||||
|
||||
// A durable proxy/credential failure must (a) temporarily unschedule the account
|
||||
// so it stops being hammered, and (b) return a failover error so the handler
|
||||
// switches to a healthy account instead of writing a hard 502 itself.
|
||||
@@ -159,3 +176,37 @@ func TestHandleOpenAIUpstreamTransportError_DeadlineExceeded_StillFailsOver(t *t
|
||||
var fo *UpstreamFailoverError
|
||||
require.True(t, errors.As(err, &fo), "context.DeadlineExceeded must still return *UpstreamFailoverError")
|
||||
}
|
||||
|
||||
func TestForwardAsRawChatCompletions_TransportErrorFailsOver(t *testing.T) {
|
||||
repo := &openaiTransportAccountRepoStub{}
|
||||
upstream := &failingOpenAIHTTPUpstream{
|
||||
err: errors.New(`Post "https://opencode.ai/zen/v1/chat/completions": EOF`),
|
||||
}
|
||||
svc := &OpenAIGatewayService{
|
||||
accountRepo: repo,
|
||||
httpUpstream: upstream,
|
||||
cfg: &config.Config{
|
||||
Security: config.SecurityConfig{
|
||||
URLAllowlist: config.URLAllowlistConfig{Enabled: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
account := &Account{
|
||||
ID: 81,
|
||||
Name: "oc-20053",
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeAPIKey,
|
||||
Credentials: map[string]any{"api_key": "sk-test", "base_url": "https://opencode.ai/zen/v1"},
|
||||
}
|
||||
c, rec := newOpenAITransportErrTestContext()
|
||||
body := []byte(`{"model":"deepseek-v4-flash-free","messages":[{"role":"user","content":"hello"}]}`)
|
||||
|
||||
_, err := svc.forwardAsRawChatCompletions(context.Background(), c, account, body, "")
|
||||
|
||||
require.Equal(t, 1, upstream.calls)
|
||||
var fo *UpstreamFailoverError
|
||||
require.True(t, errors.As(err, &fo), "transport error must trigger account failover")
|
||||
require.Equal(t, http.StatusBadGateway, fo.StatusCode)
|
||||
require.Empty(t, repo.tempUnschedCalls, "plain EOF is transient: fail over but do not evict")
|
||||
require.Equal(t, 0, rec.Body.Len(), "service must not write a hard 502 before handler can fail over")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user