From 56fc3441da1eb9349857115c1a34011f525b9f99 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 3 Sep 2025 14:00:52 +0800 Subject: [PATCH] feat(monitor_setting): implement automatic channel testing configuration --- controller/channel-test.go | 34 +++++--- main.go | 10 +-- relay/common/relay_utils.go | 6 +- service/file_decoder.go | 3 + service/token_counter.go | 80 +------------------ setting/operation_setting/monitor_setting.go | 34 ++++++++ .../components/settings/OperationSetting.jsx | 7 +- .../Setting/Operation/SettingsMonitoring.jsx | 36 +++++++++ 8 files changed, 107 insertions(+), 103 deletions(-) create mode 100644 setting/operation_setting/monitor_setting.go diff --git a/controller/channel-test.go b/controller/channel-test.go index 81f7e19ab..5fc6d749c 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -20,6 +20,7 @@ import ( relayconstant "one-api/relay/constant" "one-api/relay/helper" "one-api/service" + "one-api/setting/operation_setting" "one-api/types" "strconv" "strings" @@ -477,15 +478,26 @@ func TestAllChannels(c *gin.Context) { return } -func AutomaticallyTestChannels(frequency int) { - if frequency <= 0 { - common.SysLog("CHANNEL_TEST_FREQUENCY is not set or invalid, skipping automatic channel test") - return - } - for { - time.Sleep(time.Duration(frequency) * time.Minute) - common.SysLog("testing all channels") - _ = testAllChannels(false) - common.SysLog("channel test finished") - } +var autoTestChannelsOnce sync.Once + +func AutomaticallyTestChannels() { + autoTestChannelsOnce.Do(func() { + for { + if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled { + time.Sleep(10 * time.Minute) + continue + } + frequency := operation_setting.GetMonitorSetting().AutoTestChannelMinutes + common.SysLog(fmt.Sprintf("automatically test channels with interval %d minutes", frequency)) + for { + time.Sleep(time.Duration(frequency) * time.Minute) + common.SysLog("automatically testing all channels") + _ = testAllChannels(false) + common.SysLog("automatically channel test finished") + if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled { + break + } + } + } + }) } diff --git a/main.go b/main.go index 2dfddaccf..cc2288a61 100644 --- a/main.go +++ b/main.go @@ -94,13 +94,9 @@ func main() { } go controller.AutomaticallyUpdateChannels(frequency) } - if os.Getenv("CHANNEL_TEST_FREQUENCY") != "" { - frequency, err := strconv.Atoi(os.Getenv("CHANNEL_TEST_FREQUENCY")) - if err != nil { - common.FatalLog("failed to parse CHANNEL_TEST_FREQUENCY: " + err.Error()) - } - go controller.AutomaticallyTestChannels(frequency) - } + + go controller.AutomaticallyTestChannels() + if common.IsMasterNode && constant.UpdateTask { gopool.Go(func() { controller.UpdateMidjourneyTaskBulk() diff --git a/relay/common/relay_utils.go b/relay/common/relay_utils.go index 290865854..3d5efcb6d 100644 --- a/relay/common/relay_utils.go +++ b/relay/common/relay_utils.go @@ -2,12 +2,10 @@ package common import ( "fmt" - "github.com/gin-gonic/gin" - _ "image/gif" - _ "image/jpeg" - _ "image/png" "one-api/constant" "strings" + + "github.com/gin-gonic/gin" ) func GetFullRequestURL(baseURL string, requestURL string, channelType int) string { diff --git a/service/file_decoder.go b/service/file_decoder.go index 94f3f0282..99fdc3f9a 100644 --- a/service/file_decoder.go +++ b/service/file_decoder.go @@ -5,6 +5,9 @@ import ( "encoding/base64" "fmt" "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" "io" "net/http" "one-api/common" diff --git a/service/token_counter.go b/service/token_counter.go index b7dd81f52..be5c2e80c 100644 --- a/service/token_counter.go +++ b/service/token_counter.go @@ -5,6 +5,9 @@ import ( "errors" "fmt" "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" "log" "math" "one-api/common" @@ -357,33 +360,6 @@ func CountRequestToken(c *gin.Context, meta *types.TokenCountMeta, info *relayco return tkm, nil } -//func CountTokenChatRequest(info *relaycommon.RelayInfo, request dto.GeneralOpenAIRequest) (int, error) { -// tkm := 0 -// msgTokens, err := CountTokenMessages(info, request.Messages, request.Model, request.Stream) -// if err != nil { -// return 0, err -// } -// tkm += msgTokens -// if request.Tools != nil { -// openaiTools := request.Tools -// countStr := "" -// for _, tool := range openaiTools { -// countStr = tool.Function.Name -// if tool.Function.Description != "" { -// countStr += tool.Function.Description -// } -// if tool.Function.Parameters != nil { -// countStr += fmt.Sprintf("%v", tool.Function.Parameters) -// } -// } -// toolTokens := CountTokenInput(countStr, request.Model) -// tkm += 8 -// tkm += toolTokens -// } -// -// return tkm, nil -//} - func CountTokenClaudeRequest(request dto.ClaudeRequest, model string) (int, error) { tkm := 0 @@ -543,56 +519,6 @@ func CountTokenRealtime(info *relaycommon.RelayInfo, request dto.RealtimeEvent, return textToken, audioToken, nil } -//func CountTokenMessages(info *relaycommon.RelayInfo, messages []dto.Message, model string, stream bool) (int, error) { -// //recover when panic -// tokenEncoder := getTokenEncoder(model) -// // Reference: -// // https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb -// // https://github.com/pkoukk/tiktoken-go/issues/6 -// // -// // Every message follows <|start|>{role/name}\n{content}<|end|>\n -// var tokensPerMessage int -// var tokensPerName int -// -// tokensPerMessage = 3 -// tokensPerName = 1 -// -// tokenNum := 0 -// for _, message := range messages { -// tokenNum += tokensPerMessage -// tokenNum += getTokenNum(tokenEncoder, message.Role) -// if message.Content != nil { -// if message.Name != nil { -// tokenNum += tokensPerName -// tokenNum += getTokenNum(tokenEncoder, *message.Name) -// } -// arrayContent := message.ParseContent() -// for _, m := range arrayContent { -// if m.Type == dto.ContentTypeImageURL { -// imageUrl := m.GetImageMedia() -// imageTokenNum, err := getImageToken(info, imageUrl, model, stream) -// if err != nil { -// return 0, err -// } -// tokenNum += imageTokenNum -// log.Printf("image token num: %d", imageTokenNum) -// } else if m.Type == dto.ContentTypeInputAudio { -// // TODO: 音频token数量计算 -// tokenNum += 100 -// } else if m.Type == dto.ContentTypeFile { -// tokenNum += 5000 -// } else if m.Type == dto.ContentTypeVideoUrl { -// tokenNum += 5000 -// } else { -// tokenNum += getTokenNum(tokenEncoder, m.Text) -// } -// } -// } -// } -// tokenNum += 3 // Every reply is primed with <|start|>assistant<|message|> -// return tokenNum, nil -//} - func CountTokenInput(input any, model string) int { switch v := input.(type) { case string: diff --git a/setting/operation_setting/monitor_setting.go b/setting/operation_setting/monitor_setting.go new file mode 100644 index 000000000..1d0bbec40 --- /dev/null +++ b/setting/operation_setting/monitor_setting.go @@ -0,0 +1,34 @@ +package operation_setting + +import ( + "one-api/setting/config" + "os" + "strconv" +) + +type MonitorSetting struct { + AutoTestChannelEnabled bool `json:"auto_test_channel_enabled"` + AutoTestChannelMinutes int `json:"auto_test_channel_minutes"` +} + +// 默认配置 +var monitorSetting = MonitorSetting{ + AutoTestChannelEnabled: false, + AutoTestChannelMinutes: 10, +} + +func init() { + // 注册到全局配置管理器 + config.GlobalConfig.Register("monitor_setting", &monitorSetting) +} + +func GetMonitorSetting() *MonitorSetting { + if os.Getenv("CHANNEL_TEST_FREQUENCY") != "" { + frequency, err := strconv.Atoi(os.Getenv("CHANNEL_TEST_FREQUENCY")) + if err == nil && frequency > 0 { + monitorSetting.AutoTestChannelEnabled = true + monitorSetting.AutoTestChannelMinutes = frequency + } + } + return &monitorSetting +} diff --git a/web/src/components/settings/OperationSetting.jsx b/web/src/components/settings/OperationSetting.jsx index 05bda1528..0d6e44107 100644 --- a/web/src/components/settings/OperationSetting.jsx +++ b/web/src/components/settings/OperationSetting.jsx @@ -68,6 +68,8 @@ const OperationSetting = () => { AutomaticDisableChannelEnabled: false, AutomaticEnableChannelEnabled: false, AutomaticDisableKeywords: '', + 'monitor_setting.auto_test_channel_enabled': false, + 'monitor_setting.auto_test_channel_minutes': 10, }); let [loading, setLoading] = useState(false); @@ -78,10 +80,7 @@ const OperationSetting = () => { if (success) { let newInputs = {}; data.forEach((item) => { - if ( - item.key.endsWith('Enabled') || - ['DefaultCollapseSidebar'].includes(item.key) - ) { + if (typeof inputs[item.key] === 'boolean') { newInputs[item.key] = toBoolean(item.value); } else { newInputs[item.key] = item.value; diff --git a/web/src/pages/Setting/Operation/SettingsMonitoring.jsx b/web/src/pages/Setting/Operation/SettingsMonitoring.jsx index f4de4f6ea..d64f19b63 100644 --- a/web/src/pages/Setting/Operation/SettingsMonitoring.jsx +++ b/web/src/pages/Setting/Operation/SettingsMonitoring.jsx @@ -38,6 +38,8 @@ export default function SettingsMonitoring(props) { AutomaticDisableChannelEnabled: false, AutomaticEnableChannelEnabled: false, AutomaticDisableKeywords: '', + 'monitor_setting.auto_test_channel_enabled': false, + 'monitor_setting.auto_test_channel_minutes': 10, }); const refForm = useRef(); const [inputsRow, setInputsRow] = useState(inputs); @@ -98,6 +100,40 @@ export default function SettingsMonitoring(props) { style={{ marginBottom: 15 }} > + + + + setInputs({ + ...inputs, + 'monitor_setting.auto_test_channel_enabled': value, + }) + } + /> + + + + setInputs({ + ...inputs, + 'monitor_setting.auto_test_channel_minutes': parseInt(value), + }) + } + /> + +