Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c87deaa7
authored
Dec 12, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(token): add cross-group retry option for token processing
parent
ea3e9a01
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
38 additions
and
8 deletions
+38
-8
constant/context_key.go
+2
-0
controller/token.go
+1
-0
middleware/auth.go
+1
-0
model/token.go
+2
-1
relay/channel/openai/helper.go
+1
-1
service/channel_select.go
+18
-4
service/token_counter.go
+1
-1
web/src/components/table/tokens/modals/EditTokenModal.jsx
+12
-1
No files found.
constant/context_key.go
View file @
c87deaa7
...
@@ -18,8 +18,10 @@ const (
...
@@ -18,8 +18,10 @@ const (
ContextKeyTokenSpecificChannelId
ContextKey
=
"specific_channel_id"
ContextKeyTokenSpecificChannelId
ContextKey
=
"specific_channel_id"
ContextKeyTokenModelLimitEnabled
ContextKey
=
"token_model_limit_enabled"
ContextKeyTokenModelLimitEnabled
ContextKey
=
"token_model_limit_enabled"
ContextKeyTokenModelLimit
ContextKey
=
"token_model_limit"
ContextKeyTokenModelLimit
ContextKey
=
"token_model_limit"
ContextKeyTokenCrossGroupRetry
ContextKey
=
"token_cross_group_retry"
/* channel related keys */
/* channel related keys */
ContextKeyAutoGroupIndex
ContextKey
=
"auto_group_index"
ContextKeyChannelId
ContextKey
=
"channel_id"
ContextKeyChannelId
ContextKey
=
"channel_id"
ContextKeyChannelName
ContextKey
=
"channel_name"
ContextKeyChannelName
ContextKey
=
"channel_name"
ContextKeyChannelCreateTime
ContextKey
=
"channel_create_time"
ContextKeyChannelCreateTime
ContextKey
=
"channel_create_time"
...
...
controller/token.go
View file @
c87deaa7
...
@@ -248,6 +248,7 @@ func UpdateToken(c *gin.Context) {
...
@@ -248,6 +248,7 @@ func UpdateToken(c *gin.Context) {
cleanToken
.
ModelLimits
=
token
.
ModelLimits
cleanToken
.
ModelLimits
=
token
.
ModelLimits
cleanToken
.
AllowIps
=
token
.
AllowIps
cleanToken
.
AllowIps
=
token
.
AllowIps
cleanToken
.
Group
=
token
.
Group
cleanToken
.
Group
=
token
.
Group
cleanToken
.
CrossGroupRetry
=
token
.
CrossGroupRetry
}
}
err
=
cleanToken
.
Update
()
err
=
cleanToken
.
Update
()
if
err
!=
nil
{
if
err
!=
nil
{
...
...
middleware/auth.go
View file @
c87deaa7
...
@@ -308,6 +308,7 @@ func SetupContextForToken(c *gin.Context, token *model.Token, parts ...string) e
...
@@ -308,6 +308,7 @@ func SetupContextForToken(c *gin.Context, token *model.Token, parts ...string) e
c
.
Set
(
"token_model_limit_enabled"
,
false
)
c
.
Set
(
"token_model_limit_enabled"
,
false
)
}
}
c
.
Set
(
"token_group"
,
token
.
Group
)
c
.
Set
(
"token_group"
,
token
.
Group
)
c
.
Set
(
"token_cross_group_retry"
,
token
.
CrossGroupRetry
)
if
len
(
parts
)
>
1
{
if
len
(
parts
)
>
1
{
if
model
.
IsAdmin
(
token
.
UserId
)
{
if
model
.
IsAdmin
(
token
.
UserId
)
{
c
.
Set
(
"specific_channel_id"
,
parts
[
1
])
c
.
Set
(
"specific_channel_id"
,
parts
[
1
])
...
...
model/token.go
View file @
c87deaa7
...
@@ -27,6 +27,7 @@ type Token struct {
...
@@ -27,6 +27,7 @@ type Token struct {
AllowIps
*
string
`json:"allow_ips" gorm:"default:''"`
AllowIps
*
string
`json:"allow_ips" gorm:"default:''"`
UsedQuota
int
`json:"used_quota" gorm:"default:0"`
// used quota
UsedQuota
int
`json:"used_quota" gorm:"default:0"`
// used quota
Group
string
`json:"group" gorm:"default:''"`
Group
string
`json:"group" gorm:"default:''"`
CrossGroupRetry
bool
`json:"cross_group_retry" gorm:"default:false"`
// 跨分组重试,仅auto分组有效
DeletedAt
gorm
.
DeletedAt
`gorm:"index"`
DeletedAt
gorm
.
DeletedAt
`gorm:"index"`
}
}
...
@@ -185,7 +186,7 @@ func (token *Token) Update() (err error) {
...
@@ -185,7 +186,7 @@ func (token *Token) Update() (err error) {
}
}
}()
}()
err
=
DB
.
Model
(
token
)
.
Select
(
"name"
,
"status"
,
"expired_time"
,
"remain_quota"
,
"unlimited_quota"
,
err
=
DB
.
Model
(
token
)
.
Select
(
"name"
,
"status"
,
"expired_time"
,
"remain_quota"
,
"unlimited_quota"
,
"model_limits_enabled"
,
"model_limits"
,
"allow_ips"
,
"group"
)
.
Updates
(
token
)
.
Error
"model_limits_enabled"
,
"model_limits"
,
"allow_ips"
,
"group"
,
"cross_group_retry"
)
.
Updates
(
token
)
.
Error
return
err
return
err
}
}
...
...
relay/channel/openai/helper.go
View file @
c87deaa7
...
@@ -172,7 +172,7 @@ func handleLastResponse(lastStreamData string, responseId *string, createAt *int
...
@@ -172,7 +172,7 @@ func handleLastResponse(lastStreamData string, responseId *string, createAt *int
shouldSendLastResp
*
bool
)
error
{
shouldSendLastResp
*
bool
)
error
{
var
lastStreamResponse
dto
.
ChatCompletionsStreamResponse
var
lastStreamResponse
dto
.
ChatCompletionsStreamResponse
if
err
:=
js
on
.
Unmarshal
(
common
.
StringToByteSlice
(
lastStreamData
),
&
lastStreamResponse
);
err
!=
nil
{
if
err
:=
comm
on
.
Unmarshal
(
common
.
StringToByteSlice
(
lastStreamData
),
&
lastStreamResponse
);
err
!=
nil
{
return
err
return
err
}
}
...
...
service/channel_select.go
View file @
c87deaa7
...
@@ -11,6 +11,7 @@ import (
...
@@ -11,6 +11,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
)
)
// CacheGetRandomSatisfiedChannel tries to get a random channel that satisfies the requirements.
func
CacheGetRandomSatisfiedChannel
(
c
*
gin
.
Context
,
group
string
,
modelName
string
,
retry
int
)
(
*
model
.
Channel
,
string
,
error
)
{
func
CacheGetRandomSatisfiedChannel
(
c
*
gin
.
Context
,
group
string
,
modelName
string
,
retry
int
)
(
*
model
.
Channel
,
string
,
error
)
{
var
channel
*
model
.
Channel
var
channel
*
model
.
Channel
var
err
error
var
err
error
...
@@ -20,15 +21,28 @@ func CacheGetRandomSatisfiedChannel(c *gin.Context, group string, modelName stri
...
@@ -20,15 +21,28 @@ func CacheGetRandomSatisfiedChannel(c *gin.Context, group string, modelName stri
if
len
(
setting
.
GetAutoGroups
())
==
0
{
if
len
(
setting
.
GetAutoGroups
())
==
0
{
return
nil
,
selectGroup
,
errors
.
New
(
"auto groups is not enabled"
)
return
nil
,
selectGroup
,
errors
.
New
(
"auto groups is not enabled"
)
}
}
for
_
,
autoGroup
:=
range
GetUserAutoGroup
(
userGroup
)
{
autoGroups
:=
GetUserAutoGroup
(
userGroup
)
logger
.
LogDebug
(
c
,
"Auto selecting group:"
,
autoGroup
)
// 如果 token 启用了跨分组重试,获取上次失败的 auto group 索引,从下一个开始尝试
channel
,
_
=
model
.
GetRandomSatisfiedChannel
(
autoGroup
,
modelName
,
retry
)
startIndex
:=
0
crossGroupRetry
:=
common
.
GetContextKeyBool
(
c
,
constant
.
ContextKeyTokenCrossGroupRetry
)
if
crossGroupRetry
&&
retry
>
0
{
logger
.
LogDebug
(
c
,
"Auto group retry cross group, retry: %d"
,
retry
)
if
lastIndex
,
exists
:=
c
.
Get
(
string
(
constant
.
ContextKeyAutoGroupIndex
));
exists
{
startIndex
=
lastIndex
.
(
int
)
+
1
}
logger
.
LogDebug
(
c
,
"Auto group retry cross group, start index: %d"
,
startIndex
)
}
for
i
:=
startIndex
;
i
<
len
(
autoGroups
);
i
++
{
autoGroup
:=
autoGroups
[
i
]
logger
.
LogDebug
(
c
,
"Auto selecting group: %s"
,
autoGroup
)
channel
,
_
=
model
.
GetRandomSatisfiedChannel
(
autoGroup
,
modelName
,
0
)
if
channel
==
nil
{
if
channel
==
nil
{
continue
continue
}
else
{
}
else
{
c
.
Set
(
"auto_group"
,
autoGroup
)
c
.
Set
(
"auto_group"
,
autoGroup
)
c
.
Set
(
string
(
constant
.
ContextKeyAutoGroupIndex
),
i
)
selectGroup
=
autoGroup
selectGroup
=
autoGroup
logger
.
LogDebug
(
c
,
"Auto selected group:"
,
autoGroup
)
logger
.
LogDebug
(
c
,
"Auto selected group:
%s
"
,
autoGroup
)
break
break
}
}
}
}
...
...
service/token_counter.go
View file @
c87deaa7
...
@@ -317,7 +317,7 @@ func EstimateRequestToken(c *gin.Context, meta *types.TokenCountMeta, info *rela
...
@@ -317,7 +317,7 @@ func EstimateRequestToken(c *gin.Context, meta *types.TokenCountMeta, info *rela
for
i
,
file
:=
range
meta
.
Files
{
for
i
,
file
:=
range
meta
.
Files
{
switch
file
.
FileType
{
switch
file
.
FileType
{
case
types
.
FileTypeImage
:
case
types
.
FileTypeImage
:
if
common
.
IsOpenAITextModel
(
info
.
OriginModelName
)
{
if
common
.
IsOpenAITextModel
(
model
)
{
token
,
err
:=
getImageToken
(
file
,
model
,
info
.
IsStream
)
token
,
err
:=
getImageToken
(
file
,
model
,
info
.
IsStream
)
if
err
!=
nil
{
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"error counting image token, media index[%d], original data[%s], err: %v"
,
i
,
file
.
OriginData
,
err
)
return
0
,
fmt
.
Errorf
(
"error counting image token, media index[%d], original data[%s], err: %v"
,
i
,
file
.
OriginData
,
err
)
...
...
web/src/components/table/tokens/modals/EditTokenModal.jsx
View file @
c87deaa7
...
@@ -73,6 +73,7 @@ const EditTokenModal = (props) => {
...
@@ -73,6 +73,7 @@ const EditTokenModal = (props) => {
model_limits
:
[],
model_limits
:
[],
allow_ips
:
''
,
allow_ips
:
''
,
group
:
''
,
group
:
''
,
cross_group_retry
:
false
,
tokenCount
:
1
,
tokenCount
:
1
,
});
});
...
@@ -377,6 +378,16 @@ const EditTokenModal = (props) => {
...
@@ -377,6 +378,16 @@ const EditTokenModal = (props) => {
/>
/>
)
}
)
}
</
Col
>
</
Col
>
<
Col
span=
{
24
}
style=
{
{
display
:
values
.
group
===
'auto'
?
'block'
:
'none'
}
}
>
<
Form
.
Switch
field=
'cross_group_retry'
label=
{
t
(
'跨分组重试'
)
}
size=
'default'
extraText=
{
t
(
'开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道'
,
)
}
/>
</
Col
>
<
Col
xs=
{
24
}
sm=
{
24
}
md=
{
24
}
lg=
{
10
}
xl=
{
10
}
>
<
Col
xs=
{
24
}
sm=
{
24
}
md=
{
24
}
lg=
{
10
}
xl=
{
10
}
>
<
Form
.
DatePicker
<
Form
.
DatePicker
field=
'expired_time'
field=
'expired_time'
...
@@ -499,7 +510,7 @@ const EditTokenModal = (props) => {
...
@@ -499,7 +510,7 @@ const EditTokenModal = (props) => {
<
Form
.
Switch
<
Form
.
Switch
field=
'unlimited_quota'
field=
'unlimited_quota'
label=
{
t
(
'无限额度'
)
}
label=
{
t
(
'无限额度'
)
}
size=
'
large
'
size=
'
default
'
extraText=
{
t
(
extraText=
{
t
(
'令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制'
,
'令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制'
,
)
}
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment