From 756a8c50d6f0a237b54c332641ecdc84f14fbaa3 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 23 Jul 2025 16:32:52 +0800 Subject: [PATCH 1/7] fix: improve error messages for channel retrieval failures in distributor and relay --- controller/relay.go | 8 ++++---- middleware/distributor.go | 4 ++-- model/channel_cache.go | 3 --- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index 18c5f1b4..3660e8be 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -259,10 +259,10 @@ func getChannel(c *gin.Context, group, originalModel string, retryCount int) (*m } channel, selectGroup, err := model.CacheGetRandomSatisfiedChannel(c, group, originalModel, retryCount) if err != nil { - if group == "auto" { - return nil, types.NewError(errors.New(fmt.Sprintf("获取自动分组下模型 %s 的可用渠道失败: %s", originalModel, err.Error())), types.ErrorCodeGetChannelFailed) - } - return nil, types.NewError(errors.New(fmt.Sprintf("获取分组 %s 下模型 %s 的可用渠道失败: %s", selectGroup, originalModel, err.Error())), types.ErrorCodeGetChannelFailed) + return nil, types.NewError(errors.New(fmt.Sprintf("获取分组 %s 下模型 %s 的可用渠道失败(retry): %s", selectGroup, originalModel, err.Error())), types.ErrorCodeGetChannelFailed) + } + if channel == nil { + return nil, types.NewError(errors.New(fmt.Sprintf("分组 %s 下模型 %s 的可用渠道不存在(数据库一致性已被破坏,retry)", selectGroup, originalModel)), types.ErrorCodeGetChannelFailed) } newAPIError := middleware.SetupContextForSelectedChannel(c, channel, originalModel) if newAPIError != nil { diff --git a/middleware/distributor.go b/middleware/distributor.go index 3b04eef0..48c05209 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -107,7 +107,7 @@ func Distribute() func(c *gin.Context) { if userGroup == "auto" { showGroup = fmt.Sprintf("auto(%s)", selectGroup) } - message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", showGroup, modelRequest.Model) + message := fmt.Sprintf("获取分组 %s 下模型 %s 的可用渠道失败(distributor): %s", showGroup, modelRequest.Model, err.Error()) // 如果错误,但是渠道不为空,说明是数据库一致性问题 if channel != nil { common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) @@ -118,7 +118,7 @@ func Distribute() func(c *gin.Context) { return } if channel == nil { - abortWithOpenAiMessage(c, http.StatusServiceUnavailable, fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道(数据库一致性已被破坏)", userGroup, modelRequest.Model)) + abortWithOpenAiMessage(c, http.StatusServiceUnavailable, fmt.Sprintf("分组 %s 下模型 %s 的可用渠道不存在(数据库一致性已被破坏,distributor)", userGroup, modelRequest.Model)) return } } diff --git a/model/channel_cache.go b/model/channel_cache.go index b2451248..d18e9c89 100644 --- a/model/channel_cache.go +++ b/model/channel_cache.go @@ -109,9 +109,6 @@ func CacheGetRandomSatisfiedChannel(c *gin.Context, group string, model string, return nil, group, err } } - if channel == nil { - return nil, group, errors.New("channel not found") - } return channel, selectGroup, nil } From eaee89f77acd1cb8de5612f7b3ae8b964301e485 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 23 Jul 2025 16:46:06 +0800 Subject: [PATCH 2/7] fix(distributor): add validation for model name in channel selection --- middleware/distributor.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/middleware/distributor.go b/middleware/distributor.go index 48c05209..3c529a41 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -100,6 +100,10 @@ func Distribute() func(c *gin.Context) { } if shouldSelectChannel { + if modelRequest.Model == "" { + abortWithOpenAiMessage(c, http.StatusBadRequest, "未指定模型名称,模型名称不能为空") + return + } var selectGroup string channel, selectGroup, err = model.CacheGetRandomSatisfiedChannel(c, userGroup, modelRequest.Model, 0) if err != nil { From 6f74e7b7380f1f572f27121f1146eceabd064db8 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 23 Jul 2025 19:09:20 +0800 Subject: [PATCH 3/7] fix(adaptor): implement request conversion methods for Claude and Image. (close #1419) --- relay/channel/siliconflow/adaptor.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/relay/channel/siliconflow/adaptor.go b/relay/channel/siliconflow/adaptor.go index 63c1c84d..789751b8 100644 --- a/relay/channel/siliconflow/adaptor.go +++ b/relay/channel/siliconflow/adaptor.go @@ -18,20 +18,19 @@ import ( type Adaptor struct { } -func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, error) { - //TODO implement me - panic("implement me") - return nil, nil +func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayInfo, req *dto.ClaudeRequest) (any, error) { + adaptor := openai.Adaptor{} + return adaptor.ConvertClaudeRequest(c, info, req) } func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) { //TODO implement me - return nil, errors.New("not implemented") + return nil, errors.New("not supported") } func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) { - //TODO implement me - return nil, errors.New("not implemented") + adaptor := openai.Adaptor{} + return adaptor.ConvertImageRequest(c, info, request) } func (a *Adaptor) Init(info *relaycommon.RelayInfo) { From 13bdb8095876f10d37a33848cc99a0aa179f4516 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 23 Jul 2025 19:28:58 +0800 Subject: [PATCH 4/7] fix(adaptor): update relay mode handling #1419 --- relay/channel/siliconflow/adaptor.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/relay/channel/siliconflow/adaptor.go b/relay/channel/siliconflow/adaptor.go index 789751b8..c80e9ea1 100644 --- a/relay/channel/siliconflow/adaptor.go +++ b/relay/channel/siliconflow/adaptor.go @@ -46,7 +46,7 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { } else if info.RelayMode == constant.RelayModeCompletions { return fmt.Sprintf("%s/v1/completions", info.BaseUrl), nil } - return "", errors.New("invalid relay mode") + return fmt.Sprintf("%s/v1/chat/completions", info.BaseUrl), nil } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { @@ -80,16 +80,19 @@ 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 + default: if info.IsStream { usage, err = openai.OaiStreamHandler(c, info, resp) } else { usage, err = openai.OpenaiHandler(c, info, resp) } - case constant.RelayModeEmbeddings: - usage, err = openai.OpenaiHandler(c, info, resp) + } return } From ae0461692c08c188a56b28308706b3990c0369a8 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 23 Jul 2025 20:01:03 +0800 Subject: [PATCH 5/7] feat: support ollama claude format --- controller/relay.go | 2 +- relay/channel/ollama/adaptor.go | 26 +++++++++++++++++---- relay/channel/ollama/relay-ollama.go | 10 ++++---- service/error.go | 8 ++----- types/error.go | 34 ++++++++++++++++++++-------- 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index 3660e8be..d4b5fd18 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -56,7 +56,7 @@ func relayHandler(c *gin.Context, relayMode int) *types.NewAPIError { userGroup := c.GetString("group") channelId := c.GetInt("channel_id") other := make(map[string]interface{}) - other["error_type"] = err.ErrorType + other["error_type"] = err.GetErrorType() other["error_code"] = err.GetErrorCode() other["status_code"] = err.StatusCode other["channel_id"] = channelId diff --git a/relay/channel/ollama/adaptor.go b/relay/channel/ollama/adaptor.go index b9e304fc..8fd1e1bf 100644 --- a/relay/channel/ollama/adaptor.go +++ b/relay/channel/ollama/adaptor.go @@ -17,10 +17,13 @@ import ( type Adaptor struct { } -func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, error) { - //TODO implement me - panic("implement me") - return nil, nil +func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.ClaudeRequest) (any, error) { + openaiAdaptor := openai.Adaptor{} + openaiRequest, err := openaiAdaptor.ConvertClaudeRequest(c, info, request) + if err != nil { + return nil, err + } + return requestOpenAI2Ollama(openaiRequest.(*dto.GeneralOpenAIRequest)) } func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) { @@ -37,6 +40,9 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) { } func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { + if info.RelayFormat == relaycommon.RelayFormatClaude { + return info.BaseUrl + "/v1/chat/completions", nil + } switch info.RelayMode { case relayconstant.RelayModeEmbeddings: return info.BaseUrl + "/api/embed", nil @@ -55,7 +61,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn if request == nil { return nil, errors.New("request is nil") } - return requestOpenAI2Ollama(*request) + return requestOpenAI2Ollama(request) } func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) { @@ -85,6 +91,16 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom usage, err = openai.OpenaiHandler(c, info, resp) } } + switch info.RelayMode { + case relayconstant.RelayModeEmbeddings: + usage, err = ollamaEmbeddingHandler(c, info, resp) + default: + if info.IsStream { + usage, err = openai.OaiStreamHandler(c, info, resp) + } else { + usage, err = openai.OpenaiHandler(c, info, resp) + } + } return } diff --git a/relay/channel/ollama/relay-ollama.go b/relay/channel/ollama/relay-ollama.go index cd899b83..f98dfc73 100644 --- a/relay/channel/ollama/relay-ollama.go +++ b/relay/channel/ollama/relay-ollama.go @@ -14,7 +14,7 @@ import ( "github.com/gin-gonic/gin" ) -func requestOpenAI2Ollama(request dto.GeneralOpenAIRequest) (*OllamaRequest, error) { +func requestOpenAI2Ollama(request *dto.GeneralOpenAIRequest) (*OllamaRequest, error) { messages := make([]dto.Message, 0, len(request.Messages)) for _, message := range request.Messages { if !message.IsStringContent() { @@ -92,15 +92,15 @@ func ollamaEmbeddingHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *h var ollamaEmbeddingResponse OllamaEmbeddingResponse responseBody, err := io.ReadAll(resp.Body) if err != nil { - return nil, types.NewError(err, types.ErrorCodeBadResponseBody) + return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError) } common.CloseResponseBodyGracefully(resp) err = common.Unmarshal(responseBody, &ollamaEmbeddingResponse) if err != nil { - return nil, types.NewError(err, types.ErrorCodeBadResponseBody) + return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError) } if ollamaEmbeddingResponse.Error != "" { - return nil, types.NewError(fmt.Errorf("ollama error: %s", ollamaEmbeddingResponse.Error), types.ErrorCodeBadResponseBody) + return nil, types.NewOpenAIError(fmt.Errorf("ollama error: %s", ollamaEmbeddingResponse.Error), types.ErrorCodeBadResponseBody, http.StatusInternalServerError) } flattenedEmbeddings := flattenEmbeddings(ollamaEmbeddingResponse.Embedding) data := make([]dto.OpenAIEmbeddingResponseItem, 0, 1) @@ -121,7 +121,7 @@ func ollamaEmbeddingHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *h } doResponseBody, err := common.Marshal(embeddingResponse) if err != nil { - return nil, types.NewError(err, types.ErrorCodeBadResponseBody) + return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError) } common.IOCopyBytesGracefully(c, resp, doResponseBody) return usage, nil diff --git a/service/error.go b/service/error.go index a0713b55..83979add 100644 --- a/service/error.go +++ b/service/error.go @@ -80,10 +80,7 @@ func ClaudeErrorWrapperLocal(err error, code string, statusCode int) *dto.Claude } func RelayErrorHandler(resp *http.Response, showBodyWhenFail bool) (newApiErr *types.NewAPIError) { - newApiErr = &types.NewAPIError{ - StatusCode: resp.StatusCode, - ErrorType: types.ErrorTypeOpenAIError, - } + newApiErr = types.InitOpenAIError(types.ErrorCodeBadResponseStatusCode, resp.StatusCode) responseBody, err := io.ReadAll(resp.Body) if err != nil { @@ -105,8 +102,7 @@ func RelayErrorHandler(resp *http.Response, showBodyWhenFail bool) (newApiErr *t // General format error (OpenAI, Anthropic, Gemini, etc.) newApiErr = types.WithOpenAIError(errResponse.Error, resp.StatusCode) } else { - newApiErr = types.NewErrorWithStatusCode(errors.New(errResponse.ToMessage()), types.ErrorCodeBadResponseStatusCode, resp.StatusCode) - newApiErr.ErrorType = types.ErrorTypeOpenAIError + newApiErr = types.NewOpenAIError(errors.New(errResponse.ToMessage()), types.ErrorCodeBadResponseStatusCode, resp.StatusCode) } return } diff --git a/types/error.go b/types/error.go index c301e59c..4ffae2d7 100644 --- a/types/error.go +++ b/types/error.go @@ -75,7 +75,7 @@ const ( type NewAPIError struct { Err error RelayError any - ErrorType ErrorType + errorType ErrorType errorCode ErrorCode StatusCode int } @@ -87,6 +87,13 @@ func (e *NewAPIError) GetErrorCode() ErrorCode { return e.errorCode } +func (e *NewAPIError) GetErrorType() ErrorType { + if e == nil { + return "" + } + return e.errorType +} + func (e *NewAPIError) Error() string { if e == nil { return "" @@ -103,7 +110,7 @@ func (e *NewAPIError) SetMessage(message string) { } func (e *NewAPIError) ToOpenAIError() OpenAIError { - switch e.ErrorType { + switch e.errorType { case ErrorTypeOpenAIError: if openAIError, ok := e.RelayError.(OpenAIError); ok { return openAIError @@ -120,14 +127,14 @@ func (e *NewAPIError) ToOpenAIError() OpenAIError { } return OpenAIError{ Message: e.Error(), - Type: string(e.ErrorType), + Type: string(e.errorType), Param: "", Code: e.errorCode, } } func (e *NewAPIError) ToClaudeError() ClaudeError { - switch e.ErrorType { + switch e.errorType { case ErrorTypeOpenAIError: openAIError := e.RelayError.(OpenAIError) return ClaudeError{ @@ -139,7 +146,7 @@ func (e *NewAPIError) ToClaudeError() ClaudeError { default: return ClaudeError{ Message: e.Error(), - Type: string(e.ErrorType), + Type: string(e.errorType), } } } @@ -148,7 +155,7 @@ func NewError(err error, errorCode ErrorCode) *NewAPIError { return &NewAPIError{ Err: err, RelayError: nil, - ErrorType: ErrorTypeNewAPIError, + errorType: ErrorTypeNewAPIError, StatusCode: http.StatusInternalServerError, errorCode: errorCode, } @@ -162,6 +169,13 @@ func NewOpenAIError(err error, errorCode ErrorCode, statusCode int) *NewAPIError return WithOpenAIError(openaiError, statusCode) } +func InitOpenAIError(errorCode ErrorCode, statusCode int) *NewAPIError { + openaiError := OpenAIError{ + Type: string(errorCode), + } + return WithOpenAIError(openaiError, statusCode) +} + func NewErrorWithStatusCode(err error, errorCode ErrorCode, statusCode int) *NewAPIError { return &NewAPIError{ Err: err, @@ -169,7 +183,7 @@ func NewErrorWithStatusCode(err error, errorCode ErrorCode, statusCode int) *New Message: err.Error(), Type: string(errorCode), }, - ErrorType: ErrorTypeNewAPIError, + errorType: ErrorTypeNewAPIError, StatusCode: statusCode, errorCode: errorCode, } @@ -182,7 +196,7 @@ func WithOpenAIError(openAIError OpenAIError, statusCode int) *NewAPIError { } return &NewAPIError{ RelayError: openAIError, - ErrorType: ErrorTypeOpenAIError, + errorType: ErrorTypeOpenAIError, StatusCode: statusCode, Err: errors.New(openAIError.Message), errorCode: ErrorCode(code), @@ -192,7 +206,7 @@ func WithOpenAIError(openAIError OpenAIError, statusCode int) *NewAPIError { func WithClaudeError(claudeError ClaudeError, statusCode int) *NewAPIError { return &NewAPIError{ RelayError: claudeError, - ErrorType: ErrorTypeClaudeError, + errorType: ErrorTypeClaudeError, StatusCode: statusCode, Err: errors.New(claudeError.Message), errorCode: ErrorCode(claudeError.Type), @@ -211,5 +225,5 @@ func IsLocalError(err *NewAPIError) bool { return false } - return err.ErrorType == ErrorTypeNewAPIError + return err.errorType == ErrorTypeNewAPIError } From 77e3502028212a795423256fdf6fe153054e0e9b Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 23 Jul 2025 20:59:56 +0800 Subject: [PATCH 6/7] fix(adaptor): enhance response handling and error logging for Claude format --- relay/channel/ollama/adaptor.go | 12 ++----- relay/channel/openai/helper.go | 6 ++-- relay/channel/openai/relay-openai.go | 17 +++++---- relay/helper/stream_scanner.go | 6 ++++ service/convert.go | 52 ++++++++++++++++++++++------ 5 files changed, 64 insertions(+), 29 deletions(-) diff --git a/relay/channel/ollama/adaptor.go b/relay/channel/ollama/adaptor.go index 8fd1e1bf..ff88de8b 100644 --- a/relay/channel/ollama/adaptor.go +++ b/relay/channel/ollama/adaptor.go @@ -23,6 +23,9 @@ func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayIn if err != nil { return nil, err } + openaiRequest.(*dto.GeneralOpenAIRequest).StreamOptions = &dto.StreamOptions{ + IncludeUsage: true, + } return requestOpenAI2Ollama(openaiRequest.(*dto.GeneralOpenAIRequest)) } @@ -82,15 +85,6 @@ func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, request } func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) { - if info.IsStream { - usage, err = openai.OaiStreamHandler(c, info, resp) - } else { - if info.RelayMode == relayconstant.RelayModeEmbeddings { - usage, err = ollamaEmbeddingHandler(c, info, resp) - } else { - usage, err = openai.OpenaiHandler(c, info, resp) - } - } switch info.RelayMode { case relayconstant.RelayModeEmbeddings: usage, err = ollamaEmbeddingHandler(c, info, resp) diff --git a/relay/channel/openai/helper.go b/relay/channel/openai/helper.go index a068c544..7fee505a 100644 --- a/relay/channel/openai/helper.go +++ b/relay/channel/openai/helper.go @@ -27,7 +27,7 @@ func handleStreamFormat(c *gin.Context, info *relaycommon.RelayInfo, data string func handleClaudeFormat(c *gin.Context, data string, info *relaycommon.RelayInfo) error { var streamResponse dto.ChatCompletionsStreamResponse - if err := json.Unmarshal(common.StringToByteSlice(data), &streamResponse); err != nil { + if err := common.Unmarshal(common.StringToByteSlice(data), &streamResponse); err != nil { return err } @@ -174,7 +174,7 @@ func handleFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, lastStream case relaycommon.RelayFormatClaude: info.ClaudeConvertInfo.Done = true var streamResponse dto.ChatCompletionsStreamResponse - if err := json.Unmarshal(common.StringToByteSlice(lastStreamData), &streamResponse); err != nil { + if err := common.Unmarshal(common.StringToByteSlice(lastStreamData), &streamResponse); err != nil { common.SysError("error unmarshalling stream response: " + err.Error()) return } @@ -183,7 +183,7 @@ func handleFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, lastStream claudeResponses := service.StreamResponseOpenAI2Claude(&streamResponse, info) for _, resp := range claudeResponses { - helper.ClaudeData(c, *resp) + _ = helper.ClaudeData(c, *resp) } } } diff --git a/relay/channel/openai/relay-openai.go b/relay/channel/openai/relay-openai.go index bfe8bcd3..d739ea19 100644 --- a/relay/channel/openai/relay-openai.go +++ b/relay/channel/openai/relay-openai.go @@ -145,8 +145,10 @@ func OaiStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Re common.SysError("error handling stream format: " + err.Error()) } } - lastStreamData = data - streamItems = append(streamItems, data) + if len(data) > 0 { + lastStreamData = data + streamItems = append(streamItems, data) + } return true }) @@ -154,16 +156,18 @@ func OaiStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Re shouldSendLastResp := true if err := handleLastResponse(lastStreamData, &responseId, &createAt, &systemFingerprint, &model, &usage, &containStreamUsage, info, &shouldSendLastResp); err != nil { - common.SysError("error handling last response: " + err.Error()) + common.LogError(c, fmt.Sprintf("error handling last response: %s, lastStreamData: [%s]", err.Error(), lastStreamData)) } - if shouldSendLastResp && info.RelayFormat == relaycommon.RelayFormatOpenAI { - _ = sendStreamData(c, info, lastStreamData, forceFormat, thinkToContent) + if info.RelayFormat == relaycommon.RelayFormatOpenAI { + if shouldSendLastResp { + _ = sendStreamData(c, info, lastStreamData, forceFormat, thinkToContent) + } } // 处理token计算 if err := processTokens(info.RelayMode, streamItems, &responseTextBuilder, &toolCount); err != nil { - common.SysError("error processing tokens: " + err.Error()) + common.LogError(c, "error processing tokens: "+err.Error()) } if !containStreamUsage { @@ -176,7 +180,6 @@ func OaiStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Re } } } - handleFinalResponse(c, info, lastStreamData, responseId, createAt, model, systemFingerprint, usage, containStreamUsage) return usage, nil diff --git a/relay/helper/stream_scanner.go b/relay/helper/stream_scanner.go index b526b1c0..c72aea6a 100644 --- a/relay/helper/stream_scanner.go +++ b/relay/helper/stream_scanner.go @@ -234,6 +234,12 @@ func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon case <-stopChan: return } + } else { + // done, 处理完成标志,直接退出停止读取剩余数据防止出错 + if common.DebugEnabled { + println("received [DONE], stopping scanner") + } + return } } diff --git a/service/convert.go b/service/convert.go index 593b59d9..7d697840 100644 --- a/service/convert.go +++ b/service/convert.go @@ -251,22 +251,54 @@ func StreamResponseOpenAI2Claude(openAIResponse *dto.ChatCompletionsStreamRespon resp.SetIndex(0) claudeResponses = append(claudeResponses, resp) } else { - //resp := &dto.ClaudeResponse{ - // Type: "content_block_start", - // ContentBlock: &dto.ClaudeMediaMessage{ - // Type: "text", - // Text: common.GetPointer[string](""), - // }, - //} - //resp.SetIndex(0) - //claudeResponses = append(claudeResponses, resp) + + } + // 判断首个响应是否存在内容(非标准的 OpenAI 响应) + if len(openAIResponse.Choices) > 0 && len(openAIResponse.Choices[0].Delta.GetContentString()) > 0 { + claudeResponses = append(claudeResponses, &dto.ClaudeResponse{ + Index: &info.ClaudeConvertInfo.Index, + Type: "content_block_start", + ContentBlock: &dto.ClaudeMediaMessage{ + Type: "text", + Text: common.GetPointer[string](""), + }, + }) + claudeResponses = append(claudeResponses, &dto.ClaudeResponse{ + Type: "content_block_delta", + Delta: &dto.ClaudeMediaMessage{ + Type: "text", + Text: common.GetPointer[string](openAIResponse.Choices[0].Delta.GetContentString()), + }, + }) + info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeText } return claudeResponses } if len(openAIResponse.Choices) == 0 { // no choices - // TODO: handle this case + // 可能为非标准的 OpenAI 响应,判断是否已经完成 + if info.Done { + claudeResponses = append(claudeResponses, generateStopBlock(info.ClaudeConvertInfo.Index)) + oaiUsage := info.ClaudeConvertInfo.Usage + if oaiUsage != nil { + claudeResponses = append(claudeResponses, &dto.ClaudeResponse{ + Type: "message_delta", + Usage: &dto.ClaudeUsage{ + InputTokens: oaiUsage.PromptTokens, + OutputTokens: oaiUsage.CompletionTokens, + CacheCreationInputTokens: oaiUsage.PromptTokensDetails.CachedCreationTokens, + CacheReadInputTokens: oaiUsage.PromptTokensDetails.CachedTokens, + }, + Delta: &dto.ClaudeMediaMessage{ + StopReason: common.GetPointer[string](stopReasonOpenAI2Claude(info.FinishReason)), + }, + }) + } + claudeResponses = append(claudeResponses, &dto.ClaudeResponse{ + Type: "message_stop", + }) + } return claudeResponses } else { chosenChoice := openAIResponse.Choices[0] From e162b9c169e400eccb1f3de5e42431e543c0daa0 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 23 Jul 2025 22:00:30 +0800 Subject: [PATCH 7/7] feat: support multi-key mode --- .../channels/modals/EditChannelModal.jsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index 6613dddc..d2fd6758 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -704,20 +704,20 @@ const EditChannelModal = (props) => { } }} >{t('批量创建')} - {/*{batch && (*/} - {/* {*/} - {/* setMultiToSingle(prev => !prev);*/} - {/* setInputs(prev => {*/} - {/* const newInputs = { ...prev };*/} - {/* if (!multiToSingle) {*/} - {/* newInputs.multi_key_mode = multiKeyMode;*/} - {/* } else {*/} - {/* delete newInputs.multi_key_mode;*/} - {/* }*/} - {/* return newInputs;*/} - {/* });*/} - {/* }}>{t('密钥聚合模式')}*/} - {/*)}*/} + {batch && ( + { + setMultiToSingle(prev => !prev); + setInputs(prev => { + const newInputs = { ...prev }; + if (!multiToSingle) { + newInputs.multi_key_mode = multiKeyMode; + } else { + delete newInputs.multi_key_mode; + } + return newInputs; + }); + }}>{t('密钥聚合模式')} + )} ) : null;