Merge pull request #2827 from ttt132/fix/api-key-responses-sse-fallback
fix: fallback to SSE body for API key responses
This commit is contained in:
commit
69657b2fa1
@ -4922,20 +4922,22 @@ func (s *OpenAIGatewayService) handleNonStreamingResponse(ctx context.Context, r
|
||||
if isEventStreamResponse(resp.Header) {
|
||||
return s.handleSSEToJSON(resp, c, body, originalModel, mappedModel)
|
||||
}
|
||||
bodyLooksLikeSSE := bytes.Contains(body, []byte("data:")) || bytes.Contains(body, []byte("event:"))
|
||||
|
||||
// For OAuth accounts, also fall back to a body-content heuristic because
|
||||
// the upstream may omit the Content-Type header while still sending SSE.
|
||||
// This heuristic is NOT applied to API-key accounts to avoid false
|
||||
// positives on JSON responses that coincidentally contain "data:" or
|
||||
// "event:" in their text content.
|
||||
if account.Type == AccountTypeOAuth {
|
||||
bodyLooksLikeSSE := bytes.Contains(body, []byte("data:")) || bytes.Contains(body, []byte("event:"))
|
||||
if bodyLooksLikeSSE {
|
||||
return s.handleSSEToJSON(resp, c, body, originalModel, mappedModel)
|
||||
}
|
||||
if account.Type == AccountTypeOAuth && bodyLooksLikeSSE {
|
||||
return s.handleSSEToJSON(resp, c, body, originalModel, mappedModel)
|
||||
}
|
||||
|
||||
usageValue, usageOK := extractOpenAIUsageFromJSONBytes(body)
|
||||
if !usageOK {
|
||||
if bodyLooksLikeSSE {
|
||||
return s.handleSSEToJSON(resp, c, body, originalModel, mappedModel)
|
||||
}
|
||||
return nil, fmt.Errorf("parse response: invalid json response")
|
||||
}
|
||||
usage := &usageValue
|
||||
|
||||
@ -2281,6 +2281,35 @@ func TestHandleSSEToJSON_CompletedEventReturnsJSON(t *testing.T) {
|
||||
require.NotContains(t, rec.Body.String(), "data:")
|
||||
}
|
||||
|
||||
func TestHandleNonStreamingResponse_APIKeyFallsBackToSSEBodyWhenContentTypeIsWrong(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
|
||||
|
||||
svc := &OpenAIGatewayService{cfg: &config.Config{}}
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
Body: io.NopCloser(strings.NewReader(strings.Join([]string{
|
||||
`data: {"type":"response.output_text.delta","delta":"hel"}`,
|
||||
`data: {"type":"response.output_text.delta","delta":"lo"}`,
|
||||
`data: {"type":"response.completed","response":{"id":"resp_api_key_sse","object":"response","model":"gpt-5.4","status":"completed","output":[],"usage":{"input_tokens":3,"output_tokens":2,"total_tokens":5}}}`,
|
||||
`data: [DONE]`,
|
||||
}, "\n"))),
|
||||
}
|
||||
account := &Account{ID: 1, Type: AccountTypeAPIKey}
|
||||
|
||||
result, err := svc.handleNonStreamingResponse(context.Background(), resp, c, account, "gpt-5.4", "gpt-5.4")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, 3, result.InputTokens)
|
||||
require.Equal(t, 2, result.OutputTokens)
|
||||
require.NotContains(t, rec.Body.String(), "data:")
|
||||
require.Equal(t, "resp_api_key_sse", gjson.Get(rec.Body.String(), "id").String())
|
||||
require.Equal(t, "hello", gjson.Get(rec.Body.String(), "output.0.content.0.text").String())
|
||||
}
|
||||
|
||||
func TestHandleSSEToJSON_ReconstructsImageGenerationOutputItemDone(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user