merge upstream

Signed-off-by: wozulong <>
This commit is contained in:
wozulong 2024-04-28 14:04:19 +08:00
commit a7bafec1bf
15 changed files with 86 additions and 37 deletions

View File

@ -2,6 +2,7 @@ package common
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io" "io"
@ -99,6 +100,7 @@ func LogQuota(quota int) string {
} }
} }
func LogQuotaF(quota float64) string { func LogQuotaF(quota float64) string {
if DisplayInCurrencyEnabled { if DisplayInCurrencyEnabled {
return fmt.Sprintf("%.6f 额度", quota/QuotaPerUnit) return fmt.Sprintf("%.6f 额度", quota/QuotaPerUnit)
@ -106,3 +108,14 @@ func LogQuotaF(quota float64) string {
return fmt.Sprintf("%d 点额度", int64(quota)) 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)))
}

View File

@ -124,7 +124,7 @@ func shouldRetry(c *gin.Context, channelId int, openaiErr *dto.OpenAIErrorWithSt
func processChannelError(c *gin.Context, channelId int, err *dto.OpenAIErrorWithStatusCode) { func processChannelError(c *gin.Context, channelId int, err *dto.OpenAIErrorWithStatusCode) {
autoBan := c.GetBool("auto_ban") 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 { if service.ShouldDisableChannel(&err.Error, err.StatusCode) && autoBan {
channelName := c.GetString("channel_name") channelName := c.GetString("channel_name")
service.DisableChannel(channelId, channelName, err.Error.Message) service.DisableChannel(channelId, channelName, err.Error.Message)
@ -160,7 +160,7 @@ func RelayMidjourney(c *gin.Context) {
"code": err.Code, "code": err.Code,
}) })
channelId := c.GetInt("channel_id") 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)))
} }
} }

View File

@ -54,17 +54,33 @@ type OpenAIEmbeddingResponse struct {
} }
type ChatCompletionsStreamResponseChoice struct { type ChatCompletionsStreamResponseChoice struct {
Delta ChatCompletionsStreamResponseChoiceDelta `json:"delta"` Delta ChatCompletionsStreamResponseChoiceDelta `json:"delta,omitempty"`
FinishReason *string `json:"finish_reason,omitempty"` Logprobs *any `json:"logprobs"`
Index int `json:"index,omitempty"` FinishReason *string `json:"finish_reason"`
Index int `json:"index"`
} }
type ChatCompletionsStreamResponseChoiceDelta struct { type ChatCompletionsStreamResponseChoiceDelta struct {
Content string `json:"content"` Content *string `json:"content,omitempty"`
Role string `json:"role,omitempty"` Role string `json:"role,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,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 { type ToolCall struct {
// Index is not nil only in chat completion chunk object // Index is not nil only in chat completion chunk object
Index *int `json:"index,omitempty"` Index *int `json:"index,omitempty"`
@ -80,11 +96,12 @@ type FunctionCall struct {
} }
type ChatCompletionsStreamResponse struct { type ChatCompletionsStreamResponse struct {
Id string `json:"id"` Id string `json:"id"`
Object string `json:"object"` Object string `json:"object"`
Created int64 `json:"created"` Created int64 `json:"created"`
Model string `json:"model"` Model string `json:"model"`
Choices []ChatCompletionsStreamResponseChoice `json:"choices"` SystemFingerprint *string `json:"system_fingerprint"`
Choices []ChatCompletionsStreamResponseChoice `json:"choices"`
} }
type ChatCompletionsStreamResponseSimple struct { type ChatCompletionsStreamResponseSimple struct {

View File

@ -136,7 +136,7 @@ func responseAli2OpenAI(response *AliChatResponse) *dto.OpenAITextResponse {
func streamResponseAli2OpenAI(aliResponse *AliChatResponse) *dto.ChatCompletionsStreamResponse { func streamResponseAli2OpenAI(aliResponse *AliChatResponse) *dto.ChatCompletionsStreamResponse {
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
choice.Delta.Content = aliResponse.Output.Text choice.Delta.SetContentString(aliResponse.Output.Text)
if aliResponse.Output.FinishReason != "null" { if aliResponse.Output.FinishReason != "null" {
finishReason := aliResponse.Output.FinishReason finishReason := aliResponse.Output.FinishReason
choice.FinishReason = &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 usage.TotalTokens = aliResponse.Usage.InputTokens + aliResponse.Usage.OutputTokens
} }
response := streamResponseAli2OpenAI(&aliResponse) 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 lastResponseText = aliResponse.Output.Text
jsonResponse, err := json.Marshal(response) jsonResponse, err := json.Marshal(response)
if err != nil { if err != nil {

View File

@ -156,6 +156,7 @@ func awsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, requestMode i
var usage relaymodel.Usage var usage relaymodel.Usage
var id string var id string
var model string var model string
createdTime := common.GetTimestamp()
c.Stream(func(w io.Writer) bool { c.Stream(func(w io.Writer) bool {
event, ok := <-stream.Events() event, ok := <-stream.Events()
if !ok { if !ok {
@ -188,6 +189,7 @@ func awsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, requestMode i
if response.Model != "" { if response.Model != "" {
model = response.Model model = response.Model
} }
response.Created = createdTime
response.Id = id response.Id = id
response.Model = model response.Model = model

View File

@ -57,7 +57,7 @@ func responseBaidu2OpenAI(response *BaiduChatResponse) *dto.OpenAITextResponse {
func streamResponseBaidu2OpenAI(baiduResponse *BaiduChatStreamResponse) *dto.ChatCompletionsStreamResponse { func streamResponseBaidu2OpenAI(baiduResponse *BaiduChatStreamResponse) *dto.ChatCompletionsStreamResponse {
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
choice.Delta.Content = baiduResponse.Result choice.Delta.SetContentString(baiduResponse.Result)
if baiduResponse.IsEnd { if baiduResponse.IsEnd {
choice.FinishReason = &relaycommon.StopFinishReason choice.FinishReason = &relaycommon.StopFinishReason
} }

View File

@ -73,13 +73,13 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR
formatMessages := make([]dto.Message, 0) formatMessages := make([]dto.Message, 0)
var lastMessage *dto.Message var lastMessage *dto.Message
for i, message := range textRequest.Messages { for i, message := range textRequest.Messages {
if message.Role == "system" { //if message.Role == "system" {
if i != 0 { // if i != 0 {
message.Role = "user" // message.Role = "user"
} // }
} //}
if message.Role == "" { if message.Role == "" {
message.Role = "user" textRequest.Messages[i].Role = "user"
} }
fmtMessage := dto.Message{ fmtMessage := dto.Message{
Role: message.Role, Role: message.Role,
@ -98,13 +98,24 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR
fmtMessage.Content = content fmtMessage.Content = content
} }
formatMessages = append(formatMessages, fmtMessage) formatMessages = append(formatMessages, fmtMessage)
lastMessage = &message lastMessage = &textRequest.Messages[i]
} }
claudeMessages := make([]ClaudeMessage, 0) claudeMessages := make([]ClaudeMessage, 0)
for _, message := range formatMessages { for _, message := range formatMessages {
if message.Role == "system" { 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 { } else {
claudeMessage := ClaudeMessage{ claudeMessage := ClaudeMessage{
Role: message.Role, Role: message.Role,
@ -149,7 +160,6 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR
} }
claudeRequest.Prompt = "" claudeRequest.Prompt = ""
claudeRequest.Messages = claudeMessages claudeRequest.Messages = claudeMessages
return &claudeRequest, nil return &claudeRequest, nil
} }
@ -161,7 +171,7 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (*
response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0) response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0)
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
if reqMode == RequestModeCompletion { if reqMode == RequestModeCompletion {
choice.Delta.Content = claudeResponse.Completion choice.Delta.SetContentString(claudeResponse.Completion)
finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason) finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason)
if finishReason != "null" { if finishReason != "null" {
choice.FinishReason = &finishReason choice.FinishReason = &finishReason
@ -171,9 +181,13 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (*
response.Id = claudeResponse.Message.Id response.Id = claudeResponse.Message.Id
response.Model = claudeResponse.Message.Model response.Model = claudeResponse.Message.Model
claudeUsage = &claudeResponse.Message.Usage 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" { } else if claudeResponse.Type == "content_block_delta" {
choice.Index = claudeResponse.Index choice.Index = claudeResponse.Index
choice.Delta.Content = claudeResponse.Delta.Text choice.Delta.SetContentString(claudeResponse.Delta.Text)
} else if claudeResponse.Type == "message_delta" { } else if claudeResponse.Type == "message_delta" {
finishReason := stopReasonClaude2OpenAI(*claudeResponse.Delta.StopReason) finishReason := stopReasonClaude2OpenAI(*claudeResponse.Delta.StopReason)
if finishReason != "null" { if finishReason != "null" {
@ -182,12 +196,15 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (*
claudeUsage = &claudeResponse.Usage claudeUsage = &claudeResponse.Usage
} else if claudeResponse.Type == "message_stop" { } else if claudeResponse.Type == "message_stop" {
return nil, nil return nil, nil
} else {
return nil, nil
} }
} }
if claudeUsage == nil { if claudeUsage == nil {
claudeUsage = &ClaudeUsage{} claudeUsage = &ClaudeUsage{}
} }
response.Choices = append(response.Choices, choice) response.Choices = append(response.Choices, choice)
return &response, claudeUsage return &response, claudeUsage
} }

View File

@ -117,7 +117,7 @@ func cohereStreamHandler(c *gin.Context, resp *http.Response, modelName string,
{ {
Delta: dto.ChatCompletionsStreamResponseChoiceDelta{ Delta: dto.ChatCompletionsStreamResponseChoiceDelta{
Role: "assistant", Role: "assistant",
Content: cohereResp.Text, Content: &cohereResp.Text,
}, },
Index: 0, Index: 0,
}, },

View File

@ -151,7 +151,7 @@ func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResp
func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) *dto.ChatCompletionsStreamResponse { func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) *dto.ChatCompletionsStreamResponse {
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
choice.Delta.Content = geminiResponse.GetResponseText() choice.Delta.SetContentString(geminiResponse.GetResponseText())
choice.FinishReason = &relaycommon.StopFinishReason choice.FinishReason = &relaycommon.StopFinishReason
var response dto.ChatCompletionsStreamResponse var response dto.ChatCompletionsStreamResponse
response.Object = "chat.completion.chunk" 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) err := json.Unmarshal([]byte(data), &dummy)
responseText += dummy.Content responseText += dummy.Content
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
choice.Delta.Content = dummy.Content choice.Delta.SetContentString(dummy.Content)
response := dto.ChatCompletionsStreamResponse{ response := dto.ChatCompletionsStreamResponse{
Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()), Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
Object: "chat.completion.chunk", Object: "chat.completion.chunk",

View File

@ -68,7 +68,7 @@ func OpenaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*d
err := json.Unmarshal(common.StringToByteSlice(item), &streamResponse) err := json.Unmarshal(common.StringToByteSlice(item), &streamResponse)
if err == nil { if err == nil {
for _, choice := range streamResponse.Choices { for _, choice := range streamResponse.Choices {
responseTextBuilder.WriteString(choice.Delta.Content) responseTextBuilder.WriteString(choice.Delta.GetContentString())
if choice.Delta.ToolCalls != nil { if choice.Delta.ToolCalls != nil {
if len(choice.Delta.ToolCalls) > toolCount { if len(choice.Delta.ToolCalls) > toolCount {
toolCount = len(choice.Delta.ToolCalls) toolCount = len(choice.Delta.ToolCalls)
@ -84,7 +84,7 @@ func OpenaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*d
} else { } else {
for _, streamResponse := range streamResponses { for _, streamResponse := range streamResponses {
for _, choice := range streamResponse.Choices { for _, choice := range streamResponse.Choices {
responseTextBuilder.WriteString(choice.Delta.Content) responseTextBuilder.WriteString(choice.Delta.GetContentString())
if choice.Delta.ToolCalls != nil { if choice.Delta.ToolCalls != nil {
if len(choice.Delta.ToolCalls) > toolCount { if len(choice.Delta.ToolCalls) > toolCount {
toolCount = len(choice.Delta.ToolCalls) toolCount = len(choice.Delta.ToolCalls)

View File

@ -61,7 +61,7 @@ func responsePaLM2OpenAI(response *PaLMChatResponse) *dto.OpenAITextResponse {
func streamResponsePaLM2OpenAI(palmResponse *PaLMChatResponse) *dto.ChatCompletionsStreamResponse { func streamResponsePaLM2OpenAI(palmResponse *PaLMChatResponse) *dto.ChatCompletionsStreamResponse {
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
if len(palmResponse.Candidates) > 0 { if len(palmResponse.Candidates) > 0 {
choice.Delta.Content = palmResponse.Candidates[0].Content choice.Delta.SetContentString(palmResponse.Candidates[0].Content)
} }
choice.FinishReason = &relaycommon.StopFinishReason choice.FinishReason = &relaycommon.StopFinishReason
var response dto.ChatCompletionsStreamResponse var response dto.ChatCompletionsStreamResponse

View File

@ -86,7 +86,7 @@ func streamResponseTencent2OpenAI(TencentResponse *TencentChatResponse) *dto.Cha
} }
if len(TencentResponse.Choices) > 0 { if len(TencentResponse.Choices) > 0 {
var choice dto.ChatCompletionsStreamResponseChoice 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" { if TencentResponse.Choices[0].FinishReason == "stop" {
choice.FinishReason = &relaycommon.StopFinishReason choice.FinishReason = &relaycommon.StopFinishReason
} }
@ -138,7 +138,7 @@ func tencentStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIError
} }
response := streamResponseTencent2OpenAI(&TencentResponse) response := streamResponseTencent2OpenAI(&TencentResponse)
if len(response.Choices) != 0 { if len(response.Choices) != 0 {
responseText += response.Choices[0].Delta.Content responseText += response.Choices[0].Delta.GetContentString()
} }
jsonResponse, err := json.Marshal(response) jsonResponse, err := json.Marshal(response)
if err != nil { if err != nil {

View File

@ -87,7 +87,7 @@ func streamResponseXunfei2OpenAI(xunfeiResponse *XunfeiChatResponse) *dto.ChatCo
} }
} }
var choice dto.ChatCompletionsStreamResponseChoice 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 { if xunfeiResponse.Payload.Choices.Status == 2 {
choice.FinishReason = &relaycommon.StopFinishReason choice.FinishReason = &relaycommon.StopFinishReason
} }

View File

@ -126,7 +126,7 @@ func responseZhipu2OpenAI(response *ZhipuResponse) *dto.OpenAITextResponse {
func streamResponseZhipu2OpenAI(zhipuResponse string) *dto.ChatCompletionsStreamResponse { func streamResponseZhipu2OpenAI(zhipuResponse string) *dto.ChatCompletionsStreamResponse {
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
choice.Delta.Content = zhipuResponse choice.Delta.SetContentString(zhipuResponse)
response := dto.ChatCompletionsStreamResponse{ response := dto.ChatCompletionsStreamResponse{
Object: "chat.completion.chunk", Object: "chat.completion.chunk",
Created: common.GetTimestamp(), Created: common.GetTimestamp(),
@ -138,7 +138,7 @@ func streamResponseZhipu2OpenAI(zhipuResponse string) *dto.ChatCompletionsStream
func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*dto.ChatCompletionsStreamResponse, *dto.Usage) { func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*dto.ChatCompletionsStreamResponse, *dto.Usage) {
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
choice.Delta.Content = "" choice.Delta.SetContentString("")
choice.FinishReason = &relaycommon.StopFinishReason choice.FinishReason = &relaycommon.StopFinishReason
response := dto.ChatCompletionsStreamResponse{ response := dto.ChatCompletionsStreamResponse{
Id: zhipuResponse.RequestId, Id: zhipuResponse.RequestId,

View File

@ -232,7 +232,7 @@ func CountTokenInput(input any, model string, check bool) (int, error, bool) {
func CountTokenStreamChoices(messages []dto.ChatCompletionsStreamResponseChoice, model string) int { func CountTokenStreamChoices(messages []dto.ChatCompletionsStreamResponseChoice, model string) int {
tokens := 0 tokens := 0
for _, message := range messages { for _, message := range messages {
tkm, _, _ := CountTokenInput(message.Delta.Content, model, false) tkm, _, _ := CountTokenInput(message.Delta.GetContentString(), model, false)
tokens += tkm tokens += tkm
if message.Delta.ToolCalls != nil { if message.Delta.ToolCalls != nil {
for _, tool := range message.Delta.ToolCalls { for _, tool := range message.Delta.ToolCalls {