diff --git a/common/model.go b/common/model.go index 4ebc7b532d74..0f5957f071b8 100644 --- a/common/model.go +++ b/common/model.go @@ -12,7 +12,8 @@ var ( ImageGenerationModels = []string{ "dall-e-3", "dall-e-2", - "gpt-image-1", + "prefix:gpt-image-", + "chatgpt-image-latest", "prefix:imagen-", "flux-", "flux.1-", diff --git a/relay/helper/text_endpoint_compatibility_test.go b/relay/helper/text_endpoint_compatibility_test.go new file mode 100644 index 000000000000..69bc0aa6cd0e --- /dev/null +++ b/relay/helper/text_endpoint_compatibility_test.go @@ -0,0 +1,62 @@ +package helper + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + + relayconstant "github.com/QuantumNous/new-api/relay/constant" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestChatCompletionsRejectsImageGenerationModels(t *testing.T) { + gin.SetMode(gin.TestMode) + + tests := []struct { + name string + model string + }{ + {name: "current GPT Image model", model: "gpt-image-2"}, + {name: "GPT Image snapshot", model: "gpt-image-2-2026-04-21"}, + {name: "future GPT Image model", model: "gpt-image-3"}, + {name: "legacy GPT Image model", model: "gpt-image-1.5"}, + {name: "ChatGPT image model", model: "chatgpt-image-latest"}, + {name: "DALL-E model", model: "dall-e-3"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body := `{"model":"` + tt.model + `","messages":[{"role":"user","content":"Generate an image"}]}` + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", bytes.NewBufferString(body)) + c.Request.Header.Set("Content-Type", "application/json") + + _, err := GetAndValidateTextRequest(c, relayconstant.RelayModeChatCompletions) + + require.Error(t, err) + assert.Equal( + t, + `model "`+tt.model+`" is not supported on /v1/chat/completions; use /v1/images/generations instead`, + err.Error(), + ) + }) + } +} + +func TestChatCompletionsAcceptsTextModel(t *testing.T) { + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest( + http.MethodPost, + "/v1/chat/completions", + bytes.NewBufferString(`{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}]}`), + ) + c.Request.Header.Set("Content-Type", "application/json") + + request, err := GetAndValidateTextRequest(c, relayconstant.RelayModeChatCompletions) + + require.NoError(t, err) + assert.Equal(t, "gpt-5", request.Model) +} diff --git a/relay/helper/valid_request.go b/relay/helper/valid_request.go index 22068a45ca5c..4b5859bdb621 100644 --- a/relay/helper/valid_request.go +++ b/relay/helper/valid_request.go @@ -329,6 +329,12 @@ func GetAndValidateTextRequest(c *gin.Context, relayMode int) (*dto.GeneralOpenA if textRequest.Model == "" { return nil, errors.New("model is required") } + if relayMode == relayconstant.RelayModeChatCompletions && common.IsImageGenerationModel(textRequest.Model) { + return nil, fmt.Errorf( + "model %q is not supported on /v1/chat/completions; use /v1/images/generations instead", + textRequest.Model, + ) + } if textRequest.WebSearchOptions != nil { if textRequest.WebSearchOptions.SearchContextSize != "" { validSizes := map[string]bool{