package coze import ( "bufio" "encoding/json" "fmt" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/conv" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/common/render" "github.com/songquanpeng/one-api/relay/adaptor/coze/constant/messagetype" "github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/model" "io" "net/http" "strings" ) // https://www.coze.com/open func stopReasonCoze2OpenAI(reason *string) string { if reason == nil { return "" } switch *reason { case "end_turn": return "stop" case "stop_sequence": return "stop" case "max_tokens": return "length" default: return *reason } } func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request { cozeRequest := Request{ Stream: textRequest.Stream, User: textRequest.User, BotId: strings.TrimPrefix(textRequest.Model, "bot-"), } for i, message := range textRequest.Messages { if i == len(textRequest.Messages)-1 { cozeRequest.Query = message.StringContent() continue } cozeMessage := Message{ Role: message.Role, Content: message.StringContent(), } cozeRequest.ChatHistory = append(cozeRequest.ChatHistory, cozeMessage) } return &cozeRequest } func V3ConvertRequest(textRequest model.GeneralOpenAIRequest) *V3Request { cozeRequest := V3Request{ UserId: textRequest.User, Stream: textRequest.Stream, BotId: strings.TrimPrefix(textRequest.Model, "bot-"), } if cozeRequest.UserId == "" { cozeRequest.UserId = "any" } for i, message := range textRequest.Messages { if i == len(textRequest.Messages)-1 { cozeRequest.AdditionalMessages = append(cozeRequest.AdditionalMessages, Message{ Role: "user", Content: message.CozeV3StringContent(), }) continue } cozeMessage := Message{ Role: message.Role, Content: message.CozeV3StringContent(), } cozeRequest.AdditionalMessages = append(cozeRequest.AdditionalMessages, cozeMessage) } return &cozeRequest } func StreamResponseCoze2OpenAI(cozeResponse *StreamResponse) (*openai.ChatCompletionsStreamResponse, *Response) { var response *Response var stopReason string var choice openai.ChatCompletionsStreamResponseChoice if cozeResponse.Message != nil { if cozeResponse.Message.Type != messagetype.Answer { return nil, nil } choice.Delta.Content = cozeResponse.Message.Content } choice.Delta.Role = "assistant" finishReason := stopReasonCoze2OpenAI(&stopReason) if finishReason != "null" { choice.FinishReason = &finishReason } var openaiResponse openai.ChatCompletionsStreamResponse openaiResponse.Object = "chat.completion.chunk" openaiResponse.Choices = []openai.ChatCompletionsStreamResponseChoice{choice} openaiResponse.Id = cozeResponse.ConversationId return &openaiResponse, response } func V3StreamResponseCoze2OpenAI(cozeResponse *V3StreamResponse) (*openai.ChatCompletionsStreamResponse, *Response) { var response *Response var choice openai.ChatCompletionsStreamResponseChoice choice.Delta.Role = cozeResponse.Role choice.Delta.Content = cozeResponse.Content var openaiResponse openai.ChatCompletionsStreamResponse openaiResponse.Object = "chat.completion.chunk" openaiResponse.Choices = []openai.ChatCompletionsStreamResponseChoice{choice} openaiResponse.Id = cozeResponse.ConversationId if cozeResponse.Usage.TokenCount > 0 { openaiResponse.Usage = &model.Usage{ PromptTokens: cozeResponse.Usage.InputCount, CompletionTokens: cozeResponse.Usage.OutputCount, TotalTokens: cozeResponse.Usage.TokenCount, } } return &openaiResponse, response } func ResponseCoze2OpenAI(cozeResponse *Response) *openai.TextResponse { var responseText string for _, message := range cozeResponse.Messages { if message.Type == messagetype.Answer { responseText = message.Content break } } choice := openai.TextResponseChoice{ Index: 0, Message: model.Message{ Role: "assistant", Content: responseText, Name: nil, }, FinishReason: "stop", } fullTextResponse := openai.TextResponse{ Id: fmt.Sprintf("chatcmpl-%s", cozeResponse.ConversationId), Model: "coze-bot", Object: "chat.completion", Created: helper.GetTimestamp(), Choices: []openai.TextResponseChoice{choice}, } return &fullTextResponse } func V3ResponseCoze2OpenAI(cozeResponse *V3Response) *openai.TextResponse { choice := openai.TextResponseChoice{ Index: 0, Message: model.Message{ Role: "assistant", Content: cozeResponse.Data.Content, Name: nil, }, FinishReason: "stop", } fullTextResponse := openai.TextResponse{ Id: fmt.Sprintf("chatcmpl-%s", cozeResponse.Data.ConversationId), Model: "coze-bot", Object: "chat.completion", Created: helper.GetTimestamp(), Choices: []openai.TextResponseChoice{choice}, } return &fullTextResponse } func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *string) { var responseText string createdTime := helper.GetTimestamp() scanner := bufio.NewScanner(resp.Body) scanner.Split(bufio.ScanLines) common.SetEventStreamHeaders(c) var modelName string for scanner.Scan() { data := scanner.Text() if len(data) < 5 || !strings.HasPrefix(data, "data:") { continue } data = strings.TrimPrefix(data, "data:") data = strings.TrimSuffix(data, "\r") var cozeResponse StreamResponse err := json.Unmarshal([]byte(data), &cozeResponse) if err != nil { logger.SysError("error unmarshalling stream response: " + err.Error()) continue } response, _ := StreamResponseCoze2OpenAI(&cozeResponse) if response == nil { continue } for _, choice := range response.Choices { responseText += conv.AsString(choice.Delta.Content) } response.Model = modelName response.Created = createdTime err = render.ObjectData(c, response) if err != nil { logger.SysError(err.Error()) } } if err := scanner.Err(); err != nil { logger.SysError("error reading stream: " + err.Error()) } render.Done(c) err := resp.Body.Close() if err != nil { return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil } return nil, &responseText } func V3StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *string) { var responseText string createdTime := helper.GetTimestamp() scanner := bufio.NewScanner(resp.Body) scanner.Split(splitOnDoubleNewline) common.SetEventStreamHeaders(c) var modelName string for scanner.Scan() { part := scanner.Text() part = strings.TrimPrefix(part, "\n") parts := strings.Split(part, "\n") if len(parts) != 2 { continue } if !strings.HasPrefix(parts[0], "event:") || !strings.HasPrefix(parts[1], "data:") { continue } event, data := strings.TrimSpace(parts[0][6:]), strings.TrimSpace(parts[1][5:]) if event == "conversation.message.delta" || event == "conversation.chat.completed" { data = strings.TrimSuffix(data, "\r") var cozeResponse V3StreamResponse err := json.Unmarshal([]byte(data), &cozeResponse) if err != nil { logger.SysError("error unmarshalling stream response: " + err.Error()) continue } response, _ := V3StreamResponseCoze2OpenAI(&cozeResponse) if response == nil { continue } for _, choice := range response.Choices { responseText += conv.AsString(choice.Delta.Content) } response.Model = modelName response.Created = createdTime err = render.ObjectData(c, response) if err != nil { logger.SysError(err.Error()) } } } if err := scanner.Err(); err != nil { logger.SysError("error reading stream: " + err.Error()) } render.Done(c) err := resp.Body.Close() if err != nil { return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil } return nil, &responseText } func Handler(c *gin.Context, resp *http.Response, promptTokens int, modelName string) (*model.ErrorWithStatusCode, *string) { responseBody, err := io.ReadAll(resp.Body) if err != nil { return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil } err = resp.Body.Close() if err != nil { return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil } var cozeResponse Response err = json.Unmarshal(responseBody, &cozeResponse) if err != nil { return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil } if cozeResponse.Code != 0 { return &model.ErrorWithStatusCode{ Error: model.Error{ Message: cozeResponse.Msg, Code: cozeResponse.Code, }, StatusCode: resp.StatusCode, }, nil } fullTextResponse := ResponseCoze2OpenAI(&cozeResponse) fullTextResponse.Model = modelName jsonResponse, err := json.Marshal(fullTextResponse) if err != nil { return openai.ErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil } c.Writer.Header().Set("Content-Type", "application/json") c.Writer.WriteHeader(resp.StatusCode) _, err = c.Writer.Write(jsonResponse) var responseText string if len(fullTextResponse.Choices) > 0 { responseText = fullTextResponse.Choices[0].Message.StringContent() } return nil, &responseText } func V3Handler(c *gin.Context, resp *http.Response, promptTokens int, modelName string) (*model.ErrorWithStatusCode, *string) { responseBody, err := io.ReadAll(resp.Body) if err != nil { return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil } err = resp.Body.Close() if err != nil { return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil } var cozeResponse V3Response err = json.Unmarshal(responseBody, &cozeResponse) if err != nil { return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil } if cozeResponse.Code != 0 { return &model.ErrorWithStatusCode{ Error: model.Error{ Message: cozeResponse.Msg, Code: cozeResponse.Code, }, StatusCode: resp.StatusCode, }, nil } fullTextResponse := V3ResponseCoze2OpenAI(&cozeResponse) fullTextResponse.Model = modelName jsonResponse, err := json.Marshal(fullTextResponse) if err != nil { return openai.ErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil } c.Writer.Header().Set("Content-Type", "application/json") c.Writer.WriteHeader(resp.StatusCode) _, err = c.Writer.Write(jsonResponse) var responseText string if len(fullTextResponse.Choices) > 0 { responseText = fullTextResponse.Choices[0].Message.StringContent() } return nil, &responseText }