Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
0d6c1c77
Commit
0d6c1c77
authored
Mar 06, 2026
by
erio
Browse files
feat: add independent load_factor field for scheduling load calculation
parent
ae5d9c8b
Changes
31
Show whitespace changes
Inline
Side-by-side
backend/internal/service/openai_account_scheduler.go
View file @
0d6c1c77
...
...
@@ -590,7 +590,7 @@ func (s *defaultOpenAIAccountScheduler) selectByLoadBalance(
filtered
=
append
(
filtered
,
account
)
loadReq
=
append
(
loadReq
,
AccountWithConcurrency
{
ID
:
account
.
ID
,
MaxConcurrency
:
account
.
Concurrency
,
MaxConcurrency
:
account
.
EffectiveLoadFactor
()
,
})
}
if
len
(
filtered
)
==
0
{
...
...
backend/internal/service/openai_gateway_service.go
View file @
0d6c1c77
...
...
@@ -1242,7 +1242,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
for
_
,
acc
:=
range
candidates
{
accountLoads
=
append
(
accountLoads
,
AccountWithConcurrency
{
ID
:
acc
.
ID
,
MaxConcurrency
:
acc
.
Concurrency
,
MaxConcurrency
:
acc
.
EffectiveLoadFactor
()
,
})
}
...
...
backend/internal/service/ops_concurrency.go
View file @
0d6c1c77
...
...
@@ -64,8 +64,9 @@ func (s *OpsService) getAccountsLoadMapBestEffort(ctx context.Context, accounts
if
acc
.
ID
<=
0
{
continue
}
if
prev
,
ok
:=
unique
[
acc
.
ID
];
!
ok
||
acc
.
Concurrency
>
prev
{
unique
[
acc
.
ID
]
=
acc
.
Concurrency
lf
:=
acc
.
EffectiveLoadFactor
()
if
prev
,
ok
:=
unique
[
acc
.
ID
];
!
ok
||
lf
>
prev
{
unique
[
acc
.
ID
]
=
lf
}
}
...
...
backend/internal/service/ops_metrics_collector.go
View file @
0d6c1c77
...
...
@@ -389,13 +389,9 @@ func (c *OpsMetricsCollector) collectConcurrencyQueueDepth(parentCtx context.Con
if
acc
.
ID
<=
0
{
continue
}
maxConc
:=
acc
.
Concurrency
if
maxConc
<
0
{
maxConc
=
0
}
batch
=
append
(
batch
,
AccountWithConcurrency
{
ID
:
acc
.
ID
,
MaxConcurrency
:
maxConc
,
MaxConcurrency
:
acc
.
EffectiveLoadFactor
()
,
})
}
if
len
(
batch
)
==
0
{
...
...
backend/migrations/067_add_account_load_factor.sql
0 → 100644
View file @
0d6c1c77
ALTER
TABLE
accounts
ADD
COLUMN
IF
NOT
EXISTS
load_factor
INTEGER
;
frontend/src/components/account/BulkEditAccountModal.vue
View file @
0d6c1c77
...
...
@@ -469,7 +469,7 @@
<
/div
>
<!--
Concurrency
&
Priority
-->
<
div
class
=
"
grid grid-cols-2 gap-4 border-t border-gray-200 pt-4 dark:border-dark-600 lg:grid-cols-
3
"
>
<
div
class
=
"
grid grid-cols-2 gap-4 border-t border-gray-200 pt-4 dark:border-dark-600 lg:grid-cols-
4
"
>
<
div
>
<
div
class
=
"
mb-3 flex items-center justify-between
"
>
<
label
...
...
@@ -498,6 +498,35 @@
aria
-
labelledby
=
"
bulk-edit-concurrency-label
"
/>
<
/div
>
<
div
>
<
div
class
=
"
mb-3 flex items-center justify-between
"
>
<
label
id
=
"
bulk-edit-load-factor-label
"
class
=
"
input-label mb-0
"
for
=
"
bulk-edit-load-factor-enabled
"
>
{{
t
(
'
admin.accounts.loadFactor
'
)
}}
<
/label
>
<
input
v
-
model
=
"
enableLoadFactor
"
id
=
"
bulk-edit-load-factor-enabled
"
type
=
"
checkbox
"
aria
-
controls
=
"
bulk-edit-load-factor
"
class
=
"
rounded border-gray-300 text-primary-600 focus:ring-primary-500
"
/>
<
/div
>
<
input
v
-
model
.
number
=
"
loadFactor
"
id
=
"
bulk-edit-load-factor
"
type
=
"
number
"
min
=
"
1
"
:
disabled
=
"
!enableLoadFactor
"
class
=
"
input
"
:
class
=
"
!enableLoadFactor && 'cursor-not-allowed opacity-50'
"
aria
-
labelledby
=
"
bulk-edit-load-factor-label
"
/>
<
p
class
=
"
input-hint
"
>
{{
t
(
'
admin.accounts.loadFactorHint
'
)
}}
<
/p
>
<
/div
>
<
div
>
<
div
class
=
"
mb-3 flex items-center justify-between
"
>
<
label
...
...
@@ -869,6 +898,7 @@ const enableCustomErrorCodes = ref(false)
const
enableInterceptWarmup
=
ref
(
false
)
const
enableProxy
=
ref
(
false
)
const
enableConcurrency
=
ref
(
false
)
const
enableLoadFactor
=
ref
(
false
)
const
enablePriority
=
ref
(
false
)
const
enableRateMultiplier
=
ref
(
false
)
const
enableStatus
=
ref
(
false
)
...
...
@@ -889,6 +919,7 @@ const customErrorCodeInput = ref<number | null>(null)
const
interceptWarmupRequests
=
ref
(
false
)
const
proxyId
=
ref
<
number
|
null
>
(
null
)
const
concurrency
=
ref
(
1
)
const
loadFactor
=
ref
<
number
|
null
>
(
null
)
const
priority
=
ref
(
1
)
const
rateMultiplier
=
ref
(
1
)
const
status
=
ref
<
'
active
'
|
'
inactive
'
>
(
'
active
'
)
...
...
@@ -1195,6 +1226,10 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
updates
.
concurrency
=
concurrency
.
value
}
if
(
enableLoadFactor
.
value
)
{
updates
.
load_factor
=
loadFactor
.
value
}
if
(
enablePriority
.
value
)
{
updates
.
priority
=
priority
.
value
}
...
...
@@ -1340,6 +1375,7 @@ const handleSubmit = async () => {
enableInterceptWarmup
.
value
||
enableProxy
.
value
||
enableConcurrency
.
value
||
enableLoadFactor
.
value
||
enablePriority
.
value
||
enableRateMultiplier
.
value
||
enableStatus
.
value
||
...
...
@@ -1430,6 +1466,7 @@ watch(
enableInterceptWarmup
.
value
=
false
enableProxy
.
value
=
false
enableConcurrency
.
value
=
false
enableLoadFactor
.
value
=
false
enablePriority
.
value
=
false
enableRateMultiplier
.
value
=
false
enableStatus
.
value
=
false
...
...
@@ -1446,6 +1483,7 @@ watch(
interceptWarmupRequests
.
value
=
false
proxyId
.
value
=
null
concurrency
.
value
=
1
loadFactor
.
value
=
null
priority
.
value
=
1
rateMultiplier
.
value
=
1
status
.
value
=
'
active
'
...
...
frontend/src/components/account/CreateAccountModal.vue
View file @
0d6c1c77
...
...
@@ -1749,11 +1749,17 @@
<
ProxySelector
v
-
model
=
"
form.proxy_id
"
:
proxies
=
"
proxies
"
/>
<
/div
>
<
div
class
=
"
grid grid-cols-2 gap-4 lg:grid-cols-
3
"
>
<
div
class
=
"
grid grid-cols-2 gap-4 lg:grid-cols-
4
"
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.accounts.concurrency
'
)
}}
<
/label
>
<
input
v
-
model
.
number
=
"
form.concurrency
"
type
=
"
number
"
min
=
"
1
"
class
=
"
input
"
/>
<
/div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.accounts.loadFactor
'
)
}}
<
/label
>
<
input
v
-
model
.
number
=
"
form.load_factor
"
type
=
"
number
"
min
=
"
1
"
class
=
"
input
"
:
placeholder
=
"
String(form.concurrency || 1)
"
/>
<
p
class
=
"
input-hint
"
>
{{
t
(
'
admin.accounts.loadFactorHint
'
)
}}
<
/p
>
<
/div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.accounts.priority
'
)
}}
<
/label
>
<
input
...
...
@@ -2633,6 +2639,7 @@ const form = reactive({
credentials
:
{
}
as
Record
<
string
,
unknown
>
,
proxy_id
:
null
as
number
|
null
,
concurrency
:
10
,
load_factor
:
null
as
number
|
null
,
priority
:
1
,
rate_multiplier
:
1
,
group_ids
:
[]
as
number
[],
...
...
@@ -3112,6 +3119,7 @@ const resetForm = () => {
form
.
credentials
=
{
}
form
.
proxy_id
=
null
form
.
concurrency
=
10
form
.
load_factor
=
null
form
.
priority
=
1
form
.
rate_multiplier
=
1
form
.
group_ids
=
[]
...
...
@@ -3483,6 +3491,7 @@ const handleImportAccessToken = async (accessTokenInput: string) => {
extra
:
soraExtra
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
@@ -3542,6 +3551,7 @@ const createAccountAndFinish = async (
extra
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
@@ -3597,6 +3607,7 @@ const handleOpenAIExchange = async (authCode: string) => {
extra
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
@@ -3626,6 +3637,7 @@ const handleOpenAIExchange = async (authCode: string) => {
extra
:
soraExtra
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
@@ -3703,6 +3715,7 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
extra
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
@@ -3730,6 +3743,7 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
extra
:
soraExtra
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
@@ -3818,6 +3832,7 @@ const handleSoraValidateST = async (sessionTokenInput: string) => {
extra
:
soraExtra
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
@@ -3906,6 +3921,7 @@ const handleAntigravityValidateRT = async (refreshTokenInput: string) => {
extra
:
{
}
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
@@ -4064,8 +4080,11 @@ const handleAnthropicExchange = async (authCode: string) => {
}
// Add RPM limit settings
if
(
rpmLimitEnabled
.
value
&&
baseRpm
.
value
!=
null
&&
baseRpm
.
value
>
0
)
{
extra
.
base_rpm
=
baseRpm
.
value
if
(
rpmLimitEnabled
.
value
)
{
const
DEFAULT_BASE_RPM
=
15
extra
.
base_rpm
=
(
baseRpm
.
value
!=
null
&&
baseRpm
.
value
>
0
)
?
baseRpm
.
value
:
DEFAULT_BASE_RPM
extra
.
rpm_strategy
=
rpmStrategy
.
value
if
(
rpmStickyBuffer
.
value
!=
null
&&
rpmStickyBuffer
.
value
>
0
)
{
extra
.
rpm_sticky_buffer
=
rpmStickyBuffer
.
value
...
...
@@ -4176,8 +4195,11 @@ const handleCookieAuth = async (sessionKey: string) => {
}
// Add RPM limit settings
if
(
rpmLimitEnabled
.
value
&&
baseRpm
.
value
!=
null
&&
baseRpm
.
value
>
0
)
{
extra
.
base_rpm
=
baseRpm
.
value
if
(
rpmLimitEnabled
.
value
)
{
const
DEFAULT_BASE_RPM
=
15
extra
.
base_rpm
=
(
baseRpm
.
value
!=
null
&&
baseRpm
.
value
>
0
)
?
baseRpm
.
value
:
DEFAULT_BASE_RPM
extra
.
rpm_strategy
=
rpmStrategy
.
value
if
(
rpmStickyBuffer
.
value
!=
null
&&
rpmStickyBuffer
.
value
>
0
)
{
extra
.
rpm_sticky_buffer
=
rpmStickyBuffer
.
value
...
...
@@ -4223,6 +4245,7 @@ const handleCookieAuth = async (sessionKey: string) => {
extra
,
proxy_id
:
form
.
proxy_id
,
concurrency
:
form
.
concurrency
,
load_factor
:
form
.
load_factor
||
undefined
,
priority
:
form
.
priority
,
rate_multiplier
:
form
.
rate_multiplier
,
group_ids
:
form
.
group_ids
,
...
...
frontend/src/components/account/EditAccountModal.vue
View file @
0d6c1c77
...
...
@@ -650,11 +650,17 @@
<
ProxySelector
v
-
model
=
"
form.proxy_id
"
:
proxies
=
"
proxies
"
/>
<
/div
>
<
div
class
=
"
grid grid-cols-2 gap-4 lg:grid-cols-
3
"
>
<
div
class
=
"
grid grid-cols-2 gap-4 lg:grid-cols-
4
"
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.accounts.concurrency
'
)
}}
<
/label
>
<
input
v
-
model
.
number
=
"
form.concurrency
"
type
=
"
number
"
min
=
"
1
"
class
=
"
input
"
/>
<
/div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.accounts.loadFactor
'
)
}}
<
/label
>
<
input
v
-
model
.
number
=
"
form.load_factor
"
type
=
"
number
"
min
=
"
1
"
class
=
"
input
"
:
placeholder
=
"
String(form.concurrency || 1)
"
/>
<
p
class
=
"
input-hint
"
>
{{
t
(
'
admin.accounts.loadFactorHint
'
)
}}
<
/p
>
<
/div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.accounts.priority
'
)
}}
<
/label
>
<
input
...
...
@@ -1465,6 +1471,7 @@ const form = reactive({
notes
:
''
,
proxy_id
:
null
as
number
|
null
,
concurrency
:
1
,
load_factor
:
null
as
number
|
null
,
priority
:
1
,
rate_multiplier
:
1
,
status
:
'
active
'
as
'
active
'
|
'
inactive
'
,
...
...
@@ -1498,6 +1505,7 @@ watch(
form
.
notes
=
newAccount
.
notes
||
''
form
.
proxy_id
=
newAccount
.
proxy_id
form
.
concurrency
=
newAccount
.
concurrency
form
.
load_factor
=
newAccount
.
load_factor
??
null
form
.
priority
=
newAccount
.
priority
form
.
rate_multiplier
=
newAccount
.
rate_multiplier
??
1
form
.
status
=
newAccount
.
status
as
'
active
'
|
'
inactive
'
...
...
@@ -2049,6 +2057,10 @@ const handleSubmit = async () => {
if
(
form
.
expires_at
===
null
)
{
updatePayload
.
expires_at
=
0
}
// load_factor: 空值/0/NaN 时发送 0(后端约定 0 = 清除)
if
(
!
form
.
load_factor
||
form
.
load_factor
<=
0
)
{
updatePayload
.
load_factor
=
0
}
updatePayload
.
auto_pause_on_expired
=
autoPauseOnExpired
.
value
// For apikey type, handle credentials update
...
...
@@ -2188,8 +2200,11 @@ const handleSubmit = async () => {
}
// RPM limit settings
if
(
rpmLimitEnabled
.
value
&&
baseRpm
.
value
!=
null
&&
baseRpm
.
value
>
0
)
{
newExtra
.
base_rpm
=
baseRpm
.
value
if
(
rpmLimitEnabled
.
value
)
{
const
DEFAULT_BASE_RPM
=
15
newExtra
.
base_rpm
=
(
baseRpm
.
value
!=
null
&&
baseRpm
.
value
>
0
)
?
baseRpm
.
value
:
DEFAULT_BASE_RPM
newExtra
.
rpm_strategy
=
rpmStrategy
.
value
if
(
rpmStickyBuffer
.
value
!=
null
&&
rpmStickyBuffer
.
value
>
0
)
{
newExtra
.
rpm_sticky_buffer
=
rpmStickyBuffer
.
value
...
...
frontend/src/i18n/locales/en.ts
View file @
0d6c1c77
...
...
@@ -1991,10 +1991,12 @@ export default {
proxy
:
'
Proxy
'
,
noProxy
:
'
No Proxy
'
,
concurrency
:
'
Concurrency
'
,
loadFactor
:
'
Load Factor
'
,
loadFactorHint
:
'
Defaults to concurrency
'
,
priority
:
'
Priority
'
,
priorityHint
:
'
Lower value accounts are used first
'
,
billingRateMultiplier
:
'
Billing Rate Multiplier
'
,
billingRateMultiplierHint
:
'
>=0, 0 means
free
. A
ffects account billing only
'
,
billingRateMultiplierHint
:
'
0 =
free
, a
ffects account billing only
'
,
expiresAt
:
'
Expires At
'
,
expiresAtHint
:
'
Leave empty for no expiration
'
,
higherPriorityFirst
:
'
Lower value means higher priority
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
0d6c1c77
...
...
@@ -2133,10 +2133,12 @@ export default {
proxy
:
'
代理
'
,
noProxy
:
'
无代理
'
,
concurrency
:
'
并发数
'
,
loadFactor
:
'
负载因子
'
,
loadFactorHint
:
'
不填则等于并发数
'
,
priority
:
'
优先级
'
,
priorityHint
:
'
优先级越小的账号优先使用
'
,
billingRateMultiplier
:
'
账号计费倍率
'
,
billingRateMultiplierHint
:
'
>=0,0 表示该账号计费为 0;
仅影响账号计费
口径
'
,
billingRateMultiplierHint
:
'
0 表示不计费,
仅影响账号计费
'
,
expiresAt
:
'
过期时间
'
,
expiresAtHint
:
'
留空表示不过期
'
,
higherPriorityFirst
:
'
数值越小优先级越高
'
,
...
...
frontend/src/types/index.ts
View file @
0d6c1c77
...
...
@@ -653,6 +653,7 @@ export interface Account {
}
&
Record
<
string
,
unknown
>
)
proxy_id
:
number
|
null
concurrency
:
number
load_factor
?:
number
|
null
current_concurrency
?:
number
// Real-time concurrency count from Redis
priority
:
number
rate_multiplier
?:
number
// Account billing multiplier (>=0, 0 means free)
...
...
@@ -783,6 +784,7 @@ export interface CreateAccountRequest {
extra
?:
Record
<
string
,
unknown
>
proxy_id
?:
number
|
null
concurrency
?:
number
load_factor
?:
number
|
null
priority
?:
number
rate_multiplier
?:
number
// Account billing multiplier (>=0, 0 means free)
group_ids
?:
number
[]
...
...
@@ -799,6 +801,7 @@ export interface UpdateAccountRequest {
extra
?:
Record
<
string
,
unknown
>
proxy_id
?:
number
|
null
concurrency
?:
number
load_factor
?:
number
|
null
priority
?:
number
rate_multiplier
?:
number
// Account billing multiplier (>=0, 0 means free)
schedulable
?:
boolean
...
...
Prev
1
2
Next
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