From c4ca9d7c3b5c59078545f1b406be63197c42b4d3 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 29 Oct 2025 23:33:55 +0800 Subject: [PATCH] refactor(relay): enhance error logging and improve multipart form handling in audio requests #2127 --- controller/relay.go | 3 ++- relay/channel/openai/adaptor.go | 33 ++++++++++++++++++++++------ relay/channel/siliconflow/adaptor.go | 33 ++++++---------------------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index 26a11a370..f8a233e99 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -84,6 +84,7 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) { defer func() { if newAPIError != nil { + logger.LogError(c, fmt.Sprintf("relay error: %s", newAPIError.Error())) newAPIError.SetMessage(common.MessageWithRequestId(newAPIError.Error(), requestId)) switch relayFormat { case types.RelayFormatOpenAIRealtime: @@ -281,7 +282,7 @@ func shouldRetry(c *gin.Context, openaiErr *types.NewAPIError, retryTimes int) b } func processChannelError(c *gin.Context, channelError types.ChannelError, err *types.NewAPIError) { - logger.LogError(c, fmt.Sprintf("relay error (channel #%d, status code: %d): %s", channelError.ChannelId, err.StatusCode, err.Error())) + logger.LogError(c, fmt.Sprintf("channel error (channel #%d, status code: %d): %s", channelError.ChannelId, err.StatusCode, err.Error())) // 不要使用context获取渠道信息,异步处理时可能会出现渠道信息不一致的情况 // do not use context to get channel info, there may be inconsistent channel info when processing asynchronously if service.ShouldDisableChannel(channelError.ChannelId, err) && channelError.AutoBan { diff --git a/relay/channel/openai/adaptor.go b/relay/channel/openai/adaptor.go index 4e41c866a..a83082807 100644 --- a/relay/channel/openai/adaptor.go +++ b/relay/channel/openai/adaptor.go @@ -15,9 +15,11 @@ import ( "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/dto" + "github.com/QuantumNous/new-api/logger" "github.com/QuantumNous/new-api/relay/channel" "github.com/QuantumNous/new-api/relay/channel/ai360" "github.com/QuantumNous/new-api/relay/channel/lingyiwanwu" + //"github.com/QuantumNous/new-api/relay/channel/minimax" "github.com/QuantumNous/new-api/relay/channel/openrouter" "github.com/QuantumNous/new-api/relay/channel/xinference" @@ -352,27 +354,43 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf writer.WriteField("model", request.Model) - // 获取所有表单字段 - formData := c.Request.PostForm + formData, err2 := common.ParseMultipartFormReusable(c) + if err2 != nil { + return nil, fmt.Errorf("error parsing multipart form: %w", err2) + } + + // 打印类似 curl 命令格式的信息 + logger.LogDebug(c.Request.Context(), fmt.Sprintf("--form 'model=\"%s\"'", request.Model)) // 遍历表单字段并打印输出 - for key, values := range formData { + for key, values := range formData.Value { if key == "model" { continue } for _, value := range values { writer.WriteField(key, value) + logger.LogDebug(c.Request.Context(), fmt.Sprintf("--form '%s=\"%s\"'", key, value)) } } - // 添加文件字段 - file, header, err := c.Request.FormFile("file") - if err != nil { + // 从 formData 中获取文件 + fileHeaders := formData.File["file"] + if len(fileHeaders) == 0 { return nil, errors.New("file is required") } + + // 使用 formData 中的第一个文件 + fileHeader := fileHeaders[0] + logger.LogDebug(c.Request.Context(), fmt.Sprintf("--form 'file=@\"%s\"' (size: %d bytes, content-type: %s)", + fileHeader.Filename, fileHeader.Size, fileHeader.Header.Get("Content-Type"))) + + file, err := fileHeader.Open() + if err != nil { + return nil, fmt.Errorf("error opening audio file: %v", err) + } defer file.Close() - part, err := writer.CreateFormFile("file", header.Filename) + part, err := writer.CreateFormFile("file", fileHeader.Filename) if err != nil { return nil, errors.New("create form file failed") } @@ -383,6 +401,7 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf // 关闭 multipart 编写器以设置分界线 writer.Close() c.Request.Header.Set("Content-Type", writer.FormDataContentType()) + logger.LogDebug(c.Request.Context(), fmt.Sprintf("--header 'Content-Type: %s'", writer.FormDataContentType())) return &requestBody, nil } } diff --git a/relay/channel/siliconflow/adaptor.go b/relay/channel/siliconflow/adaptor.go index daffff180..02a82edfc 100644 --- a/relay/channel/siliconflow/adaptor.go +++ b/relay/channel/siliconflow/adaptor.go @@ -31,8 +31,8 @@ func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayIn } func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) { - //TODO implement me - return nil, errors.New("not supported") + adaptor := openai.Adaptor{} + return adaptor.ConvertAudioRequest(c, info, request) } func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) { @@ -65,16 +65,8 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) { func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { if info.RelayMode == constant.RelayModeRerank { return fmt.Sprintf("%s/v1/rerank", info.ChannelBaseUrl), nil - } else if info.RelayMode == constant.RelayModeEmbeddings { - return fmt.Sprintf("%s/v1/embeddings", info.ChannelBaseUrl), nil - } else if info.RelayMode == constant.RelayModeChatCompletions { - return fmt.Sprintf("%s/v1/chat/completions", info.ChannelBaseUrl), nil - } else if info.RelayMode == constant.RelayModeCompletions { - return fmt.Sprintf("%s/v1/completions", info.ChannelBaseUrl), nil - } else if info.RelayMode == constant.RelayModeImagesGenerations { - return fmt.Sprintf("%s/v1/images/generations", info.ChannelBaseUrl), nil } - return fmt.Sprintf("%s/v1/chat/completions", info.ChannelBaseUrl), nil + return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, info.RequestURLPath, info.ChannelType), nil } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { @@ -103,7 +95,8 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo } func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) { - return channel.DoApiRequest(a, c, info, requestBody) + adaptor := openai.Adaptor{} + return adaptor.DoRequest(c, info, requestBody) } func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) { @@ -118,21 +111,9 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom switch info.RelayMode { case constant.RelayModeRerank: usage, err = siliconflowRerankHandler(c, info, resp) - case constant.RelayModeEmbeddings: - usage, err = openai.OpenaiHandler(c, info, resp) - case constant.RelayModeCompletions: - fallthrough - case constant.RelayModeChatCompletions: - fallthrough - case constant.RelayModeImagesGenerations: - fallthrough default: - if info.IsStream { - usage, err = openai.OaiStreamHandler(c, info, resp) - } else { - usage, err = openai.OpenaiHandler(c, info, resp) - } - + adaptor := openai.Adaptor{} + usage, err = adaptor.DoResponse(c, resp, info) } return }