first commit: one-api base code + SAAS plan document
This commit is contained in:
77
monitor/channel.go
Normal file
77
monitor/channel.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/songquanpeng/one-api/common/config"
|
||||
"github.com/songquanpeng/one-api/common/logger"
|
||||
"github.com/songquanpeng/one-api/common/message"
|
||||
"github.com/songquanpeng/one-api/model"
|
||||
)
|
||||
|
||||
func notifyRootUser(subject string, content string) {
|
||||
if config.MessagePusherAddress != "" {
|
||||
err := message.SendMessage(subject, content, content)
|
||||
if err != nil {
|
||||
logger.SysError(fmt.Sprintf("failed to send message: %s", err.Error()))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if config.RootUserEmail == "" {
|
||||
config.RootUserEmail = model.GetRootUserEmail()
|
||||
}
|
||||
err := message.SendEmail(subject, config.RootUserEmail, content)
|
||||
if err != nil {
|
||||
logger.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// DisableChannel disable & notify
|
||||
func DisableChannel(channelId int, channelName string, reason string) {
|
||||
model.UpdateChannelStatusById(channelId, model.ChannelStatusAutoDisabled)
|
||||
logger.SysLog(fmt.Sprintf("channel #%d has been disabled: %s", channelId, reason))
|
||||
subject := fmt.Sprintf("渠道状态变更提醒")
|
||||
content := message.EmailTemplate(
|
||||
subject,
|
||||
fmt.Sprintf(`
|
||||
<p>您好!</p>
|
||||
<p>渠道「<strong>%s</strong>」(#%d)已被禁用。</p>
|
||||
<p>禁用原因:</p>
|
||||
<p style="background-color: #f8f8f8; padding: 10px; border-radius: 4px;">%s</p>
|
||||
`, channelName, channelId, reason),
|
||||
)
|
||||
notifyRootUser(subject, content)
|
||||
}
|
||||
|
||||
func MetricDisableChannel(channelId int, successRate float64) {
|
||||
model.UpdateChannelStatusById(channelId, model.ChannelStatusAutoDisabled)
|
||||
logger.SysLog(fmt.Sprintf("channel #%d has been disabled due to low success rate: %.2f", channelId, successRate*100))
|
||||
subject := fmt.Sprintf("渠道状态变更提醒")
|
||||
content := message.EmailTemplate(
|
||||
subject,
|
||||
fmt.Sprintf(`
|
||||
<p>您好!</p>
|
||||
<p>渠道 #%d 已被系统自动禁用。</p>
|
||||
<p>禁用原因:</p>
|
||||
<p style="background-color: #f8f8f8; padding: 10px; border-radius: 4px;">该渠道在最近 %d 次调用中成功率为 <strong>%.2f%%</strong>,低于系统阈值 <strong>%.2f%%</strong>。</p>
|
||||
`, channelId, config.MetricQueueSize, successRate*100, config.MetricSuccessRateThreshold*100),
|
||||
)
|
||||
notifyRootUser(subject, content)
|
||||
}
|
||||
|
||||
// EnableChannel enable & notify
|
||||
func EnableChannel(channelId int, channelName string) {
|
||||
model.UpdateChannelStatusById(channelId, model.ChannelStatusEnabled)
|
||||
logger.SysLog(fmt.Sprintf("channel #%d has been enabled", channelId))
|
||||
subject := fmt.Sprintf("渠道状态变更提醒")
|
||||
content := message.EmailTemplate(
|
||||
subject,
|
||||
fmt.Sprintf(`
|
||||
<p>您好!</p>
|
||||
<p>渠道「<strong>%s</strong>」(#%d)已被重新启用。</p>
|
||||
<p>您现在可以继续使用该渠道了。</p>
|
||||
`, channelName, channelId),
|
||||
)
|
||||
notifyRootUser(subject, content)
|
||||
}
|
||||
57
monitor/manage.go
Normal file
57
monitor/manage.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/songquanpeng/one-api/common/config"
|
||||
"github.com/songquanpeng/one-api/relay/model"
|
||||
)
|
||||
|
||||
func ShouldDisableChannel(err *model.Error, statusCode int) bool {
|
||||
if !config.AutomaticDisableChannelEnabled {
|
||||
return false
|
||||
}
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if statusCode == http.StatusUnauthorized {
|
||||
return true
|
||||
}
|
||||
switch err.Type {
|
||||
case "insufficient_quota", "authentication_error", "permission_error", "forbidden":
|
||||
return true
|
||||
}
|
||||
if err.Code == "invalid_api_key" || err.Code == "account_deactivated" {
|
||||
return true
|
||||
}
|
||||
|
||||
lowerMessage := strings.ToLower(err.Message)
|
||||
if strings.Contains(lowerMessage, "your access was terminated") ||
|
||||
strings.Contains(lowerMessage, "violation of our policies") ||
|
||||
strings.Contains(lowerMessage, "your credit balance is too low") ||
|
||||
strings.Contains(lowerMessage, "organization has been disabled") ||
|
||||
strings.Contains(lowerMessage, "credit") ||
|
||||
strings.Contains(lowerMessage, "balance") ||
|
||||
strings.Contains(lowerMessage, "permission denied") ||
|
||||
strings.Contains(lowerMessage, "organization has been restricted") || // groq
|
||||
strings.Contains(lowerMessage, "api key not valid") || // gemini
|
||||
strings.Contains(lowerMessage, "api key expired") || // gemini
|
||||
strings.Contains(lowerMessage, "已欠费") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ShouldEnableChannel(err error, openAIErr *model.Error) bool {
|
||||
if !config.AutomaticEnableChannelEnabled {
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if openAIErr != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
79
monitor/metric.go
Normal file
79
monitor/metric.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"github.com/songquanpeng/one-api/common/config"
|
||||
)
|
||||
|
||||
var store = make(map[int][]bool)
|
||||
var metricSuccessChan = make(chan int, config.MetricSuccessChanSize)
|
||||
var metricFailChan = make(chan int, config.MetricFailChanSize)
|
||||
|
||||
func consumeSuccess(channelId int) {
|
||||
if len(store[channelId]) > config.MetricQueueSize {
|
||||
store[channelId] = store[channelId][1:]
|
||||
}
|
||||
store[channelId] = append(store[channelId], true)
|
||||
}
|
||||
|
||||
func consumeFail(channelId int) (bool, float64) {
|
||||
if len(store[channelId]) > config.MetricQueueSize {
|
||||
store[channelId] = store[channelId][1:]
|
||||
}
|
||||
store[channelId] = append(store[channelId], false)
|
||||
successCount := 0
|
||||
for _, success := range store[channelId] {
|
||||
if success {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
successRate := float64(successCount) / float64(len(store[channelId]))
|
||||
if len(store[channelId]) < config.MetricQueueSize {
|
||||
return false, successRate
|
||||
}
|
||||
if successRate < config.MetricSuccessRateThreshold {
|
||||
store[channelId] = make([]bool, 0)
|
||||
return true, successRate
|
||||
}
|
||||
return false, successRate
|
||||
}
|
||||
|
||||
func metricSuccessConsumer() {
|
||||
for {
|
||||
select {
|
||||
case channelId := <-metricSuccessChan:
|
||||
consumeSuccess(channelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func metricFailConsumer() {
|
||||
for {
|
||||
select {
|
||||
case channelId := <-metricFailChan:
|
||||
disable, successRate := consumeFail(channelId)
|
||||
if disable {
|
||||
go MetricDisableChannel(channelId, successRate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
if config.EnableMetric {
|
||||
go metricSuccessConsumer()
|
||||
go metricFailConsumer()
|
||||
}
|
||||
}
|
||||
|
||||
func Emit(channelId int, success bool) {
|
||||
if !config.EnableMetric {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if success {
|
||||
metricSuccessChan <- channelId
|
||||
} else {
|
||||
metricFailChan <- channelId
|
||||
}
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user