feat: add OpenAI advanced scheduler controls

Related: #1089, #408, #123
This commit is contained in:
linshuboy
2026-07-05 17:24:38 +08:00
parent b650bdd68d
commit f26ca5661e
30 changed files with 3158 additions and 580 deletions
+34
View File
@@ -606,6 +606,28 @@ export interface SystemSettings {
payment_visible_method_alipay_enabled?: boolean;
payment_visible_method_wxpay_enabled?: boolean;
openai_advanced_scheduler_enabled?: boolean;
openai_advanced_scheduler_sticky_weighted_enabled?: boolean;
openai_advanced_scheduler_subscription_priority_enabled?: boolean;
openai_advanced_scheduler_lb_top_k?: string;
openai_advanced_scheduler_weight_priority?: string;
openai_advanced_scheduler_weight_load?: string;
openai_advanced_scheduler_weight_queue?: string;
openai_advanced_scheduler_weight_error_rate?: string;
openai_advanced_scheduler_weight_ttft?: string;
openai_advanced_scheduler_weight_reset?: string;
openai_advanced_scheduler_weight_quota_headroom?: string;
openai_advanced_scheduler_weight_previous_response?: string;
openai_advanced_scheduler_weight_session_sticky?: string;
openai_advanced_scheduler_effective_lb_top_k?: string;
openai_advanced_scheduler_effective_weight_priority?: string;
openai_advanced_scheduler_effective_weight_load?: string;
openai_advanced_scheduler_effective_weight_queue?: string;
openai_advanced_scheduler_effective_weight_error_rate?: string;
openai_advanced_scheduler_effective_weight_ttft?: string;
openai_advanced_scheduler_effective_weight_reset?: string;
openai_advanced_scheduler_effective_weight_quota_headroom?: string;
openai_advanced_scheduler_effective_weight_previous_response?: string;
openai_advanced_scheduler_effective_weight_session_sticky?: string;
// 余额、订阅到期与账号限额通知
balance_low_notify_enabled: boolean;
@@ -855,6 +877,18 @@ export interface UpdateSettingsRequest {
payment_visible_method_alipay_enabled?: boolean;
payment_visible_method_wxpay_enabled?: boolean;
openai_advanced_scheduler_enabled?: boolean;
openai_advanced_scheduler_sticky_weighted_enabled?: boolean;
openai_advanced_scheduler_subscription_priority_enabled?: boolean;
openai_advanced_scheduler_lb_top_k?: string;
openai_advanced_scheduler_weight_priority?: string;
openai_advanced_scheduler_weight_load?: string;
openai_advanced_scheduler_weight_queue?: string;
openai_advanced_scheduler_weight_error_rate?: string;
openai_advanced_scheduler_weight_ttft?: string;
openai_advanced_scheduler_weight_reset?: string;
openai_advanced_scheduler_weight_quota_headroom?: string;
openai_advanced_scheduler_weight_previous_response?: string;
openai_advanced_scheduler_weight_session_sticky?: string;
// 余额、订阅到期与账号限额通知
balance_low_notify_enabled?: boolean;
balance_low_notify_threshold?: number;
+25 -1
View File
@@ -3265,6 +3265,7 @@ export default {
priority: 'Priority',
billingRateMultiplier: 'Billing Rate',
weight: 'Weight',
schedulerScore: 'Scheduler Score',
status: 'Status',
schedulable: 'Schedulable',
todayStats: 'Today Stats',
@@ -3276,6 +3277,12 @@ export default {
expiresAt: 'Expires At',
actions: 'Actions'
},
schedulerScore: {
baseShort: 'Base',
stickyShort: 'Sticky',
ungrouped: 'Ungrouped',
hint: 'Displayed as "group / base score / sticky bonus". The base score is computed within the current filtered candidate set and includes priority, load, queue depth, error rate, first-token latency, reset window, quota headroom, and related factors. The sticky bonus applies only when sticky weighting is enabled for previous_response_id or session_hash. Higher scores are preferred.'
},
usageWindowsHint: '"5h / 7d" are the upstream account\'s official rolling usage windows (e.g. OpenAI ChatGPT, Claude). They are imposed by the upstream provider on the account itself — not configured by sub2api, and unrelated to the models you map. Usage resets automatically once each window rolls over, and the limit cannot be lifted from within sub2api.',
allPrivacyModes: 'All Privacy States',
privacyUnset: 'Unset',
@@ -6724,7 +6731,24 @@ export default {
},
openaiExperimentalScheduler: {
title: 'OpenAI experimental scheduler policy',
description: "Disabled by default. When enabled, this only changes the gateway's experimental account-selection policy for OpenAI traffic; it does not indicate an upstream OpenAI capability."
description: "Disabled by default. When enabled, this only changes the gateway's experimental account-selection policy for OpenAI traffic; it does not indicate an upstream OpenAI capability.",
stickyWeightedTitle: 'Sticky weighting',
stickyWeightedDescription: 'When enabled, previous_response_id and session_hash affinity are scored by the advanced scheduler. When disabled, sticky accounts keep the legacy hard-hit behavior.',
subscriptionPriorityTitle: 'Subscription priority',
subscriptionPriorityDescription: 'When enabled, the scheduler scores ChatGPT subscription accounts first and falls back to non-subscription accounts only if no subscription slot can be acquired.',
weightsTitle: 'Scheduler weight overrides',
weightsDescription: 'Blank values use config/environment values; when config is not set, built-in defaults apply. Non-blank page settings take priority.',
defaultPlaceholder: 'config/default: {value}',
topKLabel: 'TopK',
priorityWeight: 'Priority',
loadWeight: 'Load',
queueWeight: 'Queue',
errorRateWeight: 'Error rate',
ttftWeight: 'TTFT',
resetWeight: 'Reset window',
quotaHeadroomWeight: 'Quota headroom',
previousResponseWeight: 'previous_response sticky',
sessionStickyWeight: 'session_hash sticky'
},
usageRecords: {
title: 'Usage Records',
+25 -1
View File
@@ -3296,6 +3296,7 @@ export default {
priority: '优先级',
billingRateMultiplier: '账号倍率',
weight: '权重',
schedulerScore: '调度权值',
status: '状态',
schedulable: '调度',
todayStats: '今日统计',
@@ -3307,6 +3308,12 @@ export default {
expiresAt: '过期时间',
actions: '操作'
},
schedulerScore: {
baseShort: '普通',
stickyShort: '粘性',
ungrouped: '未分组',
hint: '显示格式为“分组名 / 基础分 / 粘性加分”。基础分按当前筛选条件限定的候选账号计算,包含优先级、负载、排队、错误率、首包延迟、重置窗口、额度余量等因子;粘性加分只在开启粘性加权时用于 previous_response_id 或 session_hash。分数越大越优先。'
},
usageWindowsHint: '“5h / 7d”是上游账号(如 OpenAI ChatGPT、Claude)官方的滚动用量窗口限制,由上游对账号设定,并非 sub2api 配置,也与你映射的模型无关。窗口滚动到期后用量会自动重置,无法在 sub2api 端解除该限制。',
allPrivacyModes: '全部Privacy状态',
privacyUnset: '未设置',
@@ -6878,7 +6885,24 @@ export default {
},
openaiExperimentalScheduler: {
title: 'OpenAI 实验调度策略',
description: '默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑,不代表上游 OpenAI 官方能力。'
description: '默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑,不代表上游 OpenAI 官方能力。',
stickyWeightedTitle: '粘性加权',
stickyWeightedDescription: '开启后 previous_response_id 和 session_hash 粘性进入高级调度打分;关闭时仍按旧逻辑硬命中粘性账号。',
subscriptionPriorityTitle: '订阅优先',
subscriptionPriorityDescription: '开启后先在 ChatGPT 订阅账号池中按权值选取;订阅池拿不到席位时再回退到非订阅账号池。',
weightsTitle: '调度权值覆盖',
weightsDescription: '留空时使用配置/环境变量值;配置未设置时使用内置默认值。页面非空设置优先。',
defaultPlaceholder: '配置/默认:{value}',
topKLabel: 'TopK',
priorityWeight: '优先级',
loadWeight: '负载',
queueWeight: '排队',
errorRateWeight: '错误率',
ttftWeight: '首包延迟',
resetWeight: '重置窗口',
quotaHeadroomWeight: '额度余量',
previousResponseWeight: 'previous_response 粘性',
sessionStickyWeight: 'session_hash 粘性'
},
usageRecords: {
title: '使用记录',
+17
View File
@@ -857,6 +857,13 @@ export interface Account {
concurrency: number
load_factor?: number | null
current_concurrency?: number // Real-time concurrency count from Redis
scheduler_score?: {
base_score: number
sticky_score?: number
sticky_score_infinity?: boolean
sticky_weighted_enabled: boolean
} | null
scheduler_scores?: AccountSchedulerGroupScore[] | null
priority: number
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
status: 'active' | 'inactive' | 'error'
@@ -947,6 +954,16 @@ export interface Account {
parent_chatgpt_account_id?: string
}
export interface AccountSchedulerGroupScore {
group_id?: number | null
group_name?: string
group_priority?: number | null
base_score: number
sticky_score?: number
sticky_score_infinity?: boolean
sticky_weighted_enabled: boolean
}
// Account Usage types
export interface WindowStats {
requests: number
+48 -1
View File
@@ -322,6 +322,29 @@
<template #cell-priority="{ value }">
<span class="text-sm text-gray-700 dark:text-gray-300">{{ value }}</span>
</template>
<template #header-scheduler_score="{ column }">
<div class="flex items-center">
<span>{{ column.label }}</span>
<HelpTooltip :content="t('admin.accounts.schedulerScore.hint')" width-class="w-80" />
</div>
</template>
<template #cell-scheduler_score="{ row }">
<div v-if="getSchedulerScoreRows(row).length" class="flex min-w-[7rem] flex-col gap-0.5 font-mono text-[11px] leading-4">
<div
v-for="score in getSchedulerScoreRows(row)"
:key="String(score.group_id)"
class="flex items-center gap-1 whitespace-nowrap text-gray-700 dark:text-gray-300"
:title="`${formatSchedulerScoreGroup(score)} / ${formatSchedulerScore(score.base_score)} / ${formatStickySchedulerScore(score)}`"
>
<span class="max-w-[4.75rem] truncate text-gray-500 dark:text-dark-400">{{ formatSchedulerScoreGroup(score) }}</span>
<span class="text-gray-300 dark:text-gray-600">/</span>
<span>{{ formatSchedulerScore(score.base_score) }}</span>
<span class="text-gray-300 dark:text-gray-600">/</span>
<span class="text-primary-700 dark:text-primary-300">{{ formatStickySchedulerScore(score) }}</span>
</div>
</div>
<span v-else class="text-sm text-gray-400 dark:text-dark-500">-</span>
</template>
<template #cell-last_used_at="{ value }">
<span class="text-sm text-gray-500 dark:text-dark-400">{{ formatRelativeTime(value) }}</span>
</template>
@@ -441,7 +464,7 @@ import TLSFingerprintProfilesModal from '@/components/admin/TLSFingerprintProfil
import { buildOpenAIUsageRefreshKey } from '@/utils/accountUsageRefresh'
import { formatDateTime, formatRelativeTime } from '@/utils/format'
import { proxyExpiryBadgeClass, proxyExpiryLabelKey } from '@/utils/proxyExpiry'
import type { Account, AccountPlatform, AccountType, Proxy as AccountProxy, AdminGroup, WindowStats, ClaudeModel } from '@/types'
import type { Account, AccountPlatform, AccountSchedulerGroupScore, AccountType, Proxy as AccountProxy, AdminGroup, WindowStats, ClaudeModel } from '@/types'
const { t } = useI18n()
const appStore = useAppStore()
@@ -643,6 +666,29 @@ const autoRefreshIntervalLabel = (sec: number) => {
return `${sec}s`
}
const formatSchedulerScore = (value: unknown): string => {
const num = Number(value)
if (!Number.isFinite(num)) return '-'
return num.toFixed(6).replace(/\.?0+$/, '')
}
const formatStickySchedulerScore = (score: AccountSchedulerGroupScore): string => {
if (!score) return '-'
if (score.sticky_score_infinity) return '+∞'
return formatSchedulerScore(score.sticky_score)
}
const getSchedulerScoreRows = (account: Account): AccountSchedulerGroupScore[] => {
if (!Array.isArray(account.scheduler_scores)) return []
return account.scheduler_scores.filter(score => score.group_id != null)
}
const formatSchedulerScoreGroup = (score: AccountSchedulerGroupScore): string => {
if ('group_name' in score && score.group_name) return score.group_name
if ('group_id' in score && score.group_id != null) return `#${score.group_id}`
return '-'
}
const loadSavedColumns = () => {
try {
const saved = localStorage.getItem(HIDDEN_COLUMNS_KEY)
@@ -1166,6 +1212,7 @@ const allColumns = computed(() => {
{ key: 'usage', label: t('admin.accounts.columns.usageWindows'), sortable: false },
{ key: 'proxy', label: t('admin.accounts.columns.proxy'), sortable: false },
{ key: 'priority', label: t('admin.accounts.columns.priority'), sortable: true },
{ key: 'scheduler_score', label: t('admin.accounts.columns.schedulerScore'), sortable: false },
{ key: 'rate_multiplier', label: t('admin.accounts.columns.billingRateMultiplier'), sortable: true },
{ key: 'last_used_at', label: t('admin.accounts.columns.lastUsed'), sortable: true },
{ key: 'created_at', label: t('admin.accounts.columns.createdAt'), sortable: true },
+220
View File
@@ -4014,6 +4014,81 @@
</div>
<Toggle v-model="form.openai_advanced_scheduler_enabled" />
</div>
<div
v-if="form.openai_advanced_scheduler_enabled"
class="flex items-center justify-between border-t border-gray-100 pt-5 dark:border-dark-700"
>
<div>
<label
class="text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("admin.settings.openaiExperimentalScheduler.stickyWeightedTitle") }}
</label>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
{{
t("admin.settings.openaiExperimentalScheduler.stickyWeightedDescription")
}}
</p>
</div>
<Toggle v-model="form.openai_advanced_scheduler_sticky_weighted_enabled" />
</div>
<div
v-if="form.openai_advanced_scheduler_enabled"
class="flex items-center justify-between border-t border-gray-100 pt-5 dark:border-dark-700"
>
<div>
<label
class="text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("admin.settings.openaiExperimentalScheduler.subscriptionPriorityTitle") }}
</label>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
{{
t("admin.settings.openaiExperimentalScheduler.subscriptionPriorityDescription")
}}
</p>
</div>
<Toggle v-model="form.openai_advanced_scheduler_subscription_priority_enabled" />
</div>
<div
v-if="form.openai_advanced_scheduler_enabled"
class="border-t border-gray-100 pt-5 dark:border-dark-700"
>
<div>
<label
class="text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("admin.settings.openaiExperimentalScheduler.weightsTitle") }}
</label>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
{{
t("admin.settings.openaiExperimentalScheduler.weightsDescription")
}}
</p>
</div>
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<label
v-for="field in openAIAdvancedSchedulerWeightFields"
:key="field.key"
class="block"
>
<span class="text-xs font-medium text-gray-600 dark:text-gray-400">
{{ field.label }}
</span>
<input
v-model="form[field.key]"
class="input mt-1"
inputmode="decimal"
:placeholder="field.placeholder"
type="text"
/>
</label>
</div>
</div>
</div>
</div>
@@ -7903,6 +7978,18 @@ type SettingsForm = Omit<
google_oauth_client_secret: string;
force_email_on_third_party_signup: boolean;
openai_advanced_scheduler_enabled: boolean;
openai_advanced_scheduler_sticky_weighted_enabled: boolean;
openai_advanced_scheduler_subscription_priority_enabled: boolean;
openai_advanced_scheduler_lb_top_k: string;
openai_advanced_scheduler_weight_priority: string;
openai_advanced_scheduler_weight_load: string;
openai_advanced_scheduler_weight_queue: string;
openai_advanced_scheduler_weight_error_rate: string;
openai_advanced_scheduler_weight_ttft: string;
openai_advanced_scheduler_weight_reset: string;
openai_advanced_scheduler_weight_quota_headroom: string;
openai_advanced_scheduler_weight_previous_response: string;
openai_advanced_scheduler_weight_session_sticky: string;
// 系统全局平台限额 map;form 内始终归一化为全 4 平台对象(模板非空绑定依赖此不变量)
default_platform_quotas: DefaultPlatformQuotasMap;
};
@@ -8094,6 +8181,18 @@ const form = reactive<SettingsForm>({
// 分组隔离
allow_ungrouped_key_scheduling: false,
openai_advanced_scheduler_enabled: false,
openai_advanced_scheduler_sticky_weighted_enabled: false,
openai_advanced_scheduler_subscription_priority_enabled: false,
openai_advanced_scheduler_lb_top_k: "",
openai_advanced_scheduler_weight_priority: "",
openai_advanced_scheduler_weight_load: "",
openai_advanced_scheduler_weight_queue: "",
openai_advanced_scheduler_weight_error_rate: "",
openai_advanced_scheduler_weight_ttft: "",
openai_advanced_scheduler_weight_reset: "",
openai_advanced_scheduler_weight_quota_headroom: "",
openai_advanced_scheduler_weight_previous_response: "",
openai_advanced_scheduler_weight_session_sticky: "",
// Gateway forwarding behavior
enable_fingerprint_unification: true,
enable_metadata_passthrough: false,
@@ -8131,6 +8230,103 @@ const form = reactive<SettingsForm>({
allow_user_view_error_requests: false,
});
type OpenAIAdvancedSchedulerOverrideKey =
| "openai_advanced_scheduler_lb_top_k"
| "openai_advanced_scheduler_weight_priority"
| "openai_advanced_scheduler_weight_load"
| "openai_advanced_scheduler_weight_queue"
| "openai_advanced_scheduler_weight_error_rate"
| "openai_advanced_scheduler_weight_ttft"
| "openai_advanced_scheduler_weight_reset"
| "openai_advanced_scheduler_weight_quota_headroom"
| "openai_advanced_scheduler_weight_previous_response"
| "openai_advanced_scheduler_weight_session_sticky";
type OpenAIAdvancedSchedulerEffectiveKey =
| "openai_advanced_scheduler_effective_lb_top_k"
| "openai_advanced_scheduler_effective_weight_priority"
| "openai_advanced_scheduler_effective_weight_load"
| "openai_advanced_scheduler_effective_weight_queue"
| "openai_advanced_scheduler_effective_weight_error_rate"
| "openai_advanced_scheduler_effective_weight_ttft"
| "openai_advanced_scheduler_effective_weight_reset"
| "openai_advanced_scheduler_effective_weight_quota_headroom"
| "openai_advanced_scheduler_effective_weight_previous_response"
| "openai_advanced_scheduler_effective_weight_session_sticky";
const openAIAdvancedSchedulerWeightFields = computed<
Array<{
key: OpenAIAdvancedSchedulerOverrideKey;
label: string;
placeholder: string;
}>
>(() => {
const placeholder = (
effectiveKey: OpenAIAdvancedSchedulerEffectiveKey,
fallbackValue: string,
) => {
const effectiveValue = String(
(form as Record<string, unknown>)[effectiveKey] ?? "",
).trim();
return t("admin.settings.openaiExperimentalScheduler.defaultPlaceholder", {
value: effectiveValue || fallbackValue,
});
};
return [
{
key: "openai_advanced_scheduler_lb_top_k",
label: t("admin.settings.openaiExperimentalScheduler.topKLabel"),
placeholder: placeholder("openai_advanced_scheduler_effective_lb_top_k", "7"),
},
{
key: "openai_advanced_scheduler_weight_priority",
label: t("admin.settings.openaiExperimentalScheduler.priorityWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_priority", "1"),
},
{
key: "openai_advanced_scheduler_weight_load",
label: t("admin.settings.openaiExperimentalScheduler.loadWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_load", "1"),
},
{
key: "openai_advanced_scheduler_weight_queue",
label: t("admin.settings.openaiExperimentalScheduler.queueWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_queue", "0.7"),
},
{
key: "openai_advanced_scheduler_weight_error_rate",
label: t("admin.settings.openaiExperimentalScheduler.errorRateWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_error_rate", "0.8"),
},
{
key: "openai_advanced_scheduler_weight_ttft",
label: t("admin.settings.openaiExperimentalScheduler.ttftWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_ttft", "0.5"),
},
{
key: "openai_advanced_scheduler_weight_reset",
label: t("admin.settings.openaiExperimentalScheduler.resetWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_reset", "0"),
},
{
key: "openai_advanced_scheduler_weight_quota_headroom",
label: t("admin.settings.openaiExperimentalScheduler.quotaHeadroomWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_quota_headroom", "0"),
},
{
key: "openai_advanced_scheduler_weight_previous_response",
label: t("admin.settings.openaiExperimentalScheduler.previousResponseWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_previous_response", "5"),
},
{
key: "openai_advanced_scheduler_weight_session_sticky",
label: t("admin.settings.openaiExperimentalScheduler.sessionStickyWeight"),
placeholder: placeholder("openai_advanced_scheduler_effective_weight_session_sticky", "3"),
},
];
});
const authSourceDefaults = reactive<AuthSourceDefaultsState>(
buildAuthSourceDefaultsState({}),
);
@@ -9359,6 +9555,30 @@ async function saveSettings() {
form.payment_cancel_rate_limit_window_mode,
payment_alipay_force_qrcode: form.payment_alipay_force_qrcode,
openai_advanced_scheduler_enabled: form.openai_advanced_scheduler_enabled,
openai_advanced_scheduler_sticky_weighted_enabled:
form.openai_advanced_scheduler_sticky_weighted_enabled,
openai_advanced_scheduler_subscription_priority_enabled:
form.openai_advanced_scheduler_subscription_priority_enabled,
openai_advanced_scheduler_lb_top_k:
form.openai_advanced_scheduler_lb_top_k.trim(),
openai_advanced_scheduler_weight_priority:
form.openai_advanced_scheduler_weight_priority.trim(),
openai_advanced_scheduler_weight_load:
form.openai_advanced_scheduler_weight_load.trim(),
openai_advanced_scheduler_weight_queue:
form.openai_advanced_scheduler_weight_queue.trim(),
openai_advanced_scheduler_weight_error_rate:
form.openai_advanced_scheduler_weight_error_rate.trim(),
openai_advanced_scheduler_weight_ttft:
form.openai_advanced_scheduler_weight_ttft.trim(),
openai_advanced_scheduler_weight_reset:
form.openai_advanced_scheduler_weight_reset.trim(),
openai_advanced_scheduler_weight_quota_headroom:
form.openai_advanced_scheduler_weight_quota_headroom.trim(),
openai_advanced_scheduler_weight_previous_response:
form.openai_advanced_scheduler_weight_previous_response.trim(),
openai_advanced_scheduler_weight_session_sticky:
form.openai_advanced_scheduler_weight_session_sticky.trim(),
// 余额、订阅到期与账号限额通知
balance_low_notify_enabled: form.balance_low_notify_enabled,
balance_low_notify_threshold:
@@ -161,6 +161,23 @@ vi.mock("vue-i18n", async () => {
"admin.settings.payment.findProvider": "查看支持的支付方式",
"admin.settings.openaiExperimentalScheduler.title": "OpenAI 实验调度策略",
"admin.settings.openaiExperimentalScheduler.description": "默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑,不代表上游 OpenAI 官方能力。",
"admin.settings.openaiExperimentalScheduler.stickyWeightedTitle": "粘性加权",
"admin.settings.openaiExperimentalScheduler.stickyWeightedDescription": "开启后 previous_response_id 和 session_hash 粘性进入高级调度打分;关闭时仍按旧逻辑硬命中粘性账号。",
"admin.settings.openaiExperimentalScheduler.subscriptionPriorityTitle": "订阅优先",
"admin.settings.openaiExperimentalScheduler.subscriptionPriorityDescription": "开启后先在 ChatGPT 订阅账号池中按权值选取;订阅池拿不到席位时再回退到非订阅账号池。",
"admin.settings.openaiExperimentalScheduler.weightsTitle": "调度权值覆盖",
"admin.settings.openaiExperimentalScheduler.weightsDescription": "留空时使用配置/环境变量值;配置未设置时使用内置默认值。页面非空设置优先。",
"admin.settings.openaiExperimentalScheduler.defaultPlaceholder": "配置/默认:{value}",
"admin.settings.openaiExperimentalScheduler.topKLabel": "TopK",
"admin.settings.openaiExperimentalScheduler.priorityWeight": "优先级",
"admin.settings.openaiExperimentalScheduler.loadWeight": "负载",
"admin.settings.openaiExperimentalScheduler.queueWeight": "排队",
"admin.settings.openaiExperimentalScheduler.errorRateWeight": "错误率",
"admin.settings.openaiExperimentalScheduler.ttftWeight": "首包延迟",
"admin.settings.openaiExperimentalScheduler.resetWeight": "重置窗口",
"admin.settings.openaiExperimentalScheduler.quotaHeadroomWeight": "额度余量",
"admin.settings.openaiExperimentalScheduler.previousResponseWeight": "previous_response 粘性",
"admin.settings.openaiExperimentalScheduler.sessionStickyWeight": "session_hash 粘性",
"admin.settings.site.uploadImage": "上传图片",
"admin.settings.site.remove": "移除",
"admin.settings.platformQuota.platform": "平台",
@@ -411,6 +428,28 @@ const baseSettingsResponse = {
payment_visible_method_alipay_enabled: true,
payment_visible_method_wxpay_enabled: true,
openai_advanced_scheduler_enabled: false,
openai_advanced_scheduler_sticky_weighted_enabled: false,
openai_advanced_scheduler_subscription_priority_enabled: false,
openai_advanced_scheduler_lb_top_k: "",
openai_advanced_scheduler_weight_priority: "",
openai_advanced_scheduler_weight_load: "",
openai_advanced_scheduler_weight_queue: "",
openai_advanced_scheduler_weight_error_rate: "",
openai_advanced_scheduler_weight_ttft: "",
openai_advanced_scheduler_weight_reset: "",
openai_advanced_scheduler_weight_quota_headroom: "",
openai_advanced_scheduler_weight_previous_response: "",
openai_advanced_scheduler_weight_session_sticky: "",
openai_advanced_scheduler_effective_lb_top_k: "7",
openai_advanced_scheduler_effective_weight_priority: "1",
openai_advanced_scheduler_effective_weight_load: "1",
openai_advanced_scheduler_effective_weight_queue: "0.7",
openai_advanced_scheduler_effective_weight_error_rate: "0.8",
openai_advanced_scheduler_effective_weight_ttft: "0.5",
openai_advanced_scheduler_effective_weight_reset: "0",
openai_advanced_scheduler_effective_weight_quota_headroom: "0",
openai_advanced_scheduler_effective_weight_previous_response: "5",
openai_advanced_scheduler_effective_weight_session_sticky: "3",
balance_low_notify_enabled: false,
balance_low_notify_threshold: 0,
balance_low_notify_recharge_url: "",