|
|
|
@@ -53,99 +53,127 @@ func Distribute() func(c *gin.Context) { |
|
|
|
} |
|
|
|
} else { |
|
|
|
// Select a channel for the user |
|
|
|
// check token model mapping |
|
|
|
modelLimitEnable := common.GetContextKeyBool(c, constant.ContextKeyTokenModelLimitEnabled) |
|
|
|
if modelLimitEnable { |
|
|
|
s, ok := common.GetContextKey(c, constant.ContextKeyTokenModelLimit) |
|
|
|
// Check if token has a bound channel |
|
|
|
boundChannelId, hasBoundChannel := common.GetContextKey(c, constant.ContextKeyTokenBoundChannelId) |
|
|
|
if hasBoundChannel { |
|
|
|
id, ok := boundChannelId.(int) |
|
|
|
if !ok { |
|
|
|
// token model limit is empty, all models are not allowed |
|
|
|
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorTokenNoModelAccess)) |
|
|
|
abortWithOpenAiMessage(c, http.StatusBadRequest, i18n.T(c, i18n.MsgDistributorInvalidChannelId)) |
|
|
|
return |
|
|
|
} |
|
|
|
var tokenModelLimit map[string]bool |
|
|
|
tokenModelLimit, ok = s.(map[string]bool) |
|
|
|
if !ok { |
|
|
|
tokenModelLimit = map[string]bool{} |
|
|
|
} |
|
|
|
matchName := ratio_setting.FormatMatchingModelName(modelRequest.Model) // match gpts & thinking-* |
|
|
|
if _, ok := tokenModelLimit[matchName]; !ok { |
|
|
|
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorTokenModelForbidden, map[string]any{"Model": modelRequest.Model})) |
|
|
|
channel, err = model.GetChannelById(id, true) |
|
|
|
if err != nil { |
|
|
|
abortWithOpenAiMessage(c, http.StatusBadRequest, i18n.T(c, i18n.MsgDistributorInvalidChannelId)) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if shouldSelectChannel { |
|
|
|
if modelRequest.Model == "" { |
|
|
|
abortWithOpenAiMessage(c, http.StatusBadRequest, i18n.T(c, i18n.MsgDistributorModelNameRequired)) |
|
|
|
if channel.Status != common.ChannelStatusEnabled { |
|
|
|
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorChannelDisabled)) |
|
|
|
return |
|
|
|
} |
|
|
|
var selectGroup string |
|
|
|
usingGroup := common.GetContextKeyString(c, constant.ContextKeyUsingGroup) |
|
|
|
// check path is /pg/chat/completions |
|
|
|
if strings.HasPrefix(c.Request.URL.Path, "/pg/chat/completions") { |
|
|
|
playgroundRequest := &dto.PlayGroundRequest{} |
|
|
|
err = common.UnmarshalBodyReusable(c, playgroundRequest) |
|
|
|
if err != nil { |
|
|
|
abortWithOpenAiMessage(c, http.StatusBadRequest, i18n.T(c, i18n.MsgDistributorInvalidPlayground, map[string]any{"Error": err.Error()})) |
|
|
|
// Verify the bound channel supports the requested model |
|
|
|
if shouldSelectChannel && modelRequest.Model != "" { |
|
|
|
usingGroup := common.GetContextKeyString(c, constant.ContextKeyUsingGroup) |
|
|
|
if !model.IsChannelEnabledForGroupModel(usingGroup, modelRequest.Model, channel.Id) { |
|
|
|
abortWithOpenAiMessage(c, http.StatusServiceUnavailable, i18n.T(c, i18n.MsgDistributorNoAvailableChannel, map[string]any{"Group": usingGroup, "Model": modelRequest.Model}), types.ErrorCodeModelNotFound) |
|
|
|
return |
|
|
|
} |
|
|
|
if playgroundRequest.Group != "" { |
|
|
|
if !service.GroupInUserUsableGroups(usingGroup, playgroundRequest.Group) && playgroundRequest.Group != usingGroup { |
|
|
|
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorGroupAccessDenied)) |
|
|
|
return |
|
|
|
} |
|
|
|
usingGroup = playgroundRequest.Group |
|
|
|
common.SetContextKey(c, constant.ContextKeyUsingGroup, usingGroup) |
|
|
|
} |
|
|
|
} else { |
|
|
|
// Normal channel selection logic |
|
|
|
// check token model mapping |
|
|
|
modelLimitEnable := common.GetContextKeyBool(c, constant.ContextKeyTokenModelLimitEnabled) |
|
|
|
if modelLimitEnable { |
|
|
|
s, ok := common.GetContextKey(c, constant.ContextKeyTokenModelLimit) |
|
|
|
if !ok { |
|
|
|
// token model limit is empty, all models are not allowed |
|
|
|
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorTokenNoModelAccess)) |
|
|
|
return |
|
|
|
} |
|
|
|
var tokenModelLimit map[string]bool |
|
|
|
tokenModelLimit, ok = s.(map[string]bool) |
|
|
|
if !ok { |
|
|
|
tokenModelLimit = map[string]bool{} |
|
|
|
} |
|
|
|
matchName := ratio_setting.FormatMatchingModelName(modelRequest.Model) // match gpts & thinking-* |
|
|
|
if _, ok := tokenModelLimit[matchName]; !ok { |
|
|
|
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorTokenModelForbidden, map[string]any{"Model": modelRequest.Model})) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if preferredChannelID, found := service.GetPreferredChannelByAffinity(c, modelRequest.Model, usingGroup); found { |
|
|
|
preferred, err := model.CacheGetChannel(preferredChannelID) |
|
|
|
if err == nil && preferred != nil && preferred.Status == common.ChannelStatusEnabled { |
|
|
|
if usingGroup == "auto" { |
|
|
|
userGroup := common.GetContextKeyString(c, constant.ContextKeyUserGroup) |
|
|
|
autoGroups := service.GetUserAutoGroup(userGroup) |
|
|
|
for _, g := range autoGroups { |
|
|
|
if model.IsChannelEnabledForGroupModel(g, modelRequest.Model, preferred.Id) { |
|
|
|
selectGroup = g |
|
|
|
common.SetContextKey(c, constant.ContextKeyAutoGroup, g) |
|
|
|
channel = preferred |
|
|
|
service.MarkChannelAffinityUsed(c, g, preferred.Id) |
|
|
|
break |
|
|
|
} |
|
|
|
if shouldSelectChannel { |
|
|
|
if modelRequest.Model == "" { |
|
|
|
abortWithOpenAiMessage(c, http.StatusBadRequest, i18n.T(c, i18n.MsgDistributorModelNameRequired)) |
|
|
|
return |
|
|
|
} |
|
|
|
var selectGroup string |
|
|
|
usingGroup := common.GetContextKeyString(c, constant.ContextKeyUsingGroup) |
|
|
|
// check path is /pg/chat/completions |
|
|
|
if strings.HasPrefix(c.Request.URL.Path, "/pg/chat/completions") { |
|
|
|
playgroundRequest := &dto.PlayGroundRequest{} |
|
|
|
err = common.UnmarshalBodyReusable(c, playgroundRequest) |
|
|
|
if err != nil { |
|
|
|
abortWithOpenAiMessage(c, http.StatusBadRequest, i18n.T(c, i18n.MsgDistributorInvalidPlayground, map[string]any{"Error": err.Error()})) |
|
|
|
return |
|
|
|
} |
|
|
|
if playgroundRequest.Group != "" { |
|
|
|
if !service.GroupInUserUsableGroups(usingGroup, playgroundRequest.Group) && playgroundRequest.Group != usingGroup { |
|
|
|
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorGroupAccessDenied)) |
|
|
|
return |
|
|
|
} |
|
|
|
} else if model.IsChannelEnabledForGroupModel(usingGroup, modelRequest.Model, preferred.Id) { |
|
|
|
channel = preferred |
|
|
|
selectGroup = usingGroup |
|
|
|
service.MarkChannelAffinityUsed(c, usingGroup, preferred.Id) |
|
|
|
usingGroup = playgroundRequest.Group |
|
|
|
common.SetContextKey(c, constant.ContextKeyUsingGroup, usingGroup) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if channel == nil { |
|
|
|
channel, selectGroup, err = service.CacheGetRandomSatisfiedChannel(&service.RetryParam{ |
|
|
|
Ctx: c, |
|
|
|
ModelName: modelRequest.Model, |
|
|
|
TokenGroup: usingGroup, |
|
|
|
Retry: common.GetPointer(0), |
|
|
|
}) |
|
|
|
if err != nil { |
|
|
|
showGroup := usingGroup |
|
|
|
if usingGroup == "auto" { |
|
|
|
showGroup = fmt.Sprintf("auto(%s)", selectGroup) |
|
|
|
if preferredChannelID, found := service.GetPreferredChannelByAffinity(c, modelRequest.Model, usingGroup); found { |
|
|
|
preferred, err := model.CacheGetChannel(preferredChannelID) |
|
|
|
if err == nil && preferred != nil && preferred.Status == common.ChannelStatusEnabled { |
|
|
|
if usingGroup == "auto" { |
|
|
|
userGroup := common.GetContextKeyString(c, constant.ContextKeyUserGroup) |
|
|
|
autoGroups := service.GetUserAutoGroup(userGroup) |
|
|
|
for _, g := range autoGroups { |
|
|
|
if model.IsChannelEnabledForGroupModel(g, modelRequest.Model, preferred.Id) { |
|
|
|
selectGroup = g |
|
|
|
common.SetContextKey(c, constant.ContextKeyAutoGroup, g) |
|
|
|
channel = preferred |
|
|
|
service.MarkChannelAffinityUsed(c, g, preferred.Id) |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
} else if model.IsChannelEnabledForGroupModel(usingGroup, modelRequest.Model, preferred.Id) { |
|
|
|
channel = preferred |
|
|
|
selectGroup = usingGroup |
|
|
|
service.MarkChannelAffinityUsed(c, usingGroup, preferred.Id) |
|
|
|
} |
|
|
|
} |
|
|
|
message := i18n.T(c, i18n.MsgDistributorGetChannelFailed, map[string]any{"Group": showGroup, "Model": modelRequest.Model, "Error": err.Error()}) |
|
|
|
// 如果错误,但是渠道不为空,说明是数据库一致性问题 |
|
|
|
//if channel != nil { |
|
|
|
// common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) |
|
|
|
// message = "数据库一致性已被破坏,请联系管理员" |
|
|
|
//} |
|
|
|
abortWithOpenAiMessage(c, http.StatusServiceUnavailable, message, types.ErrorCodeModelNotFound) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
if channel == nil { |
|
|
|
abortWithOpenAiMessage(c, http.StatusServiceUnavailable, i18n.T(c, i18n.MsgDistributorNoAvailableChannel, map[string]any{"Group": usingGroup, "Model": modelRequest.Model}), types.ErrorCodeModelNotFound) |
|
|
|
return |
|
|
|
channel, selectGroup, err = service.CacheGetRandomSatisfiedChannel(&service.RetryParam{ |
|
|
|
Ctx: c, |
|
|
|
ModelName: modelRequest.Model, |
|
|
|
TokenGroup: usingGroup, |
|
|
|
Retry: common.GetPointer(0), |
|
|
|
}) |
|
|
|
if err != nil { |
|
|
|
showGroup := usingGroup |
|
|
|
if usingGroup == "auto" { |
|
|
|
showGroup = fmt.Sprintf("auto(%s)", selectGroup) |
|
|
|
} |
|
|
|
message := i18n.T(c, i18n.MsgDistributorGetChannelFailed, map[string]any{"Group": showGroup, "Model": modelRequest.Model, "Error": err.Error()}) |
|
|
|
// 如果错误,但是渠道不为空,说明是数据库一致性问题 |
|
|
|
//if channel != nil { |
|
|
|
// common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) |
|
|
|
// message = "数据库一致性已被破坏,请联系管理员" |
|
|
|
//} |
|
|
|
abortWithOpenAiMessage(c, http.StatusServiceUnavailable, message, types.ErrorCodeModelNotFound) |
|
|
|
return |
|
|
|
} |
|
|
|
if channel == nil { |
|
|
|
abortWithOpenAiMessage(c, http.StatusServiceUnavailable, i18n.T(c, i18n.MsgDistributorNoAvailableChannel, map[string]any{"Group": usingGroup, "Model": modelRequest.Model}), types.ErrorCodeModelNotFound) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|