You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

229 lines
8.3 KiB

  1. package service
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "github.com/QuantumNous/new-api/common"
  7. "github.com/QuantumNous/new-api/constant"
  8. "github.com/QuantumNous/new-api/logger"
  9. "github.com/QuantumNous/new-api/model"
  10. "github.com/QuantumNous/new-api/relay/helper"
  11. "github.com/QuantumNous/new-api/setting"
  12. "github.com/QuantumNous/new-api/types"
  13. "github.com/gin-gonic/gin"
  14. )
  15. type RetryParam struct {
  16. Ctx *gin.Context
  17. TokenGroup string
  18. ModelName string
  19. Retry *int
  20. RequireMatrixUsageBilling bool
  21. RequiredChannelType int
  22. AllowedChannelTypes []int
  23. resetNextTry bool
  24. }
  25. func (p *RetryParam) GetRetry() int {
  26. if p.Retry == nil {
  27. return 0
  28. }
  29. return *p.Retry
  30. }
  31. func (p *RetryParam) SetRetry(retry int) {
  32. p.Retry = &retry
  33. }
  34. func (p *RetryParam) IncreaseRetry() {
  35. if p.resetNextTry {
  36. p.resetNextTry = false
  37. return
  38. }
  39. if p.Retry == nil {
  40. p.Retry = new(int)
  41. }
  42. *p.Retry++
  43. }
  44. func (p *RetryParam) ResetRetryNextTry() {
  45. p.resetNextTry = true
  46. }
  47. // CacheGetRandomSatisfiedChannel tries to get a random channel that satisfies the requirements.
  48. // 尝试获取一个满足要求的随机渠道。
  49. //
  50. // For "auto" tokenGroup with cross-group Retry enabled:
  51. // 对于启用了跨分组重试的 "auto" tokenGroup:
  52. //
  53. // - Each group will exhaust all its priorities before moving to the next group.
  54. // 每个分组会用完所有优先级后才会切换到下一个分组。
  55. //
  56. // - Uses ContextKeyAutoGroupIndex to track current group index.
  57. // 使用 ContextKeyAutoGroupIndex 跟踪当前分组索引。
  58. //
  59. // - Uses ContextKeyAutoGroupRetryIndex to track the global Retry count when current group started.
  60. // 使用 ContextKeyAutoGroupRetryIndex 跟踪当前分组开始时的全局重试次数。
  61. //
  62. // - priorityRetry = Retry - startRetryIndex, represents the priority level within current group.
  63. // priorityRetry = Retry - startRetryIndex,表示当前分组内的优先级级别。
  64. //
  65. // - When GetRandomSatisfiedChannel returns nil (priorities exhausted), moves to next group.
  66. // 当 GetRandomSatisfiedChannel 返回 nil(优先级用完)时,切换到下一个分组。
  67. //
  68. // Example flow (2 groups, each with 2 priorities, RetryTimes=3):
  69. // 示例流程(2个分组,每个有2个优先级,RetryTimes=3):
  70. //
  71. // Retry=0: GroupA, priority0 (startRetryIndex=0, priorityRetry=0)
  72. // 分组A, 优先级0
  73. //
  74. // Retry=1: GroupA, priority1 (startRetryIndex=0, priorityRetry=1)
  75. // 分组A, 优先级1
  76. //
  77. // Retry=2: GroupA exhausted → GroupB, priority0 (startRetryIndex=2, priorityRetry=0)
  78. // 分组A用完 → 分组B, 优先级0
  79. //
  80. // Retry=3: GroupB, priority1 (startRetryIndex=2, priorityRetry=1)
  81. // 分组B, 优先级1
  82. func CacheGetRandomSatisfiedChannel(param *RetryParam) (*model.Channel, string, error) {
  83. var channel *model.Channel
  84. var err error
  85. selectGroup := param.TokenGroup
  86. userGroup := common.GetContextKeyString(param.Ctx, constant.ContextKeyUserGroup)
  87. channelFilter := matrixUsageBillingChannelFilter(param)
  88. if param.TokenGroup == "auto" {
  89. if len(setting.GetAutoGroups()) == 0 {
  90. return nil, selectGroup, errors.New("auto groups is not enabled")
  91. }
  92. autoGroups := GetUserAutoGroup(userGroup)
  93. // startGroupIndex: the group index to start searching from
  94. // startGroupIndex: 开始搜索的分组索引
  95. startGroupIndex := 0
  96. crossGroupRetry := common.GetContextKeyBool(param.Ctx, constant.ContextKeyTokenCrossGroupRetry)
  97. if lastGroupIndex, exists := common.GetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex); exists {
  98. if idx, ok := lastGroupIndex.(int); ok {
  99. startGroupIndex = idx
  100. }
  101. }
  102. for i := startGroupIndex; i < len(autoGroups); i++ {
  103. autoGroup := autoGroups[i]
  104. // Calculate priorityRetry for current group
  105. // 计算当前分组的 priorityRetry
  106. priorityRetry := param.GetRetry()
  107. // If moved to a new group, reset priorityRetry and update startRetryIndex
  108. // 如果切换到新分组,重置 priorityRetry 并更新 startRetryIndex
  109. if i > startGroupIndex {
  110. priorityRetry = 0
  111. }
  112. logger.LogDebug(param.Ctx, "Auto selecting group: %s, priorityRetry: %d", autoGroup, priorityRetry)
  113. channel, err = model.GetRandomSatisfiedChannelWithFilter(autoGroup, param.ModelName, priorityRetry, channelFilter)
  114. if err != nil {
  115. return nil, selectGroup, err
  116. }
  117. if channel == nil {
  118. // Current group has no available channel for this model, try next group
  119. // 当前分组没有该模型的可用渠道,尝试下一个分组
  120. logger.LogDebug(param.Ctx, "No available channel in group %s for model %s at priorityRetry %d, trying next group", autoGroup, param.ModelName, priorityRetry)
  121. // 重置状态以尝试下一个分组
  122. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex, i+1)
  123. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupRetryIndex, 0)
  124. // Reset retry counter so outer loop can continue for next group
  125. // 重置重试计数器,以便外层循环可以为下一个分组继续
  126. param.SetRetry(0)
  127. continue
  128. }
  129. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroup, autoGroup)
  130. selectGroup = autoGroup
  131. logger.LogDebug(param.Ctx, "Auto selected group: %s", autoGroup)
  132. // Prepare state for next retry
  133. // 为下一次重试准备状态
  134. if crossGroupRetry && priorityRetry >= common.RetryTimes {
  135. // Current group has exhausted all retries, prepare to switch to next group
  136. // This request still uses current group, but next retry will use next group
  137. // 当前分组已用完所有重试次数,准备切换到下一个分组
  138. // 本次请求仍使用当前分组,但下次重试将使用下一个分组
  139. logger.LogDebug(param.Ctx, "Current group %s retries exhausted (priorityRetry=%d >= RetryTimes=%d), preparing switch to next group for next retry", autoGroup, priorityRetry, common.RetryTimes)
  140. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex, i+1)
  141. // Reset retry counter so outer loop can continue for next group
  142. // 重置重试计数器,以便外层循环可以为下一个分组继续
  143. param.SetRetry(0)
  144. param.ResetRetryNextTry()
  145. } else {
  146. // Stay in current group, save current state
  147. // 保持在当前分组,保存当前状态
  148. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex, i)
  149. }
  150. break
  151. }
  152. } else {
  153. channel, err = model.GetRandomSatisfiedChannelWithFilter(param.TokenGroup, param.ModelName, param.GetRetry(), channelFilter)
  154. if err != nil {
  155. return nil, param.TokenGroup, err
  156. }
  157. }
  158. if channel == nil && param.RequireMatrixUsageBilling {
  159. return nil, selectGroup, matrixUsageBillingChannelUnavailableError()
  160. }
  161. if channel != nil && param.RequireMatrixUsageBilling {
  162. mappedModel, mapErr := helper.ResolvePricingModelMapping(param.ModelName, channel.GetModelMapping())
  163. if mapErr != nil {
  164. return nil, selectGroup, mapErr
  165. }
  166. if !helper.SupportsMatrixUsageBilling(param.ModelName, channel.Type, mappedModel) {
  167. return nil, selectGroup, matrixUsageBillingChannelUnavailableError()
  168. }
  169. }
  170. return channel, selectGroup, nil
  171. }
  172. func matrixUsageBillingChannelFilter(param *RetryParam) model.ChannelFilter {
  173. if !param.RequireMatrixUsageBilling && param.RequiredChannelType == 0 && len(param.AllowedChannelTypes) == 0 {
  174. return nil
  175. }
  176. return func(channel *model.Channel) (bool, error) {
  177. if param.RequiredChannelType != 0 && channel.Type != param.RequiredChannelType {
  178. return false, nil
  179. }
  180. if !channelTypeAllowed(channel.Type, param.AllowedChannelTypes) {
  181. return false, nil
  182. }
  183. if param.RequireMatrixUsageBilling {
  184. mappedModel, err := helper.ResolvePricingModelMapping(param.ModelName, channel.GetModelMapping())
  185. if err != nil {
  186. return false, err
  187. }
  188. return helper.SupportsMatrixUsageBilling(param.ModelName, channel.Type, mappedModel), nil
  189. }
  190. return true, nil
  191. }
  192. }
  193. func channelTypeAllowed(channelType int, allowed []int) bool {
  194. if len(allowed) == 0 {
  195. return true
  196. }
  197. for _, allowedType := range allowed {
  198. if channelType == allowedType {
  199. return true
  200. }
  201. }
  202. return false
  203. }
  204. func matrixUsageBillingChannelUnavailableError() *types.NewAPIError {
  205. return types.NewErrorWithStatusCode(
  206. fmt.Errorf("matrix_usage_billing_channel_unavailable"),
  207. types.ErrorCode("matrix_usage_billing_channel_unavailable"),
  208. http.StatusBadRequest,
  209. types.ErrOptionWithSkipRetry(),
  210. )
  211. }