2025-10-28 23:25:43 +08:00
package service
import (
"errors"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/logger"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/setting"
"github.com/gin-gonic/gin"
)
2025-12-13 16:43:38 +08:00
type RetryParam struct {
2025-12-13 18:09:10 +08:00
Ctx * gin . Context
TokenGroup string
ModelName string
Retry * int
resetNextTry bool
2025-12-13 16:43:38 +08:00
}
func ( p * RetryParam ) GetRetry ( ) int {
if p . Retry == nil {
return 0
}
return * p . Retry
}
func ( p * RetryParam ) SetRetry ( retry int ) {
p . Retry = & retry
}
func ( p * RetryParam ) IncreaseRetry ( ) {
2025-12-13 18:09:10 +08:00
if p . resetNextTry {
p . resetNextTry = false
return
}
2025-12-13 16:43:38 +08:00
if p . Retry == nil {
p . Retry = new ( int )
}
* p . Retry ++
}
2025-12-13 18:09:10 +08:00
func ( p * RetryParam ) ResetRetryNextTry ( ) {
p . resetNextTry = true
}
2025-12-12 17:59:21 +08:00
// CacheGetRandomSatisfiedChannel tries to get a random channel that satisfies the requirements.
2025-12-13 16:43:38 +08:00
// 尝试获取一个满足要求的随机渠道。
//
// For "auto" tokenGroup with cross-group Retry enabled:
// 对于启用了跨分组重试的 "auto" tokenGroup:
//
// - Each group will exhaust all its priorities before moving to the next group.
// 每个分组会用完所有优先级后才会切换到下一个分组。
//
// - Uses ContextKeyAutoGroupIndex to track current group index.
// 使用 ContextKeyAutoGroupIndex 跟踪当前分组索引。
//
// - Uses ContextKeyAutoGroupRetryIndex to track the global Retry count when current group started.
// 使用 ContextKeyAutoGroupRetryIndex 跟踪当前分组开始时的全局重试次数。
//
// - priorityRetry = Retry - startRetryIndex, represents the priority level within current group.
// priorityRetry = Retry - startRetryIndex, 表示当前分组内的优先级级别。
//
// - When GetRandomSatisfiedChannel returns nil (priorities exhausted), moves to next group.
// 当 GetRandomSatisfiedChannel 返回 nil( 优先级用完) 时, 切换到下一个分组。
//
// Example flow (2 groups, each with 2 priorities, RetryTimes=3):
// 示例流程( 2个分组, 每个有2个优先级, RetryTimes=3) :
//
// Retry=0: GroupA, priority0 (startRetryIndex=0, priorityRetry=0)
// 分组A, 优先级0
//
// Retry=1: GroupA, priority1 (startRetryIndex=0, priorityRetry=1)
// 分组A, 优先级1
//
// Retry=2: GroupA exhausted → GroupB, priority0 (startRetryIndex=2, priorityRetry=0)
// 分组A用完 → 分组B, 优先级0
//
// Retry=3: GroupB, priority1 (startRetryIndex=2, priorityRetry=1)
// 分组B, 优先级1
func CacheGetRandomSatisfiedChannel ( param * RetryParam ) ( * model . Channel , string , error ) {
2025-10-28 23:25:43 +08:00
var channel * model . Channel
var err error
2025-12-13 16:43:38 +08:00
selectGroup := param . TokenGroup
userGroup := common . GetContextKeyString ( param . Ctx , constant . ContextKeyUserGroup )
if param . TokenGroup == "auto" {
2025-10-28 23:25:43 +08:00
if len ( setting . GetAutoGroups ( ) ) == 0 {
return nil , selectGroup , errors . New ( "auto groups is not enabled" )
}
2025-12-12 17:59:21 +08:00
autoGroups := GetUserAutoGroup ( userGroup )
2025-12-13 16:43:38 +08:00
// startGroupIndex: the group index to start searching from
// startGroupIndex: 开始搜索的分组索引
startGroupIndex := 0
crossGroupRetry := common . GetContextKeyBool ( param . Ctx , constant . ContextKeyTokenCrossGroupRetry )
if lastGroupIndex , exists := common . GetContextKey ( param . Ctx , constant . ContextKeyAutoGroupIndex ) ; exists {
if idx , ok := lastGroupIndex . ( int ) ; ok {
startGroupIndex = idx
2025-12-12 17:59:21 +08:00
}
}
2025-12-13 01:04:10 +08:00
2025-12-13 16:43:38 +08:00
for i := startGroupIndex ; i < len ( autoGroups ) ; i ++ {
2025-12-12 17:59:21 +08:00
autoGroup := autoGroups [ i ]
2025-12-13 16:43:38 +08:00
// Calculate priorityRetry for current group
// 计算当前分组的 priorityRetry
priorityRetry := param . GetRetry ( )
// If moved to a new group, reset priorityRetry and update startRetryIndex
// 如果切换到新分组,重置 priorityRetry 并更新 startRetryIndex
if i > startGroupIndex {
2025-12-13 01:04:10 +08:00
priorityRetry = 0
2025-12-13 16:43:38 +08:00
}
logger . LogDebug ( param . Ctx , "Auto selecting group: %s, priorityRetry: %d" , autoGroup , priorityRetry )
channel , _ = model . GetRandomSatisfiedChannel ( autoGroup , param . ModelName , priorityRetry )
if channel == nil {
// Current group has no available channel for this model, try next group
// 当前分组没有该模型的可用渠道,尝试下一个分组
logger . LogDebug ( param . Ctx , "No available channel in group %s for model %s at priorityRetry %d, trying next group" , autoGroup , param . ModelName , priorityRetry )
// 重置状态以尝试下一个分组
common . SetContextKey ( param . Ctx , constant . ContextKeyAutoGroupIndex , i + 1 )
common . SetContextKey ( param . Ctx , constant . ContextKeyAutoGroupRetryIndex , 0 )
// Reset retry counter so outer loop can continue for next group
// 重置重试计数器,以便外层循环可以为下一个分组继续
param . SetRetry ( 0 )
2025-10-28 23:25:43 +08:00
continue
2025-12-13 16:43:38 +08:00
}
common . SetContextKey ( param . Ctx , constant . ContextKeyAutoGroup , autoGroup )
selectGroup = autoGroup
logger . LogDebug ( param . Ctx , "Auto selected group: %s" , autoGroup )
// Prepare state for next retry
// 为下一次重试准备状态
if crossGroupRetry && priorityRetry >= common . RetryTimes {
// Current group has exhausted all retries, prepare to switch to next group
// This request still uses current group, but next retry will use next group
// 当前分组已用完所有重试次数,准备切换到下一个分组
// 本次请求仍使用当前分组,但下次重试将使用下一个分组
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 )
common . SetContextKey ( param . Ctx , constant . ContextKeyAutoGroupIndex , i + 1 )
// Reset retry counter so outer loop can continue for next group
// 重置重试计数器,以便外层循环可以为下一个分组继续
2025-12-13 18:09:10 +08:00
param . SetRetry ( 0 )
param . ResetRetryNextTry ( )
2025-10-28 23:25:43 +08:00
} else {
2025-12-13 16:43:38 +08:00
// Stay in current group, save current state
// 保持在当前分组,保存当前状态
common . SetContextKey ( param . Ctx , constant . ContextKeyAutoGroupIndex , i )
2025-10-28 23:25:43 +08:00
}
2025-12-13 16:43:38 +08:00
break
2025-10-28 23:25:43 +08:00
}
} else {
2025-12-13 16:43:38 +08:00
channel , err = model . GetRandomSatisfiedChannel ( param . TokenGroup , param . ModelName , param . GetRetry ( ) )
2025-10-28 23:25:43 +08:00
if err != nil {
2025-12-13 16:43:38 +08:00
return nil , param . TokenGroup , err
2025-10-28 23:25:43 +08:00
}
}
return channel , selectGroup , nil
}