diff --git a/common/logger.go b/common/logger.go index ac03bd1..a91c915 100644 --- a/common/logger.go +++ b/common/logger.go @@ -2,6 +2,7 @@ package common import ( "context" + "encoding/json" "fmt" "github.com/gin-gonic/gin" "io" @@ -99,6 +100,7 @@ func LogQuota(quota int) string { } } + func LogQuotaF(quota float64) string { if DisplayInCurrencyEnabled { return fmt.Sprintf("$%.6f 额度", quota/QuotaPerUnit) @@ -106,3 +108,14 @@ func LogQuotaF(quota float64) string { return fmt.Sprintf("%d 点额度", int64(quota)) } } + + +// LogJson 仅供测试使用 only for test +func LogJson(ctx context.Context, msg string, obj any) { + jsonStr, err := json.Marshal(obj) + if err != nil { + LogError(ctx, fmt.Sprintf("json marshal failed: %s", err.Error())) + return + } + LogInfo(ctx, fmt.Sprintf("%s | %s", msg, string(jsonStr))) +} diff --git a/controller/relay.go b/controller/relay.go index 3a4ae72..0bbd409 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -124,7 +124,7 @@ func shouldRetry(c *gin.Context, channelId int, openaiErr *dto.OpenAIErrorWithSt func processChannelError(c *gin.Context, channelId int, err *dto.OpenAIErrorWithStatusCode) { autoBan := c.GetBool("auto_ban") - common.LogError(c.Request.Context(), fmt.Sprintf("relay error (channel #%d): %s", channelId, err.Error.Message)) + common.LogError(c.Request.Context(), fmt.Sprintf("relay error (channel #%d, status code: %d): %s", channelId, err.StatusCode, err.Error.Message)) if service.ShouldDisableChannel(&err.Error, err.StatusCode) && autoBan { channelName := c.GetString("channel_name") service.DisableChannel(channelId, channelName, err.Error.Message) @@ -160,7 +160,7 @@ func RelayMidjourney(c *gin.Context) { "code": err.Code, }) channelId := c.GetInt("channel_id") - common.SysError(fmt.Sprintf("relay error (channel #%d): %s", channelId, fmt.Sprintf("%s %s", err.Description, err.Result))) + common.LogError(c, fmt.Sprintf("relay error (channel #%d, status code %d): %s", channelId, statusCode, fmt.Sprintf("%s %s", err.Description, err.Result))) } } diff --git a/dto/text_response.go b/dto/text_response.go index a589d75..53c87eb 100644 --- a/dto/text_response.go +++ b/dto/text_response.go @@ -54,17 +54,33 @@ type OpenAIEmbeddingResponse struct { } type ChatCompletionsStreamResponseChoice struct { - Delta ChatCompletionsStreamResponseChoiceDelta `json:"delta"` - FinishReason *string `json:"finish_reason,omitempty"` - Index int `json:"index,omitempty"` + Delta ChatCompletionsStreamResponseChoiceDelta `json:"delta,omitempty"` + Logprobs *any `json:"logprobs"` + FinishReason *string `json:"finish_reason"` + Index int `json:"index"` } type ChatCompletionsStreamResponseChoiceDelta struct { - Content string `json:"content"` + Content *string `json:"content,omitempty"` Role string `json:"role,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` } +func (c *ChatCompletionsStreamResponseChoiceDelta) IsEmpty() bool { + return c.Content == nil && len(c.ToolCalls) == 0 +} + +func (c *ChatCompletionsStreamResponseChoiceDelta) SetContentString(s string) { + c.Content = &s +} + +func (c *ChatCompletionsStreamResponseChoiceDelta) GetContentString() string { + if c.Content == nil { + return "" + } + return *c.Content +} + type ToolCall struct { // Index is not nil only in chat completion chunk object Index *int `json:"index,omitempty"` @@ -80,11 +96,12 @@ type FunctionCall struct { } type ChatCompletionsStreamResponse struct { - Id string `json:"id"` - Object string `json:"object"` - Created int64 `json:"created"` - Model string `json:"model"` - Choices []ChatCompletionsStreamResponseChoice `json:"choices"` + Id string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + SystemFingerprint *string `json:"system_fingerprint"` + Choices []ChatCompletionsStreamResponseChoice `json:"choices"` } type ChatCompletionsStreamResponseSimple struct { diff --git a/relay/channel/ali/relay-ali.go b/relay/channel/ali/relay-ali.go index e087eea..4280b1c 100644 --- a/relay/channel/ali/relay-ali.go +++ b/relay/channel/ali/relay-ali.go @@ -136,7 +136,7 @@ func responseAli2OpenAI(response *AliChatResponse) *dto.OpenAITextResponse { func streamResponseAli2OpenAI(aliResponse *AliChatResponse) *dto.ChatCompletionsStreamResponse { var choice dto.ChatCompletionsStreamResponseChoice - choice.Delta.Content = aliResponse.Output.Text + choice.Delta.SetContentString(aliResponse.Output.Text) if aliResponse.Output.FinishReason != "null" { finishReason := aliResponse.Output.FinishReason choice.FinishReason = &finishReason @@ -199,7 +199,7 @@ func aliStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorWith usage.TotalTokens = aliResponse.Usage.InputTokens + aliResponse.Usage.OutputTokens } response := streamResponseAli2OpenAI(&aliResponse) - response.Choices[0].Delta.Content = strings.TrimPrefix(response.Choices[0].Delta.Content, lastResponseText) + response.Choices[0].Delta.SetContentString(strings.TrimPrefix(response.Choices[0].Delta.GetContentString(), lastResponseText)) lastResponseText = aliResponse.Output.Text jsonResponse, err := json.Marshal(response) if err != nil { diff --git a/relay/channel/aws/relay-aws.go b/relay/channel/aws/relay-aws.go index bf64f03..1438f10 100644 --- a/relay/channel/aws/relay-aws.go +++ b/relay/channel/aws/relay-aws.go @@ -156,6 +156,7 @@ func awsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, requestMode i var usage relaymodel.Usage var id string var model string + createdTime := common.GetTimestamp() c.Stream(func(w io.Writer) bool { event, ok := <-stream.Events() if !ok { @@ -188,6 +189,7 @@ func awsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, requestMode i if response.Model != "" { model = response.Model } + response.Created = createdTime response.Id = id response.Model = model diff --git a/relay/channel/baidu/relay-baidu.go b/relay/channel/baidu/relay-baidu.go index 6f773ba..f1ceab3 100644 --- a/relay/channel/baidu/relay-baidu.go +++ b/relay/channel/baidu/relay-baidu.go @@ -57,7 +57,7 @@ func responseBaidu2OpenAI(response *BaiduChatResponse) *dto.OpenAITextResponse { func streamResponseBaidu2OpenAI(baiduResponse *BaiduChatStreamResponse) *dto.ChatCompletionsStreamResponse { var choice dto.ChatCompletionsStreamResponseChoice - choice.Delta.Content = baiduResponse.Result + choice.Delta.SetContentString(baiduResponse.Result) if baiduResponse.IsEnd { choice.FinishReason = &relaycommon.StopFinishReason } diff --git a/relay/channel/claude/relay-claude.go b/relay/channel/claude/relay-claude.go index 33e742a..07767a4 100644 --- a/relay/channel/claude/relay-claude.go +++ b/relay/channel/claude/relay-claude.go @@ -73,13 +73,13 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR formatMessages := make([]dto.Message, 0) var lastMessage *dto.Message for i, message := range textRequest.Messages { - if message.Role == "system" { - if i != 0 { - message.Role = "user" - } - } + //if message.Role == "system" { + // if i != 0 { + // message.Role = "user" + // } + //} if message.Role == "" { - message.Role = "user" + textRequest.Messages[i].Role = "user" } fmtMessage := dto.Message{ Role: message.Role, @@ -98,13 +98,24 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR fmtMessage.Content = content } formatMessages = append(formatMessages, fmtMessage) - lastMessage = &message + lastMessage = &textRequest.Messages[i] } claudeMessages := make([]ClaudeMessage, 0) for _, message := range formatMessages { if message.Role == "system" { - claudeRequest.System = message.StringContent() + if message.IsStringContent() { + claudeRequest.System = message.StringContent() + } else { + contents := message.ParseContent() + content := "" + for _, ctx := range contents { + if ctx.Type == "text" { + content += ctx.Text + } + } + claudeRequest.System = content + } } else { claudeMessage := ClaudeMessage{ Role: message.Role, @@ -149,7 +160,6 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR } claudeRequest.Prompt = "" claudeRequest.Messages = claudeMessages - return &claudeRequest, nil } @@ -161,7 +171,7 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (* response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0) var choice dto.ChatCompletionsStreamResponseChoice if reqMode == RequestModeCompletion { - choice.Delta.Content = claudeResponse.Completion + choice.Delta.SetContentString(claudeResponse.Completion) finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason) if finishReason != "null" { choice.FinishReason = &finishReason @@ -171,9 +181,13 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (* response.Id = claudeResponse.Message.Id response.Model = claudeResponse.Message.Model claudeUsage = &claudeResponse.Message.Usage + choice.Delta.SetContentString("") + choice.Delta.Role = "assistant" + } else if claudeResponse.Type == "content_block_start" { + return nil, nil } else if claudeResponse.Type == "content_block_delta" { choice.Index = claudeResponse.Index - choice.Delta.Content = claudeResponse.Delta.Text + choice.Delta.SetContentString(claudeResponse.Delta.Text) } else if claudeResponse.Type == "message_delta" { finishReason := stopReasonClaude2OpenAI(*claudeResponse.Delta.StopReason) if finishReason != "null" { @@ -182,12 +196,15 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (* claudeUsage = &claudeResponse.Usage } else if claudeResponse.Type == "message_stop" { return nil, nil + } else { + return nil, nil } } if claudeUsage == nil { claudeUsage = &ClaudeUsage{} } response.Choices = append(response.Choices, choice) + return &response, claudeUsage } diff --git a/relay/channel/cohere/relay-cohere.go b/relay/channel/cohere/relay-cohere.go index a21d4a9..463e8b1 100644 --- a/relay/channel/cohere/relay-cohere.go +++ b/relay/channel/cohere/relay-cohere.go @@ -117,7 +117,7 @@ func cohereStreamHandler(c *gin.Context, resp *http.Response, modelName string, { Delta: dto.ChatCompletionsStreamResponseChoiceDelta{ Role: "assistant", - Content: cohereResp.Text, + Content: &cohereResp.Text, }, Index: 0, }, diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go index 4a10a73..ee9301d 100644 --- a/relay/channel/gemini/relay-gemini.go +++ b/relay/channel/gemini/relay-gemini.go @@ -151,7 +151,7 @@ func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResp func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) *dto.ChatCompletionsStreamResponse { var choice dto.ChatCompletionsStreamResponseChoice - choice.Delta.Content = geminiResponse.GetResponseText() + choice.Delta.SetContentString(geminiResponse.GetResponseText()) choice.FinishReason = &relaycommon.StopFinishReason var response dto.ChatCompletionsStreamResponse response.Object = "chat.completion.chunk" @@ -203,7 +203,7 @@ func geminiChatStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIEr err := json.Unmarshal([]byte(data), &dummy) responseText += dummy.Content var choice dto.ChatCompletionsStreamResponseChoice - choice.Delta.Content = dummy.Content + choice.Delta.SetContentString(dummy.Content) response := dto.ChatCompletionsStreamResponse{ Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()), Object: "chat.completion.chunk", diff --git a/relay/channel/openai/relay-openai.go b/relay/channel/openai/relay-openai.go index 5469ed7..d627575 100644 --- a/relay/channel/openai/relay-openai.go +++ b/relay/channel/openai/relay-openai.go @@ -68,7 +68,7 @@ func OpenaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*d err := json.Unmarshal(common.StringToByteSlice(item), &streamResponse) if err == nil { for _, choice := range streamResponse.Choices { - responseTextBuilder.WriteString(choice.Delta.Content) + responseTextBuilder.WriteString(choice.Delta.GetContentString()) if choice.Delta.ToolCalls != nil { if len(choice.Delta.ToolCalls) > toolCount { toolCount = len(choice.Delta.ToolCalls) @@ -84,7 +84,7 @@ func OpenaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*d } else { for _, streamResponse := range streamResponses { for _, choice := range streamResponse.Choices { - responseTextBuilder.WriteString(choice.Delta.Content) + responseTextBuilder.WriteString(choice.Delta.GetContentString()) if choice.Delta.ToolCalls != nil { if len(choice.Delta.ToolCalls) > toolCount { toolCount = len(choice.Delta.ToolCalls) diff --git a/relay/channel/palm/relay-palm.go b/relay/channel/palm/relay-palm.go index 3a7d4fa..6933d6f 100644 --- a/relay/channel/palm/relay-palm.go +++ b/relay/channel/palm/relay-palm.go @@ -61,7 +61,7 @@ func responsePaLM2OpenAI(response *PaLMChatResponse) *dto.OpenAITextResponse { func streamResponsePaLM2OpenAI(palmResponse *PaLMChatResponse) *dto.ChatCompletionsStreamResponse { var choice dto.ChatCompletionsStreamResponseChoice if len(palmResponse.Candidates) > 0 { - choice.Delta.Content = palmResponse.Candidates[0].Content + choice.Delta.SetContentString(palmResponse.Candidates[0].Content) } choice.FinishReason = &relaycommon.StopFinishReason var response dto.ChatCompletionsStreamResponse diff --git a/relay/channel/tencent/relay-tencent.go b/relay/channel/tencent/relay-tencent.go index 6f4cd91..c22b545 100644 --- a/relay/channel/tencent/relay-tencent.go +++ b/relay/channel/tencent/relay-tencent.go @@ -86,7 +86,7 @@ func streamResponseTencent2OpenAI(TencentResponse *TencentChatResponse) *dto.Cha } if len(TencentResponse.Choices) > 0 { var choice dto.ChatCompletionsStreamResponseChoice - choice.Delta.Content = TencentResponse.Choices[0].Delta.Content + choice.Delta.SetContentString(TencentResponse.Choices[0].Delta.Content) if TencentResponse.Choices[0].FinishReason == "stop" { choice.FinishReason = &relaycommon.StopFinishReason } @@ -138,7 +138,7 @@ func tencentStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIError } response := streamResponseTencent2OpenAI(&TencentResponse) if len(response.Choices) != 0 { - responseText += response.Choices[0].Delta.Content + responseText += response.Choices[0].Delta.GetContentString() } jsonResponse, err := json.Marshal(response) if err != nil { diff --git a/relay/channel/xunfei/relay-xunfei.go b/relay/channel/xunfei/relay-xunfei.go index 1690e96..7cb6c8a 100644 --- a/relay/channel/xunfei/relay-xunfei.go +++ b/relay/channel/xunfei/relay-xunfei.go @@ -87,7 +87,7 @@ func streamResponseXunfei2OpenAI(xunfeiResponse *XunfeiChatResponse) *dto.ChatCo } } var choice dto.ChatCompletionsStreamResponseChoice - choice.Delta.Content = xunfeiResponse.Payload.Choices.Text[0].Content + choice.Delta.SetContentString(xunfeiResponse.Payload.Choices.Text[0].Content) if xunfeiResponse.Payload.Choices.Status == 2 { choice.FinishReason = &relaycommon.StopFinishReason } diff --git a/relay/channel/zhipu/relay-zhipu.go b/relay/channel/zhipu/relay-zhipu.go index 8a54842..5ef9d7a 100644 --- a/relay/channel/zhipu/relay-zhipu.go +++ b/relay/channel/zhipu/relay-zhipu.go @@ -126,7 +126,7 @@ func responseZhipu2OpenAI(response *ZhipuResponse) *dto.OpenAITextResponse { func streamResponseZhipu2OpenAI(zhipuResponse string) *dto.ChatCompletionsStreamResponse { var choice dto.ChatCompletionsStreamResponseChoice - choice.Delta.Content = zhipuResponse + choice.Delta.SetContentString(zhipuResponse) response := dto.ChatCompletionsStreamResponse{ Object: "chat.completion.chunk", Created: common.GetTimestamp(), @@ -138,7 +138,7 @@ func streamResponseZhipu2OpenAI(zhipuResponse string) *dto.ChatCompletionsStream func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*dto.ChatCompletionsStreamResponse, *dto.Usage) { var choice dto.ChatCompletionsStreamResponseChoice - choice.Delta.Content = "" + choice.Delta.SetContentString("") choice.FinishReason = &relaycommon.StopFinishReason response := dto.ChatCompletionsStreamResponse{ Id: zhipuResponse.RequestId, diff --git a/service/token_counter.go b/service/token_counter.go index 18fc5a3..29da0f2 100644 --- a/service/token_counter.go +++ b/service/token_counter.go @@ -232,7 +232,7 @@ func CountTokenInput(input any, model string, check bool) (int, error, bool) { func CountTokenStreamChoices(messages []dto.ChatCompletionsStreamResponseChoice, model string) int { tokens := 0 for _, message := range messages { - tkm, _, _ := CountTokenInput(message.Delta.Content, model, false) + tkm, _, _ := CountTokenInput(message.Delta.GetContentString(), model, false) tokens += tkm if message.Delta.ToolCalls != nil { for _, tool := range message.Delta.ToolCalls {