diff --git a/apis/record/infer.go b/apis/record/infer.go index 3c1ae86..20f25b9 100644 --- a/apis/record/infer.go +++ b/apis/record/infer.go @@ -1,880 +1,658 @@ -package record - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net/http" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/sashabaranov/go-openai" - - "MOSS_backend/config" - . "MOSS_backend/models" - . "MOSS_backend/utils" - "MOSS_backend/utils/sensitive" - "MOSS_backend/utils/tools" - - "github.com/gofiber/websocket/v2" - "github.com/google/uuid" - "go.uber.org/zap" -) - -type InferResponseModel struct { - Status int `json:"status"` // 1 for output, 0 for end, -1 for error, -2 for sensitive - StatusCode int `json:"status_code,omitempty"` - Output string `json:"output,omitempty"` - Stage string `json:"stage,omitempty"` -} - -type responseChannel struct { - ch chan InferResponseModel - closed atomic.Bool -} - -var InferResponseChannel sync.Map - -var inferHttpClient = http.Client{Timeout: 120 * time.Second} - -type InferWsContext struct { - c *websocket.Conn - connectionClosed *atomic.Bool -} - -func InferOpenAI( - record *Record, - postRecord RecordModels, - model *ModelConfig, - user *User, - ctx *InferWsContext, -) ( - err error, -) { - defer func() { - if v := recover(); v != nil { - Logger.Error("infer openai panicked", zap.Any("error", v)) - err = unknownError - } - }() - - openaiConfig := openai.DefaultConfig("") - openaiConfig.BaseURL = model.Url - client := openai.NewClientWithConfig(openaiConfig) - - var messages = make([]openai.ChatCompletionMessage, 0, len(postRecord)+2) - messages = append(messages, openai.ChatCompletionMessage{ - Role: "system", - Content: model.OpenAISystemPrompt, - }) - messages = append(messages, postRecord.ToOpenAIMessages()...) - messages = append(messages, openai.ChatCompletionMessage{ - Role: "user", - Content: record.Request, - }) - request := openai.ChatCompletionRequest{ - Model: model.OpenAIModelName, - Messages: messages, - Stop: []string{model.EndDelimiter}, - } - - if ctx == nil { - // openai client may panic when status code is 400 - response, err := client.CreateChatCompletion( - context.Background(), - request, - ) - if err != nil { - return err - } - - if len(response.Choices) == 0 { - return unknownError - } - - record.Response = response.Choices[0].Message.Content - } else { - // streaming - if config.Config.Debug { - Logger.Info("openai streaming", - zap.String("model", model.OpenAIModelName), - zap.String("url", model.Url), - ) - } - - stream, err := client.CreateChatCompletionStream( - context.Background(), - request, - ) - if err != nil { - return err - } - defer stream.Close() - - startTime := time.Now() - - var resultBuilder strings.Builder - var nowOutput string - var detectedOutput string - - for { - if ctx.connectionClosed.Load() { - return interruptError - } - response, err := stream.Recv() - if errors.Is(err, io.EOF) { - break - } - if err != nil { - return err - } - - if len(response.Choices) == 0 { - return unknownError - } - - resultBuilder.WriteString(response.Choices[0].Delta.Content) - nowOutput = resultBuilder.String() - - if model.EndDelimiter != "" && strings.Contains(nowOutput, model.EndDelimiter) { - nowOutput = strings.Split(nowOutput, model.EndDelimiter)[0] - break - } - - before, _, found := CutLastAny(nowOutput, ",.?!\n,。?!") - if !found || before == detectedOutput { - continue - } - detectedOutput = before - if model.EnableSensitiveCheck { - err = sensitiveCheck(ctx.c, record, detectedOutput, startTime, user) - if err != nil { - return err - } - } - - _ = ctx.c.WriteJSON(InferResponseModel{ - Status: 1, - Output: detectedOutput, - Stage: "MOSS", - }) - } - if nowOutput != detectedOutput { - if model.EnableSensitiveCheck { - err = sensitiveCheck(ctx.c, record, nowOutput, startTime, user) - if err != nil { - return err - } - } - - _ = ctx.c.WriteJSON(InferResponseModel{ - Status: 1, - Output: nowOutput, - Stage: "MOSS", - }) - } - - record.Response = nowOutput - record.Duration = float64(time.Since(startTime)) / 1000_000_000 - _ = ctx.c.WriteJSON(InferResponseModel{ - Status: 0, - Output: nowOutput, - Stage: "MOSS", - }) - } - - return nil -} - -func InferCommon( - record *Record, - prefix string, - postRecords RecordModels, - user *User, - param map[string]float64, - ctx *InferWsContext, -) ( - err error, -) { - // metrics - userInferRequestOnFlight.Inc() - defer userInferRequestOnFlight.Dec() - - // load model config - modelID := user.ModelID - if modelID == 0 { - modelID = config.Config.DefaultModelID - } - model, err := LoadModelConfigByID(user.ModelID) - if err != nil { - model, err = LoadModelConfigByID(config.Config.DefaultModelID) - if err != nil { - return err - } - modelID = config.Config.DefaultModelID - } - - // dispatch - if model.APIType == APITypeOpenAI { - return InferOpenAI(record, postRecords, model, user, ctx) - } else { - return InferMOSS(record, prefix, user, model, param, ctx) - } -} - -func InferMOSS( - record *Record, - prefix string, - user *User, - model *ModelConfig, - param map[string]float64, - ctx *InferWsContext, -) ( - err error, -) { - var ( - innerErr error - request = map[string]any{} - uuidText string - wg sync.WaitGroup - pluginConfig = map[string]bool{} - rawContentBuilder strings.Builder - ) - - if model == nil { - return errors.New("model is nil") - } - - // load params from db - err = LoadParamToMap(request) - if err != nil { - return err - } - - for key, value := range param { - request[key] = value - } - - // session_id - request["session_id"] = record.ChatID - - // load user plugin config, if not exist, fill with default - for key, value := range model.DefaultPluginConfig { - if v, ok := user.PluginConfig[key]; ok { - request[key] = v && value - pluginConfig[key] = v && value - } else { - request[key] = false - pluginConfig[key] = false - } - } - - // prefix replace - cleanedPrefix := resultsRegexp.ReplaceAllString(prefix, "<|Results|>: None") - - /* first infer */ - // generate request - input := mossSpecialTokenRegexp.ReplaceAllString(record.Request, " ") // replace special token - firstFormattedInput := fmt.Sprintf("<|Human|>: %s\n", input) - request["x"] = fmt.Sprintf( - "%s%s<|Inner Thoughts|>:", - cleanedPrefix, - firstFormattedInput, // <|Human|>: xxx\n - ) - - if ctx != nil { - uuidText = uuid.NewString() - - // start a new(fake) listener - go func() { - _ = inferListener(record, uuidText, user, *ctx, "Inner Thoughts") - }() - - request["url"] = model.CallbackUrl + "?uuid=" + uuidText - } - - // construct data to send - data, _ := json.Marshal(request) - inferTriggerResults, err := inferTrigger(data, model.Url) // block here - if err != nil { - return err - } - - /* middle process */ - // check if first output is valid - firstFormattedNewGenerations := "<|Inner Thoughts|>:" + inferTriggerResults.NewGeneration - if !firstGenerationsFormatRegexp.MatchString(firstFormattedNewGenerations) { - Logger.Error( - "error format first output", - zap.String("new_generations", firstFormattedNewGenerations), - ) - return unknownError - } - - // replace invalid <|Commands|> to - commandsOutputSlice := commandsRegexp.FindStringSubmatch(firstFormattedNewGenerations) - if len(commandsOutputSlice) != 3 { - Logger.Error("error format first output", zap.String("new_generations", firstFormattedNewGenerations)) - return unknownError - } else if commandsOutputSlice[2] != "" { // replace <|Commands|> to - Logger.Error( - "error <|Commands|> not end with ", - zap.String("new_generations", firstFormattedNewGenerations), - ) - firstFormattedNewGenerations = commandsRegexp.ReplaceAllString(firstFormattedNewGenerations, "<|Commands|>:$1") - } - - // replace invalid <|Inner Thoughts|> to - innerThoughtsOutputSlice := innerThoughtsRegexp.FindStringSubmatch(firstFormattedNewGenerations) - if len(innerThoughtsOutputSlice) != 3 { - Logger.Error("error format first output", zap.String("new_generations", firstFormattedNewGenerations)) - return unknownError - } else if innerThoughtsOutputSlice[2] != "" { - Logger.Error( - "error <|Inner Thoughts|> not end with ", - zap.String("new_generations", firstFormattedNewGenerations), - ) - firstFormattedNewGenerations = innerThoughtsRegexp.ReplaceAllString(firstFormattedNewGenerations, "<|Inner Thoughts|>:$1") - } - // get first output Commands and InnerThoughts - rawInnerThoughts := strings.Trim(innerThoughtsOutputSlice[1], " ") - rawCommand := strings.Trim(commandsOutputSlice[1], " ") - - // get results from tools - var results *tools.ResultTotalModel - var newCommandString string - if ctx != nil { - results, newCommandString, err = tools.Execute(ctx.c, rawCommand, pluginConfig) - } else { - results, newCommandString, err = tools.Execute(nil, rawCommand, pluginConfig) - } - - // invalid commands output => log & replace inner thoughts - if err != nil { - if errors.Is(err, tools.ErrInvalidCommandFormat) { - Logger.Error( - `error commands format`, - zap.String("command", rawCommand), - ) - } - if model.InnerThoughtsPostprocess { - firstFormattedNewGenerations = innerThoughtsRegexp.ReplaceAllString(firstFormattedNewGenerations, "<|Inner Thoughts|>: None") - rawInnerThoughts = "None" - } - } - // valid/invalid commands output replace <|Commands|> - firstFormattedNewGenerations = commandsRegexp.ReplaceAllString( - firstFormattedNewGenerations, - "<|Commands|>: "+newCommandString+"", - ) // there is a space after colon - - if ctx != nil && ctx.connectionClosed.Load() { - return interruptError - } - - /* second infer */ - - // generate new formatted text and uuid - uuidText = strings.ReplaceAll(uuid.NewString(), "-", "") - var secondFormattedInput string - if results.Result == "None" { - secondFormattedInput = fmt.Sprintf("<|Results|>: %s\n", results.Result) - } else { - secondFormattedInput = fmt.Sprintf("<|Results|>:\n%s\n", results.Result) - } - request["x"] = fmt.Sprintf( - "%s%s%s\n%s<|MOSS|>:", - cleanedPrefix, // context - firstFormattedInput, // <|Human|>: xxx\n - firstFormattedNewGenerations, // <|Inner Thoughts|>: xxx\n<|Commands|>: xxx - secondFormattedInput, // <|Results|>: xxx\n - ) - - if ctx != nil { - wg.Add(1) - // start a new listener - go func() { - innerErr = inferListener(record, uuidText, user, *ctx, "MOSS") - wg.Done() - }() - - request["url"] = model.CallbackUrl + "?uuid=" + uuidText - } - - // infer - data, _ = json.Marshal(request) - inferTriggerResults, err = inferTrigger(data, model.Url) - if err != nil { - return err - } - - if ctx != nil { - wg.Wait() - if innerErr != nil { - return innerErr - } - if ctx.connectionClosed.Load() { - return interruptError - } - } - - // second output check format - secondFormattedNewGenerations := "<|MOSS|>:" + inferTriggerResults.NewGeneration - if !secondGenerationsFormatRegexp.MatchString(secondFormattedNewGenerations) { - Logger.Error( - "error format second output", - zap.String("new_generations", secondFormattedNewGenerations), - ) - return unknownError - } - - // replace invalid <|MOSS|> to - mossOutputSlice := mossRegexp.FindStringSubmatch(secondFormattedNewGenerations) - if len(mossOutputSlice) != 3 { - Logger.Error("error format second output", zap.String("new_generations", secondFormattedNewGenerations)) - return unknownError - } else if mossOutputSlice[2] != "" { - Logger.Error( - "error <|MOSS|> not end with ", - zap.String("new_generations", secondFormattedNewGenerations), - ) - secondFormattedNewGenerations = mossRegexp.ReplaceAllString(secondFormattedNewGenerations, "<|MOSS|>:$1") - } - - // save to record - record.Prefix = inferTriggerResults.Output + "\n" // save record prefix for next inference - record.Response = strings.Trim(mossOutputSlice[1], " ") - record.Duration = inferTriggerResults.Duration - record.ExtraData = results.ExtraData - record.ProcessedExtraData = results.ProcessedExtraData - record.InnerThoughts = rawInnerThoughts - - rawContentBuilder.WriteString(firstFormattedInput) - rawContentBuilder.WriteString(firstFormattedNewGenerations) - rawContentBuilder.WriteString("\n") - rawContentBuilder.WriteString(secondFormattedInput) - rawContentBuilder.WriteString(secondFormattedNewGenerations) - rawContentBuilder.WriteString("\n") - record.RawContent = rawContentBuilder.String() - // end - if ctx != nil { - err = ctx.c.WriteJSON(InferResponseModel{Status: 0}) - if err != nil { - return fmt.Errorf("write end status error: %v", err) - } - } - return nil -} - -func Infer( - record *Record, - prefix string, - postRecord RecordModels, - user *User, - param map[string]float64, -) ( - err error, -) { - return InferCommon( - record, - prefix, - postRecord, - user, - param, - nil, - ) -} - -func InferAsync( - c *websocket.Conn, - prefix string, - record *Record, - postRecord RecordModels, - user *User, - param map[string]float64, -) ( - err error, -) { - var ( - interruptChan = make(chan any) // frontend interrupt channel - connectionClosed = new(atomic.Bool) // connection closed flag - errChan = make(chan error) // error transmission channel - successChan = make(chan any) // success infer flag - ) - connectionClosed.Store(false) // initialize - defer connectionClosed.Store(true) // if this closed, stop all goroutines - - // wait for interrupt - go interrupt( - c, - interruptChan, - connectionClosed, - ) - - // wait for infer - go func() { - innerErr := InferCommon( - record, - prefix, - postRecord, - user, - param, - &InferWsContext{ - c: c, - connectionClosed: connectionClosed, - }, - ) - if innerErr != nil { - errChan <- innerErr - } else { - close(successChan) - } - }() - - for { - select { - case <-interruptChan: - return NoStatus("client interrupt") - case err = <-errChan: - return err - case <-successChan: - return nil - } - } -} - -// inferListener listen from output channel -func inferListener( - record *Record, - uuidText string, - user *User, - ctx InferWsContext, - stage string, -) error { - var err error - - // make store channel into map - outputChan := make(chan InferResponseModel, 100) - responseCh := &responseChannel{ch: outputChan} - InferResponseChannel.Store(uuidText, responseCh) - defer func() { - InferResponseChannel.Delete(uuidText) - }() - - startTime := time.Now() - var inferListenerTimeLimit = 90 * time.Second - var timer = time.NewTimer(inferListenerTimeLimit) - - var nowOutput string - var detectedOutput string - - for { - if ctx.connectionClosed.Load() { - return nil - } - if responseCh.closed.Load() { - return InternalServerError() - } - select { - case response := <-outputChan: - timer.Reset(inferListenerTimeLimit) - if config.Config.Debug { - log.Println("receive response from output channel") - log.Println(response) - } - // send only if stage "MOSS" - if stage != "MOSS" { - continue - } - switch response.Status { - case 1: // ok - if config.Config.Debug { - log.Printf("receive response from output channal: %v\nsensitive checking\n", response.Output) - } - - nowOutput = response.Output - before, _, found := CutLastAny(nowOutput, ",.?!\n,。?!") - if !found || before == detectedOutput { - continue - } - detectedOutput = before - - err = sensitiveCheck(ctx.c, record, detectedOutput, startTime, user) - if err != nil { - return err - } - - _ = ctx.c.WriteJSON(InferResponseModel{ - Status: 1, - Output: detectedOutput, - Stage: stage, - }) - case 0: // end - if nowOutput != detectedOutput { - err = sensitiveCheck(ctx.c, record, nowOutput, startTime, user) - if err != nil { - return err - } - _ = ctx.c.WriteJSON(InferResponseModel{ - Status: 1, - Output: nowOutput, - Stage: stage, - }) - } - return nil - case -1: // error - return InternalServerError(response.Output) - } - case <-timer.C: - return InternalServerError("Internal Server Timeout") - } - } -} - -func sensitiveCheck( - c *websocket.Conn, - record *Record, - output string, - startTime time.Time, - user *User, -) error { - if config.Config.Debug { - Logger.Info("sensitive checking", zap.String("output", output)) - } - - if sensitive.IsSensitive(output, user) { - record.ResponseSensitive = true - // log new record - record.Response = output - record.Duration = float64(time.Since(startTime)) / 1000_000_000 - - banned, err := user.AddUserOffense(UserOffenseMoss) - if err != nil { - return err - } - - var outputMessage string - if banned { - outputMessage = OffenseMessage - } else { - outputMessage = DefaultResponse - } - - _ = c.WriteJSON(InferResponseModel{ - Status: -2, // banned - Output: outputMessage, - }) - - // if sensitive, jump out and record - return ErrSensitive - } - return nil -} - -type InferTriggerResponse struct { - Output string `json:"output"` - NewGeneration string `json:"new_generation"` - Duration float64 `json:"duration"` -} - -func inferTrigger(data []byte, inferUrl string) (i *InferTriggerResponse, err error) { - - var statusCode int - // metrics - inferOnFlightCounter.Inc() - defer func() { - inferOnFlightCounter.Dec() - inferStatusCounter.WithLabelValues(strconv.Itoa(statusCode)).Inc() - }() - - startTime := time.Now() - rsp, err := inferHttpClient.Post(inferUrl, "application/json", bytes.NewBuffer(data)) // take the ownership of data - if err != nil { - Logger.Error( - "post inference error", - zap.Error(err), - ) - return nil, InternalServerError("inference server error") - } - - defer func() { - _ = rsp.Body.Close() - }() - - response, err := io.ReadAll(rsp.Body) - if err != nil { - Logger.Error("fail to read response body", zap.Error(err)) - return nil, InternalServerError() - } - - latency := int(time.Since(startTime)) - duration := float64(latency) / 1000_000_000 - - statusCode = rsp.StatusCode - if rsp.StatusCode != 200 { - inferLimiter.AddStats(false) - Logger.Error( - "inference error", - zap.Int("latency", latency), - zap.Int("status code", rsp.StatusCode), - zap.ByteString("body", response), - ) - if rsp.StatusCode == 400 { - return nil, maxInputExceededFromInferError - } else if rsp.StatusCode == 560 { - return nil, unknownError - } else if rsp.StatusCode >= 500 { - return nil, InternalServerError() - } else { - return nil, unknownError - } - } else { - var responseStruct struct { - Pred string `json:"pred"` - NewGenerations string `json:"new_generations"` - InputTokenNum int `json:"input_token_num"` - NewGenerationsTokenNum int `json:"new_generations_token_num"` - } - err = json.Unmarshal(response, &responseStruct) - if err != nil { - inferLimiter.AddStats(false) - responseString := string(response) - if responseString == "400" { - statusCode = 400 - return nil, maxInputExceededFromInferError - } else if responseString == "560" { - statusCode = 560 - return nil, unknownError - } else { - statusCode = 500 - Logger.Error( - "unable to unmarshal response from infer", - zap.ByteString("response", response), - zap.Error(err), - ) - return nil, InternalServerError() - } - } else { - inferLimiter.AddStats(true) - Logger.Info( - "inference success", - zap.ByteString("request", data), - zap.Int("latency", latency), - zap.String("pred", responseStruct.Pred), - zap.String("new_generations", responseStruct.NewGenerations), - zap.Int("input_token_num", responseStruct.InputTokenNum), - zap.Int("new_generations_token_num", responseStruct.NewGenerationsTokenNum), - zap.Float64("average", float64(latency)/float64(responseStruct.NewGenerationsTokenNum)), - ) - return &InferTriggerResponse{ - Output: responseStruct.Pred, - NewGeneration: responseStruct.NewGenerations, - Duration: duration, - }, nil - } - } -} - -func ReceiveInferResponse(c *websocket.Conn) { - var ( - message []byte - err error - ) - - defer func() { - if something := recover(); something != nil { - Logger.Error("receive infer response panicked", zap.Any("error", something)) - } - }() - - uuidText := c.Query("uuid") - if uuidText == "" { - _ = c.WriteJSON(InferResponseModel{Status: -1, StatusCode: 400, Output: "Bad Request"}) - return - } - - value, ok := InferResponseChannel.Load(uuidText) - if !ok { - Logger.Error("receive from infer invalid uuid", zap.String("uuid", uuidText)) - _ = c.WriteJSON(InferResponseModel{Status: -1, StatusCode: 400, Output: "Bad Request"}) - return - } - ch := value.(*responseChannel) - - for { - if _, message, err = c.ReadMessage(); err != nil { - if ch.closed.Load() { - _ = c.WriteJSON(InferResponseModel{Status: 0}) - return - } else { - Logger.Error("receive from infer error", zap.Error(err)) - ch.closed.Store(true) - } - return - } - if ch.closed.Load() { - _ = c.WriteJSON(InferResponseModel{Status: 0}) - return - } - - if config.Config.Debug { - log.Printf("receive message from inference, uuid: %v: %s\n", uuidText, string(message)) - } - - var inferResponse InferResponseModel - err = json.Unmarshal(message, &inferResponse) - if err != nil { - log.Printf("receive from infer error message type: %s\n, error: %v", string(message), err) - continue - } - - // continue if sending a heartbeat package - if inferResponse.Status == 2 { - continue - } - - // post process - inferResponse.Output = InferPostprocess(inferResponse.Output) - - if config.Config.Debug { - log.Printf("recieve output: %v\n", inferResponse.Output) - } - - // may panic - ch.ch <- inferResponse - - if inferResponse.Status == 0 { - _ = c.WriteJSON(InferResponseModel{Status: 0}) - return - } - } -} - -func InferPostprocess(output string) (tidyOutput string) { - // process end with 0xfffd - runeSlice := []rune(output) - for len(runeSlice) > 0 && runeSlice[len(runeSlice)-1] == 0xfffd { - runeSlice = runeSlice[:len(runeSlice)-1] - } - - output = strings.Trim(string(runeSlice), " ") - - output = cutSuffix(output, "<", "") - - // not cut - return output -} - -func cutSuffix(s string, suffix ...string) string { - for _, suf := range suffix { - s, _ = strings.CutSuffix(s, suf) - } - return s -} +package record + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/sashabaranov/go-openai" + "golang.org/x/exp/slices" + + "MOSS_backend/config" + . "MOSS_backend/models" + . "MOSS_backend/utils" + "MOSS_backend/utils/sensitive" + + "github.com/gofiber/websocket/v2" + "go.uber.org/zap" +) + +type InferResponseModel struct { + Status int `json:"status"` // 1 for output, 0 for end, -1 for error, -2 for sensitive + StatusCode int `json:"status_code,omitempty"` + Output string `json:"output,omitempty"` + Stage string `json:"stage,omitempty"` +} + +type responseChannel struct { + ch chan InferResponseModel + closed atomic.Bool +} + +var InferResponseChannel sync.Map + +var inferHttpClient = http.Client{Timeout: 120 * time.Second} + +type InferWsContext struct { + c JsonReaderWriter + connectionClosed *atomic.Bool +} + +var UrlConfig struct { + pythonUrl string + retrievalUrl string + documentUrl string + bingapiUrl string +} + + +func InferMoss2( + record *Record, + postRecord RecordModels, + model *ModelConfig, + user *User, + ctx *InferWsContext, +) ( + err error, +) { + //TODO init UrlConfig in main + defer func() { + if v := recover(); v != nil { + Logger.Error("infer moss2 panicked", zap.Any("error", v)) + err = unknownError + } + }() + // Moss2 adopts the same format as OpenAI + moss2Config := openai.DefaultConfig("") + moss2Config.BaseURL = model.Url + client := openai.NewClientWithConfig(moss2Config) + + var messages = make([]openai.ChatCompletionMessage, 0, len(postRecord) + 2) + messages = append(messages, openai.ChatCompletionMessage{ + Role: "system", + Content: model.OpenAISystemPrompt, + }) + messages = append(messages, postRecord.ToOpenAIMessages()...) + messages = append(messages, openai.ChatCompletionMessage{ + Role: "user", + Content: record.Request, + }) + request := openai.ChatCompletionRequest{ + Model: model.OpenAIModelName, + Messages: messages, + Stop: model.EndDelimiter, + } + + if ctx == nil { + // TODO: when will ctx become nil and how to handle the situation + response, err := client.CreateChatCompletion( + context.Background(), + request, + ) + if err != nil { + return err + } + + if len(response.Choices) == 0 { + return unknownError + } + + record.Response = response.Choices[0].Message.Content + + } else { + // streaming, Done + if config.Config.Debug { + Logger.Info("openai streaming", + zap.String("model", model.OpenAIModelName), + zap.String("url", model.Url), + ) + } + + stream, err := client.CreateChatCompletionStream( + context.Background(), + request, + ) + if err != nil { + return err + } + defer stream.Close() + + startTime := time.Now() + var resultBuilder strings.Builder + var nowOutput string + var detectedOutput string + + for { + if ctx.connectionClosed.Load() { + return interruptError + } + response, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return err + } + + if len(response.Choices) == 0 { + return unknownError + } + + resultBuilder.WriteString(response.Choices[0].Delta.Content) + nowOutput = resultBuilder.String() + + if slices.Contains(model.EndDelimiter, MossEnd) && strings.Contains(nowOutput, MossEnd) { + // if MossEnd is found, break the loop + nowOutput = strings.Split(nowOutput, MossEnd)[0] + break + } + + if slices.Contains(model.EndDelimiter, FuncCallEnd) && strings.Contains(nowOutput, FuncCallEnd) { + // if FuncCallEnd is found, call tool apis + var funcCallResult string + + err = GetFuncCallResult(nowOutput, &funcCallResult) + if err != nil { + return err + } + + // TODO: Do we need this? or ignore func_ret and simply send the funcCallResult + message := openai.ChatCompletionMessage{ + Role: "func_ret", + Content: funcCallResult, + } + + request = openai.ChatCompletionRequest{ + Model: model.OpenAIModelName, + Messages: []openai.ChatCompletionMessage{message}, + Stop: model.EndDelimiter, + } + // close old stream + stream.Close() + stream, err = client.CreateChatCompletionStream( + context.Background(), + request, + ) + if err != nil { + return err + } + // erase the content of fun_call + nowOutput = strings.Split(nowOutput, FuncCallStart)[0] + resultBuilder.Reset() + resultBuilder.WriteString(nowOutput) + } + + before, _, found := CutLastAny(nowOutput, ",.?!\n,。?!") + if !found || before == detectedOutput { + continue + } + detectedOutput = before + if model.EnableSensitiveCheck { + err = sensitiveCheck(ctx.c, record, detectedOutput, startTime, user) + if err != nil { + return err + } + } + + _ = ctx.c.WriteJSON(InferResponseModel{ + Status: 1, + Output: detectedOutput, + Stage: "MOSS", + }) + } + + if nowOutput != detectedOutput { + if model.EnableSensitiveCheck { + err = sensitiveCheck(ctx.c, record, nowOutput, startTime, user) + if err != nil { + return err + } + } + + _ = ctx.c.WriteJSON(InferResponseModel{ + Status: 1, + Output: nowOutput, + Stage: "MOSS", + }) + } + + record.Response = nowOutput + record.Duration = float64(time.Since(startTime)) / 1000_000_000 + _ = ctx.c.WriteJSON(InferResponseModel{ + Status: 0, + Output: nowOutput, + Stage: "MOSS", + }) + + } + return nil +} + +func GetFuncCallResult( + nowOutput string, + result *string, +)( + err error, +) { + defer func() { + if v := recover(); v != nil { + Logger.Error("infer funcCall panicked", zap.Any("error", v)) + err = unknownError + } + }() + + funcCallMatches := funcCallRegexp.FindStringSubmatch(nowOutput) + if len(funcCallMatches) == 0 { + return errors.New("no code found") + } + + // get the last match funcCall + lastCodeStr := funcCallMatches[len(funcCallMatches) - 1] + var data map[string]interface{} + err = json.Unmarshal([]byte(lastCodeStr), &data) + + if err != nil { + return err + } + + var inputContent string + var body map[string]interface{} + var url string + // handle different kinds of func call + switch data["name"] { + case "python": { + parameters := data["parameters"].(map[string]interface{}) + codeContent := parameters["code"].(string) + if inputC, ok := parameters["input"]; ok { + inputContent = inputC.(string) + } + url = UrlConfig.pythonUrl + body = map[string]interface{} { + "type": "python", + "code": codeContent, + "input": inputContent, + + } + + } + break + case "retrieval": { + url = UrlConfig.retrievalUrl + parameters := data["parameters"].(map[string]interface{}) + query := parameters["query"].(string) + body = map[string]interface{} { + "query": query, + "top_k": 5, + } + + } + break + case "document": { + url = UrlConfig.documentUrl + parameters := data["parameters"].(map[string]interface{}) + query := parameters["question"].(string) + + body = map[string]interface{} { + "query": query, + "top_k": 5, + } + } + break + case "bingapi": { + url = UrlConfig.bingapiUrl + parameters := data["parameters"].(map[string]interface{}) + query := parameters["question"].(string) + topK := 2 + if val, exists := parameters["top_k"]; exists { + topK = val.(int) + delete(parameters, "top_k") + } + + body = map[string]interface{} { + "query": query, + "top_k": topK, + } + + + } + break + default: { + return errors.New("unknown function call") + } + + } + jsonBody, err := json.Marshal(body) + if err != nil { + return err + } + requestBody := bytes.NewBuffer(jsonBody) + response, err := http.Post(url, "application/json", requestBody) + if err != nil { + return err + } + defer response.Body.Close() + + responseBody, err := io.ReadAll(response.Body) + if err != nil { + return err + } + + *result = string(responseBody) + return nil + +} + + + + + +func InferOpenAI( + record *Record, + postRecord RecordModels, + model *ModelConfig, + user *User, + ctx *InferWsContext, +) ( + err error, +) { + defer func() { + if v := recover(); v != nil { + Logger.Error("infer openai panicked", zap.Any("error", v)) + err = unknownError + } + }() + + openaiConfig := openai.DefaultConfig("") + openaiConfig.BaseURL = model.Url + client := openai.NewClientWithConfig(openaiConfig) + + var messages = make([]openai.ChatCompletionMessage, 0, len(postRecord)+2) + messages = append(messages, openai.ChatCompletionMessage{ + Role: "system", + Content: model.OpenAISystemPrompt, + }) + messages = append(messages, postRecord.ToOpenAIMessages()...) + messages = append(messages, openai.ChatCompletionMessage{ + Role: "user", + Content: record.Request, + }) + request := openai.ChatCompletionRequest{ + Model: model.OpenAIModelName, + Messages: messages, + Stop: model.EndDelimiter, + } + + if ctx == nil { + // openai client may panic when status code is 400 + response, err := client.CreateChatCompletion( + context.Background(), + request, + ) + if err != nil { + return err + } + + if len(response.Choices) == 0 { + return unknownError + } + + record.Response = response.Choices[0].Message.Content + } else { + // streaming + if config.Config.Debug { + Logger.Info("openai streaming", + zap.String("model", model.OpenAIModelName), + zap.String("url", model.Url), + ) + } + + stream, err := client.CreateChatCompletionStream( + context.Background(), + request, + ) + if err != nil { + return err + } + defer stream.Close() + + startTime := time.Now() + + var resultBuilder strings.Builder + var nowOutput string + var detectedOutput string + + for { + if ctx.connectionClosed.Load() { + return interruptError + } + response, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return err + } + + if len(response.Choices) == 0 { + return unknownError + } + + resultBuilder.WriteString(response.Choices[0].Delta.Content) + nowOutput = resultBuilder.String() + + if slices.Contains(model.EndDelimiter, MossEnd) && strings.Contains(nowOutput, MossEnd) { + // if MossEnd is found, break the loop + nowOutput = strings.Split(nowOutput, MossEnd)[0] + break + } + + before, _, found := CutLastAny(nowOutput, ",.?!\n,。?!") + if !found || before == detectedOutput { + continue + } + detectedOutput = before + if model.EnableSensitiveCheck { + err = sensitiveCheck(ctx.c, record, detectedOutput, startTime, user) + if err != nil { + return err + } + } + + _ = ctx.c.WriteJSON(InferResponseModel{ + Status: 1, + Output: detectedOutput, + Stage: "MOSS", + }) + } + if nowOutput != detectedOutput { + if model.EnableSensitiveCheck { + err = sensitiveCheck(ctx.c, record, nowOutput, startTime, user) + if err != nil { + return err + } + } + + _ = ctx.c.WriteJSON(InferResponseModel{ + Status: 1, + Output: nowOutput, + Stage: "MOSS", + }) + } + + record.Response = nowOutput + record.Duration = float64(time.Since(startTime)) / 1000_000_000 + _ = ctx.c.WriteJSON(InferResponseModel{ + Status: 0, + Output: nowOutput, + Stage: "MOSS", + }) + } + + return nil +} + + +func InferCommon( + record *Record, + prefix string, + postRecords RecordModels, + user *User, + param map[string]float64, + ctx *InferWsContext, +) ( + err error, +) { + // metrics + userInferRequestOnFlight.Inc() + defer userInferRequestOnFlight.Dec() + + // load model config + modelID := user.ModelID + if modelID == 0 { + modelID = config.Config.DefaultModelID + } + model, err := LoadModelConfigByID(user.ModelID) + if err != nil { + model, err = LoadModelConfigByID(config.Config.DefaultModelID) + if err != nil { + return err + } + modelID = config.Config.DefaultModelID + } + + // dispatch + if model.APIType == APITypeMOSS2 { + return InferMoss2(record, postRecords, model, user, ctx) + } else if model.APIType == APITypeMOSS{ + return InferOpenAI(record, postRecords, model, user, ctx) + } else { + return errors.New("unknown API type") + } + return nil +} + + + +func Infer( + record *Record, + prefix string, + postRecord RecordModels, + user *User, + param map[string]float64, +) ( + err error, +) { + return InferCommon( + record, + prefix, + postRecord, + user, + param, + nil, + ) +} + +func InferAsync( + c *websocket.Conn, + prefix string, + record *Record, + postRecord RecordModels, + user *User, + param map[string]float64, +) ( + err error, +) { + var ( + interruptChan = make(chan any) // frontend interrupt channel + connectionClosed = new(atomic.Bool) // connection closed flag + errChan = make(chan error) // error transmission channel + successChan = make(chan any) // success infer flag + ) + connectionClosed.Store(false) // initialize + defer connectionClosed.Store(true) // if this closed, stop all goroutines + // wait for interrupt + go interrupt( + c, + interruptChan, + connectionClosed, + ) + // wait for infer + go func() { + innerErr := InferCommon( + record, + prefix, + postRecord, + user, + param, + &InferWsContext{ + c: c, + connectionClosed: connectionClosed, + }, + ) + if innerErr != nil { + errChan <- innerErr + } else { + close(successChan) + } + }() + + for { + select { + case <-interruptChan: + return NoStatus("client interrupt") + case err = <-errChan: + return err + case <-successChan: + return nil + } + } +} + + + +func sensitiveCheck( + c JsonReaderWriter, + record *Record, + output string, + startTime time.Time, + user *User, +) error { + if config.Config.Debug { + Logger.Info("sensitive checking", zap.String("output", output)) + } + + if sensitive.IsSensitive(output, user) { + record.ResponseSensitive = true + // log new record + record.Response = output + record.Duration = float64(time.Since(startTime)) / 1000_000_000 + + banned, err := user.AddUserOffense(UserOffenseMoss) + if err != nil { + return err + } + + var outputMessage string + if banned { + outputMessage = OffenseMessage + } else { + outputMessage = DefaultResponse + } + + _ = c.WriteJSON(InferResponseModel{ + Status: -2, // banned + Output: outputMessage, + }) + + // if sensitive, jump out and record + return ErrSensitive + } + return nil +} diff --git a/apis/record/routes.go b/apis/record/routes.go index 64a400c..db18e52 100644 --- a/apis/record/routes.go +++ b/apis/record/routes.go @@ -1,30 +1,27 @@ -package record - -import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/websocket/v2" -) - -func RegisterRoutes(routes fiber.Router) { - // record - routes.Get("/chats/:id/records", ListRecords) - routes.Post("/chats/:id/records", AddRecord) - routes.Get("/ws/chats/:id/records", websocket.New(AddRecordAsync)) - routes.Get("/ws/chats/:id/regenerate", websocket.New(RegenerateAsync)) - routes.Put("/records/:id", ModifyRecord) - - // infer response - routes.Get("/ws/response", websocket.New(ReceiveInferResponse)) - - // infer without login - routes.Post("/inference", InferWithoutLogin) - routes.Get("/ws/inference", websocket.New(InferWithoutLoginAsync)) - - // OpenAI API protocol - routes.Get("/v1/models", OpenAIListModels) - routes.Get("/v1/models/:name", OpenAIRetrieveModel) - routes.Post("/v1/chat/completions", OpenAICreateChatCompletion) - - // yocsef API - routes.Get("/ws/yocsef/inference", websocket.New(InferYocsefAsyncAPI)) -} +package record + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/websocket/v2" +) + +func RegisterRoutes(routes fiber.Router) { + // record + routes.Get("/chats/:id/records", ListRecords) + routes.Post("/chats/:id/records", AddRecord) + routes.Get("/ws/chats/:id/records", websocket.New(AddRecordAsync)) + routes.Get("/ws/chats/:id/regenerate", websocket.New(RegenerateAsync)) + routes.Put("/records/:id", ModifyRecord) + + // infer without login + routes.Post("/inference", InferWithoutLogin) + routes.Get("/ws/inference", websocket.New(InferWithoutLoginAsync)) + + // OpenAI API protocol + routes.Get("/v1/models", OpenAIListModels) + routes.Get("/v1/models/:name", OpenAIRetrieveModel) + routes.Post("/v1/chat/completions", OpenAICreateChatCompletion) + + // yocsef API + routes.Get("/ws/yocsef/inference", websocket.New(InferYocsefAsyncAPI)) +} diff --git a/apis/record/utils.go b/apis/record/utils.go index b3e765a..8e2470e 100644 --- a/apis/record/utils.go +++ b/apis/record/utils.go @@ -1,31 +1,32 @@ -package record - -import ( - . "MOSS_backend/utils" - "errors" - "regexp" -) - -// regexps -var ( - endContentRegexp = regexp.MustCompile(`<[es]o\w>`) - mossSpecialTokenRegexp = regexp.MustCompile(``) - innerThoughtsRegexp = regexp.MustCompile(`<\|Inner Thoughts\|>:([\s\S]+?)()`) - commandsRegexp = regexp.MustCompile(`<\|Commands\|>:([\s\S]+?)()`) - resultsRegexp = regexp.MustCompile(`<\|Results\|>:[\s\S]+?`) // not greedy - mossRegexp = regexp.MustCompile(`<\|MOSS\|>:([\s\S]+?)()`) - secondGenerationsFormatRegexp = regexp.MustCompile(`^<\|MOSS\|>:[\s\S]+?$`) - firstGenerationsFormatRegexp = regexp.MustCompile(`^<\|Inner Thoughts\|>:[\s\S]+?\n *?<\|Commands\|>:[\s\S]+?$`) -) - -//var maxLengthExceededError = BadRequest("The maximum context length is exceeded").WithMessageType(MaxLength) - -// error messages -var ( - userRequestingError = BadRequest("上一次请求还未结束,请稍后再试。User requesting, please wait and try again") - maxInputExceededError = BadRequest("单次输入限长为 2048 字符。Input no more than 2048 characters").WithMessageType(MaxLength) - maxInputExceededFromInferError = BadRequest("单次输入超长,请减少字数并重试。Input max length exceeded, please reduce length and try again").WithMessageType(MaxLength) - unknownError = InternalServerError("未知错误,请刷新或等待一分钟后再试。Unknown error, please refresh or wait a minute and try again") - ErrSensitive = errors.New("sensitive") - interruptError = NoStatus("client interrupt") -) +package record + +import ( + . "MOSS_backend/utils" + "errors" + "regexp" +) + +// regexps +var ( + endContentRegexp = regexp.MustCompile(`<[es]o\w>`) + mossSpecialTokenRegexp = regexp.MustCompile(``) + innerThoughtsRegexp = regexp.MustCompile(`<\|Inner Thoughts\|>:([\s\S]+?)()`) + commandsRegexp = regexp.MustCompile(`<\|Commands\|>:([\s\S]+?)()`) + resultsRegexp = regexp.MustCompile(`<\|Results\|>:[\s\S]+?`) // not greedy + mossRegexp = regexp.MustCompile(`<\|MOSS\|>:([\s\S]+?)()`) + secondGenerationsFormatRegexp = regexp.MustCompile(`^<\|MOSS\|>:[\s\S]+?$`) + firstGenerationsFormatRegexp = regexp.MustCompile(`^<\|Inner Thoughts\|>:[\s\S]+?\n *?<\|Commands\|>:[\s\S]+?$`) + funcCallRegexp = regexp.MustCompile(`(?s)func_call\s*(.*?)<\|end_of_func_call\|>`) +) + +//var maxLengthExceededError = BadRequest("The maximum context length is exceeded").WithMessageType(MaxLength) + +// error messages +var ( + userRequestingError = BadRequest("上一次请求还未结束,请稍后再试。User requesting, please wait and try again") + maxInputExceededError = BadRequest("单次输入限长为 2048 字符。Input no more than 2048 characters").WithMessageType(MaxLength) + maxInputExceededFromInferError = BadRequest("单次输入超长,请减少字数并重试。Input max length exceeded, please reduce length and try again").WithMessageType(MaxLength) + unknownError = InternalServerError("未知错误,请刷新或等待一分钟后再试。Unknown error, please refresh or wait a minute and try again") + ErrSensitive = errors.New("sensitive") + interruptError = NoStatus("client interrupt") +) diff --git a/go.mod b/go.mod index 4ebcb2f..90fea4b 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,8 @@ require ( github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.926 github.com/vmihailenco/msgpack/v5 v5.4.1 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.23.0 - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 + golang.org/x/crypto v0.24.0 + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/time v0.5.0 gorm.io/driver/mysql v1.5.6 gorm.io/driver/sqlite v1.5.5 @@ -81,11 +81,11 @@ require ( github.com/valyala/tcplisten v1.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.25.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.21.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9f2a8b6..294cf56 100644 --- a/go.sum +++ b/go.sum @@ -16,15 +16,9 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= -github.com/chromedp/cdproto v0.0.0-20240312231614-1e5096e63154 h1:jeAmkzyOAQBPRmZMhX+i/CJv0VViLkHk1nF0qx8s0Mk= -github.com/chromedp/cdproto v0.0.0-20240312231614-1e5096e63154/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= -github.com/chromedp/cdproto v0.0.0-20240328024531-fe04f09ede24 h1:XLG3KlHtG6Wg75ed/daLltJtcj8VXjw7F9mzYenzFL0= -github.com/chromedp/cdproto v0.0.0-20240328024531-fe04f09ede24/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/cdproto v0.0.0-20240519224452-66462be74baa h1:T3Ho4BWIkoEoMPCj90W2HIPF/k56qk4JWzTs6JUBxVw= github.com/chromedp/cdproto v0.0.0-20240519224452-66462be74baa/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg= @@ -37,8 +31,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/eko/gocache/lib/v4 v4.1.5 h1:CeMQmdIzwBKKLRjk3FCDXzNFsQTyqJ01JLI7Ib0C9r8= -github.com/eko/gocache/lib/v4 v4.1.5/go.mod h1:XaNfCwW8KYW1bRZ/KoHA1TugnnkMz0/gT51NDIu7LSY= github.com/eko/gocache/lib/v4 v4.1.6 h1:5WWIGISKhE7mfkyF+SJyWwqa4Dp2mkdX8QsZpnENqJI= github.com/eko/gocache/lib/v4 v4.1.6/go.mod h1:HFxC8IiG2WeRotg09xEnPD72sCheJiTSr4Li5Ameg7g= github.com/eko/gocache/store/go_cache/v4 v4.2.1 h1:3xSksOamzCf+YZXz9l67gr6jOj3AA4hnk0mV4z3Jwbs= @@ -63,27 +55,20 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= -github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q= github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4= github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc= -github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo= -github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc= @@ -104,8 +89,6 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -116,10 +99,6 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kUL github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240307020853-f8f5f687e6a2 h1:d1r8teoGqM6npYqldpfOaBvpKh9+utiO6T9Klb6BSIE= -github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240307020853-f8f5f687e6a2/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs= -github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240322081424-40c7f53fc97c h1:GwFf1OsK3FXpur32WnUCeHGJci+TX815PYUfX59NBWc= -github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240322081424-40c7f53fc97c/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs= github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6 h1:YeIGErDiB/fhmNsJy0cfjoT8XnRNT9hb19xZ4MvWQDU= github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -141,26 +120,12 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ= -github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ= -github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= -github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= -github.com/prometheus/common v0.52.2 h1:LW8Vk7BccEdONfrJBDffQGRtpSzi5CQaRZGtboOO2ck= -github.com/prometheus/common v0.52.2/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= -github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA= -github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= -github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= -github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek= github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk= github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= @@ -172,14 +137,6 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/sashabaranov/go-openai v1.20.3 h1:JMhaSfE1mLznYIvlf8odxbyWGiFFGnQjyLw00I9R4fU= -github.com/sashabaranov/go-openai v1.20.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= -github.com/sashabaranov/go-openai v1.20.4 h1:095xQ/fAtRa0+Rj21sezVJABgKfGPNbyx/sAN/hJUmg= -github.com/sashabaranov/go-openai v1.20.4/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= -github.com/sashabaranov/go-openai v1.20.5 h1:Sab4nzBLtoyxm4jRqH9G9pkIh50WpBFacPTEpruPB6o= -github.com/sashabaranov/go-openai v1.20.5/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= -github.com/sashabaranov/go-openai v1.21.0 h1:isAf3zPSD3VLd0pC2/2Q6ZyRK7jzPAaz+X3rjsviaYQ= -github.com/sashabaranov/go-openai v1.21.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sashabaranov/go-openai v1.24.0 h1:4H4Pg8Bl2RH/YSnU8DYumZbuHnnkfioor/dtNlB20D4= github.com/sashabaranov/go-openai v1.24.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8= @@ -190,40 +147,13 @@ github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.870/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.873 h1:VdQ+lQ98CFwbmfQj02iMVWWzdwnKubuICIa2QhzQBJI= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.873/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.874 h1:wGeKaimE6hK0zUCWjlgagRA1IZIGrZtIhz2wbGp1Y/U= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.874/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.890 h1:pWCzLswZmiNUNNwdEejAMmrQkxIcyL2cFHiyMwZYLVI= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.890/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.895 h1:mcGC8MgWtWFP+uEfEIcoSpHxPiA77I3wQ0qT/5fcDec= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.895/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 h1:ERwcXqhc94L9cFxtiI0pvt7IJtlHl/p/Jayl3mLw+ms= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.925 h1:/o38UecHEICIq/n/chR/pVtknwCGXHgkatjtHdJ1WmE= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.925/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.926/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.927 h1:GQfspzw7p3Apu3Fkx11Me9h6CdmO3NdtT7Gde4pJA7M= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.927/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.870 h1:2EjE2UHO+NcvjlzHfeYJdFDW7PBqBKkJjY26AMLq4yE= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.870/go.mod h1:HftwZbAvTY5BtkNpAO1cDSFbHjE6QBBSz10zRUrXSrI= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.874 h1:jFRlGuaAjQ/RnwxiXuetqWY5fz7UwfMoaZZ8R+Lxedg= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.874/go.mod h1:AprErbzkboMk/jaIAMAaCZwD0tKBiWM/XxnmhtPKRSI= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.890 h1:69Oe++8hREOIjpbDbKd/blSNpz5Whz5QgBfD0Y0KO0k= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.890/go.mod h1:DmPm+fCbM+nyqWPlPTT10fG9lXjwsjRhQa1IOY5J5bw= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.895 h1:7sOSU5xU6wPTGFBN1aFyXlj6P/3D/sAIZftqDZXx2Mk= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.895/go.mod h1:0zQ/AsBg2PCijseodnrdn9NUYOf5tHgpD44y+ZQRqNw= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.898 h1:I17guwwSYvYjc6Y6+/NpE/VUIfP2Y38XDafv9S5hMI4= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.898/go.mod h1:itrDqJrIyqbrh6E5LRrb0hQFKq7YPm1x9AjtFqK6b+Q= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.925 h1:YdQosjSaerXogV+ND1TCxu47syRyzCjVymoor+W5Aig= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.925/go.mod h1:fT7+LFYhditONiht1Vv3u4CO/PSU5gCiyeVGL64xNYo= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.926 h1:MjaGYERiOvfpmxeOlZbwNDWaUfilY5QH9aMRGoPB748= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.926/go.mod h1:tsKJfCAR446w62giYEOHhugxqQoAXQHpDCDP38qJGCo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= -github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= github.com/valyala/fasthttp v1.53.0 h1:lW/+SUkOxCx2vlIu0iaImv4JLrVRnbbkpCoaawvA4zc= github.com/valyala/fasthttp v1.53.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= @@ -241,32 +171,18 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= -golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= -golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= @@ -279,35 +195,23 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -315,16 +219,10 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso= -gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs= gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= -gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= -gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/models/config.go b/models/config.go index 680355f..26bd193 100644 --- a/models/config.go +++ b/models/config.go @@ -1,115 +1,123 @@ -package models - -import ( - "time" - - "go.uber.org/zap" - - "MOSS_backend/config" - "MOSS_backend/utils" -) - -type APIType string - -const ( - APITypeOpenAI APIType = "openai" -) - -type ModelConfig struct { - ID int `json:"id"` - InnerThoughtsPostprocess bool `json:"inner_thoughts_postprocess" default:"false"` - Description string `json:"description"` - DefaultPluginConfig map[string]bool `json:"default_plugin_config" gorm:"serializer:json"` - Url string `json:"url"` - CallbackUrl string `json:"callback_url"` - APIType APIType `json:"api_type"` - OpenAIModelName string `json:"openai_model_name"` - OpenAISystemPrompt string `json:"openai_system_prompt"` - EnableSensitiveCheck bool `json:"enable_sensitive_check"` - EndDelimiter string `json:"end_delimiter"` -} - -type ModelConfigs = []*ModelConfig - -func (cfg *ModelConfig) TableName() string { - return "language_model_config" -} - -type Config struct { - ID int `json:"id"` - InviteRequired bool `json:"invite_required"` - OffenseCheck bool `json:"offense_check"` - Notice string `json:"notice"` - ModelConfig []ModelConfig `json:"model_config" gorm:"-:all"` -} - -const configCacheName = "moss_backend_config" -const configCacheExpire = 24 * time.Hour - -func LoadConfig(configObjectPtr *Config) error { - if config.GetCache(configCacheName, configObjectPtr) != nil { - if err := DB.First(configObjectPtr).Error; err != nil { - return err - } - if err := DB.Find(&(configObjectPtr.ModelConfig)).Error; err != nil { - return err - } - _ = config.SetCache(configCacheName, *configObjectPtr, configCacheExpire) - } - return nil -} - -func LoadModelConfigs() (ModelConfigs, error) { - var modelConfigs ModelConfigs - if err := DB.Find(&modelConfigs).Error; err != nil { - return nil, err - } - return modelConfigs, nil -} - -func LoadModelConfigByName(name string) (*ModelConfig, error) { - var modelConfig ModelConfig - if err := DB.Where("description = ?", name).First(&modelConfig).Error; err != nil { - return nil, err - } - return &modelConfig, nil -} - -func LoadModelConfigByID(id int) (*ModelConfig, error) { - var modelConfig ModelConfig - if err := DB.Where("id = ?", id).First(&modelConfig).Error; err != nil { - return nil, err - } - return &modelConfig, nil -} - -func UpdateConfig(configObjectPtr *Config) error { - err := DB.Model(&Config{ID: 1}).Updates(configObjectPtr).Error - if err != nil { - utils.Logger.Error("failed to update config", zap.Error(err)) - return err - } - for i := range configObjectPtr.ModelConfig { - err = DB.Model(&configObjectPtr.ModelConfig).Updates(&configObjectPtr.ModelConfig[i]).Error - if err != nil { - utils.Logger.Error("failed to update model config", zap.Error(err)) - return err - } - } - _ = config.SetCache(configCacheName, *configObjectPtr, configCacheExpire) - return nil -} - -func GetPluginConfig(modelID int) (map[string]bool, error) { - var configObject Config - if err := LoadConfig(&configObject); err != nil { - return nil, err - } - for _, modelConfig := range configObject.ModelConfig { - if modelConfig.ID == modelID { - return modelConfig.DefaultPluginConfig, nil - } - } - // if not found, return default config of first model - return configObject.ModelConfig[0].DefaultPluginConfig, nil -} +package models + +import ( + "time" + + "go.uber.org/zap" + + "MOSS_backend/config" + "MOSS_backend/utils" +) + +type APIType string + + +const ( + APITypeMOSS APIType = "moss" + APITypeMOSS2 APIType = "moss2" +) + +const ( + FuncCallStart string = "func_call" + FuncCallEnd string = "<|end_of_func_call|>" + MossEnd string = "<|end_of_moss|>" +) + +type ModelConfig struct { + ID int `json:"id"` + InnerThoughtsPostprocess bool `json:"inner_thoughts_postprocess" default:"false"` + Description string `json:"description"` + DefaultPluginConfig map[string]bool `json:"default_plugin_config" gorm:"serializer:json"` + Url string `json:"url"` + CallbackUrl string `json:"callback_url"` + APIType APIType `json:"api_type"` + OpenAIModelName string `json:"openai_model_name"` + OpenAISystemPrompt string `json:"openai_system_prompt"` + EnableSensitiveCheck bool `json:"enable_sensitive_check"` + EndDelimiter []string `json:"end_delimiter"` +} + +type ModelConfigs = []*ModelConfig + +func (cfg *ModelConfig) TableName() string { + return "language_model_config" +} + +type Config struct { + ID int `json:"id"` + InviteRequired bool `json:"invite_required"` + OffenseCheck bool `json:"offense_check"` + Notice string `json:"notice"` + ModelConfig []ModelConfig `json:"model_config" gorm:"-:all"` +} + +const configCacheName = "moss_backend_config" +const configCacheExpire = 24 * time.Hour + +func LoadConfig(configObjectPtr *Config) error { + if config.GetCache(configCacheName, configObjectPtr) != nil { + if err := DB.First(configObjectPtr).Error; err != nil { + return err + } + if err := DB.Find(&(configObjectPtr.ModelConfig)).Error; err != nil { + return err + } + _ = config.SetCache(configCacheName, *configObjectPtr, configCacheExpire) + } + return nil +} + +func LoadModelConfigs() (ModelConfigs, error) { + var modelConfigs ModelConfigs + if err := DB.Find(&modelConfigs).Error; err != nil { + return nil, err + } + return modelConfigs, nil +} + +func LoadModelConfigByName(name string) (*ModelConfig, error) { + var modelConfig ModelConfig + if err := DB.Where("description = ?", name).First(&modelConfig).Error; err != nil { + return nil, err + } + return &modelConfig, nil +} + +func LoadModelConfigByID(id int) (*ModelConfig, error) { + var modelConfig ModelConfig + if err := DB.Where("id = ?", id).First(&modelConfig).Error; err != nil { + return nil, err + } + return &modelConfig, nil +} + +func UpdateConfig(configObjectPtr *Config) error { + err := DB.Model(&Config{ID: 1}).Updates(configObjectPtr).Error + if err != nil { + utils.Logger.Error("failed to update config", zap.Error(err)) + return err + } + for i := range configObjectPtr.ModelConfig { + err = DB.Model(&configObjectPtr.ModelConfig).Updates(&configObjectPtr.ModelConfig[i]).Error + if err != nil { + utils.Logger.Error("failed to update model config", zap.Error(err)) + return err + } + } + _ = config.SetCache(configCacheName, *configObjectPtr, configCacheExpire) + return nil +} + +func GetPluginConfig(modelID int) (map[string]bool, error) { + var configObject Config + if err := LoadConfig(&configObject); err != nil { + return nil, err + } + for _, modelConfig := range configObject.ModelConfig { + if modelConfig.ID == modelID { + return modelConfig.DefaultPluginConfig, nil + } + } + // if not found, return default config of first model + return configObject.ModelConfig[0].DefaultPluginConfig, nil +} diff --git a/utils/utils.go b/utils/utils.go index 3225275..7792d95 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,65 +1,65 @@ -package utils - -import ( - "github.com/gofiber/fiber/v2" -) - -type CanPreprocess interface { - Preprocess(c *fiber.Ctx) error -} - -func Serialize(c *fiber.Ctx, obj CanPreprocess) error { - err := obj.Preprocess(c) - if err != nil { - return err - } - return c.JSON(obj) -} - -func GetRealIP(c *fiber.Ctx) string { - IPs := c.IPs() - if len(IPs) > 0 { - return IPs[0] - } else { - return c.Get("X-Real-Ip", c.IP()) - } -} - -func StripContent(content string, length int) string { - return string([]rune(content)[:min(len([]rune(content)), length)]) -} - -func CutLastAny(s string, chars string) (before, after string, found bool) { - sourceRunes := []rune(s) - charRunes := []rune(chars) - maxIndex := -1 - for _, char := range charRunes { - index := -1 - for i, sourceRune := range sourceRunes { - if char == sourceRune { - index = i - } - } - if index > 0 { - maxIndex = max(maxIndex, index) - } - } - if maxIndex == -1 { - return s, "", false - } else { - return string(sourceRunes[:maxIndex+1]), string(sourceRunes[maxIndex+1:]), true - } -} - -type JSONReader interface { - ReadJson(any) error -} - -type JSONWriter interface { - WriteJSON(any) error -} - -type JsonReaderWriter interface { - JSONReader - JSONWriter -} +package utils + +import ( + "github.com/gofiber/fiber/v2" +) + +type CanPreprocess interface { + Preprocess(c *fiber.Ctx) error +} + +func Serialize(c *fiber.Ctx, obj CanPreprocess) error { + err := obj.Preprocess(c) + if err != nil { + return err + } + return c.JSON(obj) +} + +func GetRealIP(c *fiber.Ctx) string { + IPs := c.IPs() + if len(IPs) > 0 { + return IPs[0] + } else { + return c.Get("X-Real-Ip", c.IP()) + } +} + +func StripContent(content string, length int) string { + return string([]rune(content)[:min(len([]rune(content)), length)]) +} + +func CutLastAny(s string, chars string) (before, after string, found bool) { + sourceRunes := []rune(s) + charRunes := []rune(chars) + maxIndex := -1 + for _, char := range charRunes { + index := -1 + for i, sourceRune := range sourceRunes { + if char == sourceRune { + index = i + } + } + if index > 0 { + maxIndex = max(maxIndex, index) + } + } + if maxIndex == -1 { + return s, "", false + } else { + return string(sourceRunes[:maxIndex+1]), string(sourceRunes[maxIndex+1:]), true + } +} + +type JSONReader interface { + ReadJSON(any) error +} + +type JSONWriter interface { + WriteJSON(any) error +} + +type JsonReaderWriter interface { + JSONReader + JSONWriter +}