mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-07 01:56:53 +00:00
feat: support Responses to Chat (#5787)
* fix(openai): harden Chat-to-Responses compatibility Add a shared Responses-to-Chat stream state machine and use it from the OpenAI relay path. Preserve assistant text alongside tool calls, bind tool argument deltas by output_index, map incomplete finish reasons, support reasoning/custom tool events, and buffer upstream SSE for non-stream Chat clients. Add deterministic service tests and relay SSE tests for the conversion path. Related to #5745. * refactor: rename openaicompat to relayconvert for improved clarity * feat(gemini): support responses request conversion * feat: add responses to chat conversion support * fix: harden responses chat conversion edge cases
This commit is contained in:
@@ -104,10 +104,18 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if converter != dto.AdvancedCustomConverterNone {
|
||||
switch converter {
|
||||
case dto.AdvancedCustomConverterNone:
|
||||
return a.convertOpenAICompatibleResponsesRequest(c, info, request)
|
||||
case dto.AdvancedCustomConverterOpenAIResponsesToOpenAIChatCompletions:
|
||||
chatReq, err := service.ResponsesRequestToChatCompletionsRequest(&request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.convertOpenAICompatibleRequest(c, info, chatReq)
|
||||
default:
|
||||
return nil, fmt.Errorf("converter %q does not support OpenAI Responses requests", converter)
|
||||
}
|
||||
return a.convertOpenAICompatibleResponsesRequest(c, info, request)
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
|
||||
@@ -221,6 +229,11 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
|
||||
return openai.OaiResponsesToChatStreamHandler(c, info, resp)
|
||||
}
|
||||
return openai.OaiResponsesToChatHandler(c, info, resp)
|
||||
case dto.AdvancedCustomConverterOpenAIResponsesToOpenAIChatCompletions:
|
||||
if info.IsStream {
|
||||
return openai.OaiChatToResponsesStreamHandler(c, info, resp)
|
||||
}
|
||||
return openai.OaiChatToResponsesHandler(c, info, resp)
|
||||
default:
|
||||
return nil, types.NewOpenAIError(fmt.Errorf("unsupported advanced custom converter: %s", a.converter), types.ErrorCodeInvalidRequest, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/url"
|
||||
"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"
|
||||
@@ -279,6 +280,44 @@ func TestAdaptorMatchesGeminiIncomingPathTemplate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdaptorConvertsResponsesRequestToOpenAIChatUpstream(t *testing.T) {
|
||||
adaptor := &Adaptor{}
|
||||
info := advancedCustomRelayInfo(&dto.AdvancedCustomConfig{
|
||||
Routes: []dto.AdvancedCustomRoute{
|
||||
{
|
||||
IncomingPath: "/v1/responses",
|
||||
UpstreamPath: "/v1/chat/completions",
|
||||
Converter: dto.AdvancedCustomConverterOpenAIResponsesToOpenAIChatCompletions,
|
||||
},
|
||||
},
|
||||
})
|
||||
info.RelayMode = relayconstant.RelayModeResponses
|
||||
info.RequestURLPath = "/v1/responses"
|
||||
c := advancedCustomGinContext("/v1/responses")
|
||||
|
||||
converted, err := adaptor.ConvertOpenAIResponsesRequest(c, info, dto.OpenAIResponsesRequest{
|
||||
Model: "gpt-test",
|
||||
Instructions: mustAdvancedCustomRawMessage(t, "system rules"),
|
||||
Input: mustAdvancedCustomRawMessage(t, "hello"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
chatReq, ok := converted.(*dto.GeneralOpenAIRequest)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "gpt-test", chatReq.Model)
|
||||
require.Len(t, chatReq.Messages, 2)
|
||||
assert.Equal(t, "system", chatReq.Messages[0].Role)
|
||||
assert.Equal(t, "system rules", chatReq.Messages[0].StringContent())
|
||||
assert.Equal(t, "user", chatReq.Messages[1].Role)
|
||||
assert.Equal(t, "hello", chatReq.Messages[1].StringContent())
|
||||
|
||||
requestURL, err := adaptor.GetRequestURL(info)
|
||||
require.NoError(t, err)
|
||||
parsedURL, err := url.Parse(requestURL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "/v1/chat/completions", parsedURL.Path)
|
||||
}
|
||||
|
||||
func advancedCustomRelayInfo(config *dto.AdvancedCustomConfig) *relaycommon.RelayInfo {
|
||||
return &relaycommon.RelayInfo{
|
||||
RelayFormat: types.RelayFormatOpenAI,
|
||||
@@ -302,3 +341,10 @@ func advancedCustomGinContext(path string) *gin.Context {
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
return c
|
||||
}
|
||||
|
||||
func mustAdvancedCustomRawMessage(t *testing.T, value any) []byte {
|
||||
t.Helper()
|
||||
raw, err := common.Marshal(value)
|
||||
require.NoError(t, err)
|
||||
return raw
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user