channel_select.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package service
  2. import (
  3. "errors"
  4. "github.com/QuantumNous/new-api/common"
  5. "github.com/QuantumNous/new-api/constant"
  6. "github.com/QuantumNous/new-api/logger"
  7. "github.com/QuantumNous/new-api/model"
  8. "github.com/QuantumNous/new-api/setting"
  9. "github.com/gin-gonic/gin"
  10. )
  11. type RetryParam struct {
  12. Ctx *gin.Context
  13. TokenGroup string
  14. ModelName string
  15. Retry *int
  16. }
  17. func (p *RetryParam) GetRetry() int {
  18. if p.Retry == nil {
  19. return 0
  20. }
  21. return *p.Retry
  22. }
  23. func (p *RetryParam) SetRetry(retry int) {
  24. p.Retry = &retry
  25. }
  26. func (p *RetryParam) IncreaseRetry() {
  27. if p.Retry == nil {
  28. p.Retry = new(int)
  29. }
  30. *p.Retry++
  31. }
  32. // CacheGetRandomSatisfiedChannel tries to get a random channel that satisfies the requirements.
  33. // 尝试获取一个满足要求的随机渠道。
  34. //
  35. // For "auto" tokenGroup with cross-group Retry enabled:
  36. // 对于启用了跨分组重试的 "auto" tokenGroup:
  37. //
  38. // - Each group will exhaust all its priorities before moving to the next group.
  39. // 每个分组会用完所有优先级后才会切换到下一个分组。
  40. //
  41. // - Uses ContextKeyAutoGroupIndex to track current group index.
  42. // 使用 ContextKeyAutoGroupIndex 跟踪当前分组索引。
  43. //
  44. // - Uses ContextKeyAutoGroupRetryIndex to track the global Retry count when current group started.
  45. // 使用 ContextKeyAutoGroupRetryIndex 跟踪当前分组开始时的全局重试次数。
  46. //
  47. // - priorityRetry = Retry - startRetryIndex, represents the priority level within current group.
  48. // priorityRetry = Retry - startRetryIndex,表示当前分组内的优先级级别。
  49. //
  50. // - When GetRandomSatisfiedChannel returns nil (priorities exhausted), moves to next group.
  51. // 当 GetRandomSatisfiedChannel 返回 nil(优先级用完)时,切换到下一个分组。
  52. //
  53. // Example flow (2 groups, each with 2 priorities, RetryTimes=3):
  54. // 示例流程(2个分组,每个有2个优先级,RetryTimes=3):
  55. //
  56. // Retry=0: GroupA, priority0 (startRetryIndex=0, priorityRetry=0)
  57. // 分组A, 优先级0
  58. //
  59. // Retry=1: GroupA, priority1 (startRetryIndex=0, priorityRetry=1)
  60. // 分组A, 优先级1
  61. //
  62. // Retry=2: GroupA exhausted → GroupB, priority0 (startRetryIndex=2, priorityRetry=0)
  63. // 分组A用完 → 分组B, 优先级0
  64. //
  65. // Retry=3: GroupB, priority1 (startRetryIndex=2, priorityRetry=1)
  66. // 分组B, 优先级1
  67. func CacheGetRandomSatisfiedChannel(param *RetryParam) (*model.Channel, string, error) {
  68. var channel *model.Channel
  69. var err error
  70. selectGroup := param.TokenGroup
  71. userGroup := common.GetContextKeyString(param.Ctx, constant.ContextKeyUserGroup)
  72. if param.TokenGroup == "auto" {
  73. if len(setting.GetAutoGroups()) == 0 {
  74. return nil, selectGroup, errors.New("auto groups is not enabled")
  75. }
  76. autoGroups := GetUserAutoGroup(userGroup)
  77. // startGroupIndex: the group index to start searching from
  78. // startGroupIndex: 开始搜索的分组索引
  79. startGroupIndex := 0
  80. crossGroupRetry := common.GetContextKeyBool(param.Ctx, constant.ContextKeyTokenCrossGroupRetry)
  81. if lastGroupIndex, exists := common.GetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex); exists {
  82. if idx, ok := lastGroupIndex.(int); ok {
  83. startGroupIndex = idx
  84. }
  85. }
  86. for i := startGroupIndex; i < len(autoGroups); i++ {
  87. autoGroup := autoGroups[i]
  88. // Calculate priorityRetry for current group
  89. // 计算当前分组的 priorityRetry
  90. priorityRetry := param.GetRetry()
  91. // If moved to a new group, reset priorityRetry and update startRetryIndex
  92. // 如果切换到新分组,重置 priorityRetry 并更新 startRetryIndex
  93. if i > startGroupIndex {
  94. priorityRetry = 0
  95. }
  96. logger.LogDebug(param.Ctx, "Auto selecting group: %s, priorityRetry: %d", autoGroup, priorityRetry)
  97. channel, _ = model.GetRandomSatisfiedChannel(autoGroup, param.ModelName, priorityRetry)
  98. if channel == nil {
  99. // Current group has no available channel for this model, try next group
  100. // 当前分组没有该模型的可用渠道,尝试下一个分组
  101. logger.LogDebug(param.Ctx, "No available channel in group %s for model %s at priorityRetry %d, trying next group", autoGroup, param.ModelName, priorityRetry)
  102. // 重置状态以尝试下一个分组
  103. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex, i+1)
  104. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupRetryIndex, 0)
  105. // Reset retry counter so outer loop can continue for next group
  106. // 重置重试计数器,以便外层循环可以为下一个分组继续
  107. param.SetRetry(0)
  108. continue
  109. }
  110. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroup, autoGroup)
  111. selectGroup = autoGroup
  112. logger.LogDebug(param.Ctx, "Auto selected group: %s", autoGroup)
  113. // Prepare state for next retry
  114. // 为下一次重试准备状态
  115. if crossGroupRetry && priorityRetry >= common.RetryTimes {
  116. // Current group has exhausted all retries, prepare to switch to next group
  117. // This request still uses current group, but next retry will use next group
  118. // 当前分组已用完所有重试次数,准备切换到下一个分组
  119. // 本次请求仍使用当前分组,但下次重试将使用下一个分组
  120. 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)
  121. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex, i+1)
  122. // Reset retry counter so outer loop can continue for next group
  123. // 重置重试计数器,以便外层循环可以为下一个分组继续
  124. param.SetRetry(-1)
  125. } else {
  126. // Stay in current group, save current state
  127. // 保持在当前分组,保存当前状态
  128. common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex, i)
  129. }
  130. break
  131. }
  132. } else {
  133. channel, err = model.GetRandomSatisfiedChannel(param.TokenGroup, param.ModelName, param.GetRetry())
  134. if err != nil {
  135. return nil, param.TokenGroup, err
  136. }
  137. }
  138. return channel, selectGroup, nil
  139. }