|
- package openai
-
- import (
- "io"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
-
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/constant"
- "github.com/QuantumNous/new-api/dto"
- relaycommon "github.com/QuantumNous/new-api/relay/common"
- "github.com/QuantumNous/new-api/relay/helper"
- "github.com/QuantumNous/new-api/types"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
-
- func TestResponsesStreamEventErrorParsing(t *testing.T) {
- t.Run("standalone error event", func(t *testing.T) {
- data := `{"type":"error","error":{"type":"too_many_requests","code":"too_many_requests","message":"Too Many Requests","param":null}}`
-
- var streamResp dto.ResponsesStreamResponse
- err := common.UnmarshalJsonStr(data, &streamResp)
- require.NoError(t, err)
- assert.Equal(t, "error", streamResp.Type)
- assert.NotNil(t, streamResp.Error)
-
- oaiErr := dto.GetOpenAIError(streamResp.Error)
- require.NotNil(t, oaiErr)
- assert.Equal(t, "too_many_requests", oaiErr.Type)
- assert.Equal(t, "Too Many Requests", oaiErr.Message)
- })
-
- t.Run("response.failed event", func(t *testing.T) {
- data := `{"type":"response.failed","response":{"id":"test-id","status":"failed","error":{"type":"server_error","message":"Internal server error"}}}`
-
- var streamResp dto.ResponsesStreamResponse
- err := common.UnmarshalJsonStr(data, &streamResp)
- require.NoError(t, err)
- assert.Equal(t, "response.failed", streamResp.Type)
- require.NotNil(t, streamResp.Response)
-
- oaiErr := streamResp.Response.GetOpenAIError()
- require.NotNil(t, oaiErr)
- assert.Equal(t, "server_error", oaiErr.Type)
- })
-
- t.Run("response.error event with error in response object", func(t *testing.T) {
- data := `{"type":"response.error","response":{"id":"test-id","status":"failed","error":{"type":"invalid_request_error","message":"Invalid model"}}}`
-
- var streamResp dto.ResponsesStreamResponse
- err := common.UnmarshalJsonStr(data, &streamResp)
- require.NoError(t, err)
- assert.Equal(t, "response.error", streamResp.Type)
-
- oaiErr := streamResp.Response.GetOpenAIError()
- require.NotNil(t, oaiErr)
- assert.Equal(t, "invalid_request_error", oaiErr.Type)
- })
-
- t.Run("normal event has no error", func(t *testing.T) {
- data := `{"type":"response.output_text.delta","delta":"hello"}`
-
- var streamResp dto.ResponsesStreamResponse
- err := common.UnmarshalJsonStr(data, &streamResp)
- require.NoError(t, err)
- assert.Equal(t, "response.output_text.delta", streamResp.Type)
- assert.Nil(t, streamResp.Error)
- })
- }
-
- func TestOaiResponsesHandlerAttachesUpstreamBodyOnInvalidJSON(t *testing.T) {
- gin.SetMode(gin.TestMode)
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
-
- resp := &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(strings.NewReader(`{"incomplete":`)),
- }
-
- usage, err := OaiResponsesHandler(c, &relaycommon.RelayInfo{}, resp)
- require.Nil(t, usage)
- require.Error(t, err)
- assert.NotEmpty(t, err.UpstreamBody)
- assert.Contains(t, err.UpstreamBody, `{"incomplete":`)
- }
-
- func TestOaiResponsesStreamHandlerDoesNotForwardErrorEvent(t *testing.T) {
- gin.SetMode(gin.TestMode)
- oldTimeout := constant.StreamingTimeout
- constant.StreamingTimeout = 30
- t.Cleanup(func() {
- constant.StreamingTimeout = oldTimeout
- })
-
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
-
- errorEvent := `{"type":"error","error":{"type":"tokens","code":"rate_limit_exceeded","message":"Request too large","param":null},"sequence_number":2}`
- resp := &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(strings.NewReader("data: " + errorEvent + "\n\n")),
- }
-
- usage, err := OaiResponsesStreamHandler(c, &relaycommon.RelayInfo{}, resp)
- require.Nil(t, usage)
- require.Error(t, err)
- assert.Equal(t, http.StatusInternalServerError, err.StatusCode)
- assert.Contains(t, err.UpstreamBody, "rate_limit_exceeded")
- assert.NotContains(t, w.Body.String(), "rate_limit_exceeded")
- assert.Empty(t, w.Body.String())
- assert.Empty(t, w.Header().Get("Content-Type"))
- }
-
- func TestOaiStreamHandlerDoesNotForwardInitialErrorEvent(t *testing.T) {
- gin.SetMode(gin.TestMode)
- oldTimeout := constant.StreamingTimeout
- constant.StreamingTimeout = 30
- t.Cleanup(func() {
- constant.StreamingTimeout = oldTimeout
- })
-
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
-
- errorEvent := `{"error":{"type":"tokens","code":"rate_limit_exceeded","message":"Request too large","param":null}}`
- resp := &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(strings.NewReader("data: " + errorEvent + "\n\n")),
- }
- info := &relaycommon.RelayInfo{
- RelayFormat: types.RelayFormatOpenAI,
- ChannelMeta: &relaycommon.ChannelMeta{
- UpstreamModelName: "gpt-test",
- },
- }
-
- usage, err := OaiStreamHandler(c, info, resp)
- require.Nil(t, usage)
- require.Error(t, err)
- assert.Contains(t, err.UpstreamBody, "rate_limit_exceeded")
- assert.NotContains(t, w.Body.String(), "rate_limit_exceeded")
- assert.Empty(t, w.Body.String())
- assert.Empty(t, w.Header().Get("Content-Type"))
- }
-
- func TestOaiStreamHandlerReturnsInitialErrorAfterPing(t *testing.T) {
- gin.SetMode(gin.TestMode)
- oldTimeout := constant.StreamingTimeout
- constant.StreamingTimeout = 30
- t.Cleanup(func() {
- constant.StreamingTimeout = oldTimeout
- })
-
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
-
- require.NoError(t, helper.PingData(c))
-
- errorEvent := `{"error":{"type":"tokens","code":"rate_limit_exceeded","message":"Request too large","param":null}}`
- resp := &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(strings.NewReader("data: " + errorEvent + "\n\n")),
- }
- info := &relaycommon.RelayInfo{
- RelayFormat: types.RelayFormatOpenAI,
- ChannelMeta: &relaycommon.ChannelMeta{
- UpstreamModelName: "gpt-test",
- },
- }
-
- usage, err := OaiStreamHandler(c, info, resp)
- require.Nil(t, usage)
- require.Error(t, err)
- assert.Equal(t, http.StatusInternalServerError, err.StatusCode)
- assert.Contains(t, err.UpstreamBody, "rate_limit_exceeded")
- assert.Contains(t, w.Body.String(), ": PING")
- assert.NotContains(t, w.Body.String(), "rate_limit_exceeded")
- }
-
- func TestOaiStreamHandlerDoesNotRetryAfterNormalStreamData(t *testing.T) {
- gin.SetMode(gin.TestMode)
- oldTimeout := constant.StreamingTimeout
- constant.StreamingTimeout = 30
- t.Cleanup(func() {
- constant.StreamingTimeout = oldTimeout
- })
-
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
-
- normalEvent := `{"id":"chatcmpl-test","object":"chat.completion.chunk","created":1,"model":"gpt-test","choices":[{"index":0,"delta":{"content":"hi"}}]}`
- errorEvent := `{"error":{"type":"tokens","code":"rate_limit_exceeded","message":"Request too large","param":null}}`
- resp := &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(strings.NewReader("data: " + normalEvent + "\n\ndata: " + errorEvent + "\n\n")),
- }
- info := &relaycommon.RelayInfo{
- RelayFormat: types.RelayFormatOpenAI,
- ChannelMeta: &relaycommon.ChannelMeta{
- UpstreamModelName: "gpt-test",
- },
- }
-
- usage, err := OaiStreamHandler(c, info, resp)
- require.Nil(t, err)
- require.NotNil(t, usage)
- assert.Contains(t, w.Body.String(), "hi")
- assert.NotContains(t, w.Body.String(), "rate_limit_exceeded")
- }
-
- func TestOaiResponsesStreamHandlerDoesNotRetryAfterNormalStreamData(t *testing.T) {
- gin.SetMode(gin.TestMode)
- oldTimeout := constant.StreamingTimeout
- constant.StreamingTimeout = 30
- t.Cleanup(func() {
- constant.StreamingTimeout = oldTimeout
- })
-
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
-
- normalEvent := `{"type":"response.completed","response":{"id":"resp_test","usage":{"input_tokens":1,"output_tokens":1,"total_tokens":2}}}`
- errorEvent := `{"type":"error","error":{"type":"tokens","code":"rate_limit_exceeded","message":"Request too large","param":null},"sequence_number":2}`
- resp := &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(strings.NewReader("data: " + normalEvent + "\n\ndata: " + errorEvent + "\n\n")),
- }
- info := &relaycommon.RelayInfo{
- ChannelMeta: &relaycommon.ChannelMeta{
- UpstreamModelName: "gpt-test",
- },
- }
-
- usage, err := OaiResponsesStreamHandler(c, info, resp)
- require.Nil(t, err)
- require.NotNil(t, usage)
- assert.Contains(t, w.Body.String(), "response.completed")
- assert.NotContains(t, w.Body.String(), "rate_limit_exceeded")
- }
-
- func TestOaiResponsesToChatHandlerAttachesUpstreamBodyOnInvalidJSON(t *testing.T) {
- gin.SetMode(gin.TestMode)
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
-
- resp := &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(strings.NewReader(`{"incomplete":`)),
- }
-
- usage, err := OaiResponsesToChatHandler(c, &relaycommon.RelayInfo{}, resp)
- require.Nil(t, usage)
- require.Error(t, err)
- assert.NotEmpty(t, err.UpstreamBody)
- assert.Contains(t, err.UpstreamBody, `{"incomplete":`)
- }
|