package controller import ( "bufio" "encoding/json" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt" "io" "net/http" "one-api/common" "strings" "sync" "time" ) // https://open.bigmodel.cn/doc/api#chatglm_std // chatglm_std, chatglm_lite // https://open.bigmodel.cn/api/paas/v3/model-api/chatglm_std/invoke // https://open.bigmodel.cn/api/paas/v3/model-api/chatglm_std/sse-invoke type ZhipuMessage struct { Role string `json:"role"` Content string `json:"content"` } type ZhipuRequest struct { Prompt []ZhipuMessage `json:"prompt"` Temperature float64 `json:"temperature,omitempty"` TopP float64 `json:"top_p,omitempty"` RequestId string `json:"request_id,omitempty"` Incremental bool `json:"incremental,omitempty"` } type ZhipuResponseData struct { TaskId string `json:"task_id"` RequestId string `json:"request_id"` TaskStatus string `json:"task_status"` Choices []ZhipuMessage `json:"choices"` Usage `json:"usage"` } type ZhipuResponse struct { Code int `json:"code"` Msg string `json:"msg"` Success bool `json:"success"` Data ZhipuResponseData `json:"data"` } type ZhipuStreamMetaResponse struct { RequestId string `json:"request_id"` TaskId string `json:"task_id"` TaskStatus string `json:"task_status"` Usage `json:"usage"` } type zhipuTokenData struct { Token string ExpiryTime time.Time } var zhipuTokens sync.Map var expSeconds int64 = 24 * 3600 func getZhipuToken(apikey string) string { data, ok := zhipuTokens.Load(apikey) if ok { tokenData := data.(zhipuTokenData) if time.Now().Before(tokenData.ExpiryTime) { return tokenData.Token } } split := strings.Split(apikey, ".") if len(split) != 2 { common.SysError("invalid zhipu key: " + apikey) return "" } id := split[0] secret := split[1] expMillis := time.Now().Add(time.Duration(expSeconds)*time.Second).UnixNano() / 1e6 expiryTime := time.Now().Add(time.Duration(expSeconds) * time.Second) timestamp := time.Now().UnixNano() / 1e6 payload := jwt.MapClaims{ "api_key": id, "exp": expMillis, "timestamp": timestamp, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) token.Header["alg"] = "HS256" token.Header["sign_type"] = "SIGN" tokenString, err := token.SignedString([]byte(secret)) if err != nil { return "" } zhipuTokens.Store(apikey, zhipuTokenData{ Token: tokenString, ExpiryTime: expiryTime, }) return tokenString } func requestOpenAI2Zhipu(request GeneralOpenAIRequest) *ZhipuRequest { messages := make([]ZhipuMessage, 0, len(request.Messages)) for _, message := range request.Messages { if message.Role == "system" { messages = append(messages, ZhipuMessage{ Role: "system", Content: message.Content, }) messages = append(messages, ZhipuMessage{ Role: "user", Content: "Okay", }) } else { messages = append(messages, ZhipuMessage{ Role: message.Role, Content: message.Content, }) } } return &ZhipuRequest{ Prompt: messages, Temperature: request.Temperature, TopP: request.TopP, Incremental: false, } } func responseZhipu2OpenAI(response *ZhipuResponse) *OpenAITextResponse { fullTextResponse := OpenAITextResponse{ Id: response.Data.TaskId, Object: "chat.completion", Created: common.GetTimestamp(), Choices: make([]OpenAITextResponseChoice, 0, len(response.Data.Choices)), Usage: response.Data.Usage, } for i, choice := range response.Data.Choices { openaiChoice := OpenAITextResponseChoice{ Index: i, Message: Message{ Role: choice.Role, Content: strings.Trim(choice.Content, "\""), }, FinishReason: "", } if i == len(response.Data.Choices)-1 { openaiChoice.FinishReason = "stop" } fullTextResponse.Choices = append(fullTextResponse.Choices, openaiChoice) } return &fullTextResponse } func streamResponseZhipu2OpenAI(zhipuResponse string) *ChatCompletionsStreamResponse { var choice ChatCompletionsStreamResponseChoice choice.Delta.Content = zhipuResponse response := ChatCompletionsStreamResponse{ Object: "chat.completion.chunk", Created: common.GetTimestamp(), Model: "chatglm", Choices: []ChatCompletionsStreamResponseChoice{choice}, } return &response } func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*ChatCompletionsStreamResponse, *Usage) { var choice ChatCompletionsStreamResponseChoice choice.Delta.Content = "" choice.FinishReason = &stopFinishReason response := ChatCompletionsStreamResponse{ Id: zhipuResponse.RequestId, Object: "chat.completion.chunk", Created: common.GetTimestamp(), Model: "chatglm", Choices: []ChatCompletionsStreamResponseChoice{choice}, } return &response, &zhipuResponse.Usage } func zhipuStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { var usage *Usage scanner := bufio.NewScanner(resp.Body) scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF && len(data) == 0 { return 0, nil, nil } if i := strings.Index(string(data), "\n\n"); i >= 0 && strings.Index(string(data), ":") >= 0 { return i + 2, data[0:i], nil } if atEOF { return len(data), data, nil } return 0, nil, nil }) dataChan := make(chan string) metaChan := make(chan string) stopChan := make(chan bool) go func() { for scanner.Scan() { data := scanner.Text() lines := strings.Split(data, "\n") for i, line := range lines { if len(line) < 5 { continue } if line[:5] == "data:" { dataChan <- line[5:] if i != len(lines)-1 { dataChan <- "\n" } } else if line[:5] == "meta:" { metaChan <- line[5:] } } } stopChan <- true }() setEventStreamHeaders(c) c.Stream(func(w io.Writer) bool { select { case data := <-dataChan: response := streamResponseZhipu2OpenAI(data) jsonResponse, err := json.Marshal(response) if err != nil { common.SysError("error marshalling stream response: " + err.Error()) return true } c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonResponse)}) return true case data := <-metaChan: var zhipuResponse ZhipuStreamMetaResponse err := json.Unmarshal([]byte(data), &zhipuResponse) if err != nil { common.SysError("error unmarshalling stream response: " + err.Error()) return true } response, zhipuUsage := streamMetaResponseZhipu2OpenAI(&zhipuResponse) jsonResponse, err := json.Marshal(response) if err != nil { common.SysError("error marshalling stream response: " + err.Error()) return true } usage = zhipuUsage c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonResponse)}) return true case <-stopChan: c.Render(-1, common.CustomEvent{Data: "data: [DONE]"}) return false } }) err := resp.Body.Close() if err != nil { return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil } return nil, usage } func zhipuHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { var zhipuResponse ZhipuResponse responseBody, err := io.ReadAll(resp.Body) if err != nil { return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil } err = resp.Body.Close() if err != nil { return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil } err = json.Unmarshal(responseBody, &zhipuResponse) if err != nil { return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil } if !zhipuResponse.Success { return &OpenAIErrorWithStatusCode{ OpenAIError: OpenAIError{ Message: zhipuResponse.Msg, Type: "zhipu_error", Param: "", Code: zhipuResponse.Code, }, StatusCode: resp.StatusCode, }, nil } fullTextResponse := responseZhipu2OpenAI(&zhipuResponse) jsonResponse, err := json.Marshal(fullTextResponse) if err != nil { return 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) return nil, &fullTextResponse.Usage }