Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions controller/channel-test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,13 @@ func testChannel(ctx context.Context, channel *model.Channel, testUserID int, te
info.IsChannelTest = true
info.InitChannelMeta(c)

// 与正常请求路径 (TextHelper) 保持一致:渠道开启 ForceStream 时,
// 强制上游流式并缓冲成单 JSON,使只支持流式的上游也能通过非流式测试。
// 仅对 chat completions 生效(OaiStreamBufferHandler 只处理该格式)。
if generalReq, ok := request.(*dto.GeneralOpenAIRequest); ok {
relay.ApplyForceStream(info, generalReq)
}

err = attachTestBillingRequestInput(info, request)
if err != nil {
return testResult{
Expand Down
1 change: 1 addition & 0 deletions dto/channel_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
type ChannelSettings struct {
ForceFormat bool `json:"force_format,omitempty"`
ThinkingToContent bool `json:"thinking_to_content,omitempty"`
ForceStream bool `json:"force_stream,omitempty"`
Proxy string `json:"proxy"`
PassThroughBodyEnabled bool `json:"pass_through_body_enabled,omitempty"`
SystemPrompt string `json:"system_prompt,omitempty"`
Expand Down
4 changes: 3 additions & 1 deletion relay/channel/openai/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,9 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
case relayconstant.RelayModeResponsesCompact:
usage, err = OaiResponsesCompactionHandler(c, resp)
default:
if info.IsStream {
if info.ForceStreamBuffer {
usage, err = OaiStreamBufferHandler(c, info, resp)
} else if info.IsStream {
usage, err = OaiStreamHandler(c, info, resp)
} else {
usage, err = OpenaiHandler(c, info, resp)
Expand Down
28 changes: 28 additions & 0 deletions relay/channel/openai/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"strings"

"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/logger"
relaycommon "github.com/QuantumNous/new-api/relay/common"
Expand Down Expand Up @@ -208,3 +209,30 @@ func sendResponsesStreamData(c *gin.Context, streamResponse dto.ResponsesStreamR
}
_ = helper.ResponseChunkData(c, streamResponse, data)
}

// markContentFilterReject checks choices for a content_filter finish reason
// and sets the admin reject-reason context key if found. Shared between
// OpenaiHandler and OaiStreamBufferHandler.
func markContentFilterReject(c *gin.Context, choices []dto.OpenAITextResponseChoice) {
for _, choice := range choices {
if choice.FinishReason == constant.FinishReasonContentFilter {
common.SetContextKey(c, constant.ContextKeyAdminRejectReason, "openai_finish_reason=content_filter")
break
}
}
}

// marshalTextResponse converts an OpenAITextResponse to the client's expected
// relay format (Claude / Gemini / OpenAI) and marshals it. Shared between
// OpenaiHandler and OaiStreamBufferHandler so the conversion logic stays in
// one place.
func marshalTextResponse(textResponse *dto.OpenAITextResponse, info *relaycommon.RelayInfo) ([]byte, error) {
switch info.RelayFormat {
case types.RelayFormatClaude:
return common.Marshal(service.ResponseOpenAI2Claude(textResponse, info))
case types.RelayFormatGemini:
return common.Marshal(service.ResponseOpenAI2Gemini(textResponse, info))
default:
return common.Marshal(textResponse)
}
}
30 changes: 7 additions & 23 deletions relay/channel/openai/relay-openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,7 @@ func OpenaiHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Respo
return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
}

for _, choice := range simpleResponse.Choices {
if choice.FinishReason == constant.FinishReasonContentFilter {
common.SetContextKey(c, constant.ContextKeyAdminRejectReason, "openai_finish_reason=content_filter")
break
}
}
markContentFilterReject(c, simpleResponse.Choices)

forceFormat := false
if info.ChannelSetting.ForceFormat {
Expand All @@ -251,8 +246,7 @@ func OpenaiHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Respo

applyUsagePostProcessing(info, &simpleResponse.Usage, responseBody)

switch info.RelayFormat {
case types.RelayFormatOpenAI:
if info.RelayFormat == types.RelayFormatOpenAI {
if usageModified {
var bodyMap map[string]interface{}
err = common.Unmarshal(responseBody, &bodyMap)
Expand All @@ -267,23 +261,13 @@ func OpenaiHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Respo
if err != nil {
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
}
} else {
break
}
case types.RelayFormatClaude:
claudeResp := service.ResponseOpenAI2Claude(&simpleResponse, info)
claudeRespStr, err := common.Marshal(claudeResp)
if err != nil {
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
}
responseBody = claudeRespStr
case types.RelayFormatGemini:
geminiResp := service.ResponseOpenAI2Gemini(&simpleResponse, info)
geminiRespStr, err := common.Marshal(geminiResp)
if err != nil {
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
} else {
converted, marshalErr := marshalTextResponse(&simpleResponse, info)
if marshalErr != nil {
return nil, types.NewError(marshalErr, types.ErrorCodeBadResponseBody)
}
responseBody = geminiRespStr
responseBody = converted
}

service.IOCopyBytesGracefully(c, resp, responseBody)
Expand Down
Loading