feat(queue): 增加非文本模型分布式准入队列
使用 PostgreSQL 统一同步与异步非文本任务的并发准入、持久化等待和 Worker 容量分配,并将生产 API 与独立 Worker 角色拆分。 补充策略管理、共享契约、OpenAPI、Kubernetes 双节点 Worker 清单及跨节点验收脚本;未默认启用任何生产 queue_size 策略。 已在原基线完成 Go、前端、迁移、Shell、Kustomize 与长任务容量验收;合入最新主干后将重新执行发布门禁。
This commit is contained in:
@@ -498,6 +498,8 @@ export function PlatformManagementPanel(props: {
|
||||
<Label>RPS / 每秒请求数<Input value={form.rpsLimit} placeholder="不填或负数为不限" inputMode="numeric" onChange={(event) => setForm({ ...form, rpsLimit: event.target.value })} /></Label>
|
||||
<Label>TPM / 分钟 Token<Input value={form.tpmLimit} placeholder="不填或负数为不限" inputMode="numeric" onChange={(event) => setForm({ ...form, tpmLimit: event.target.value })} /></Label>
|
||||
<Label>并发请求<Input value={form.concurrencyLimit} placeholder="不填或负数为不限" inputMode="numeric" onChange={(event) => setForm({ ...form, concurrencyLimit: event.target.value })} /></Label>
|
||||
<Label>排队长度<Input value={form.queueSize} placeholder="0 或留空关闭排队" inputMode="numeric" onChange={(event) => setForm({ ...form, queueSize: event.target.value })} /></Label>
|
||||
<Label>最长等待(秒)<Input value={form.queueMaxWaitSeconds} placeholder="默认 600" inputMode="numeric" disabled={!Number(form.queueSize)} onChange={(event) => setForm({ ...form, queueMaxWaitSeconds: event.target.value })} /></Label>
|
||||
<div className="platformTogglePair">
|
||||
<ToggleField checked={form.supportBase64Input} label="支持 Base64 输入" onChange={(checked) => setForm({ ...form, supportBase64Input: checked })} />
|
||||
<ToggleField checked={form.supportUrlInput} label="支持 URL 输入" onChange={(checked) => setForm({ ...form, supportUrlInput: checked })} />
|
||||
@@ -567,6 +569,8 @@ function ModelBindingPolicy(props: { form: PlatformWizardForm; onChange: (value:
|
||||
<Label>模型 RPS<Input value={form.modelRpsLimit} placeholder="不填则不限制" inputMode="numeric" onChange={(event) => onChange({ ...form, modelRpsLimit: event.target.value, modelRateLimitOverrideTouched: true })} /></Label>
|
||||
<Label>模型 TPM<Input value={form.modelTpmLimit} placeholder="不填则不限制" inputMode="numeric" onChange={(event) => onChange({ ...form, modelTpmLimit: event.target.value, modelRateLimitOverrideTouched: true })} /></Label>
|
||||
<Label>模型并发<Input value={form.modelConcurrencyLimit} placeholder="不填则不限制" inputMode="numeric" onChange={(event) => onChange({ ...form, modelConcurrencyLimit: event.target.value, modelRateLimitOverrideTouched: true })} /></Label>
|
||||
<Label>模型排队长度<Input value={form.modelQueueSize} placeholder="0 或留空关闭排队" inputMode="numeric" onChange={(event) => onChange({ ...form, modelQueueSize: event.target.value, modelRateLimitOverrideTouched: true })} /></Label>
|
||||
<Label>模型最长等待(秒)<Input value={form.modelQueueMaxWaitSeconds} placeholder="默认 600" inputMode="numeric" disabled={!Number(form.modelQueueSize)} onChange={(event) => onChange({ ...form, modelQueueMaxWaitSeconds: event.target.value, modelRateLimitOverrideTouched: true })} /></Label>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -1151,9 +1155,11 @@ function platformToForm(
|
||||
const config = platform.config ?? {};
|
||||
const retryPolicy = platform.retryPolicy ?? {};
|
||||
const rateLimitPolicy = platform.rateLimitPolicy ?? {};
|
||||
const rateLimitParts = splitRateLimitPolicy(rateLimitPolicy);
|
||||
const networkProxy = readNetworkProxyConfig(config);
|
||||
const currentModels = platformModels.filter((model) => model.platformId === platform.id);
|
||||
const modelRateLimitPolicy = currentModels.find((model) => model.rateLimitPolicyMode === 'override')?.rateLimitPolicy ?? {};
|
||||
const modelRateLimitParts = splitRateLimitPolicy(modelRateLimitPolicy);
|
||||
return {
|
||||
...createEmptyPlatformForm(platform.provider, defaults),
|
||||
provider: platform.provider,
|
||||
@@ -1176,6 +1182,11 @@ function platformToForm(
|
||||
rpsLimit: readLimit(rateLimitPolicy, 'rps'),
|
||||
tpmLimit: readLimit(rateLimitPolicy, 'tpm_total'),
|
||||
concurrencyLimit: readLimit(rateLimitPolicy, 'concurrent'),
|
||||
queueSize: readLimit(rateLimitPolicy, 'queue_size'),
|
||||
queueMaxWaitSeconds: readRateLimitValue(rateLimitPolicy, 'queue_size', 'maxWaitSeconds', '600'),
|
||||
rateLimitPolicyExtra: rateLimitParts.extra,
|
||||
rateLimitRuleExtras: rateLimitParts.ruleExtras,
|
||||
rateLimitPreservedRules: rateLimitParts.preservedRules,
|
||||
testMode: readBoolean(config, 'testMode', false),
|
||||
proxyMode: networkProxy.proxyMode,
|
||||
httpProxy: networkProxy.httpProxy,
|
||||
@@ -1190,6 +1201,11 @@ function platformToForm(
|
||||
modelRpsLimit: readLimit(modelRateLimitPolicy, 'rps'),
|
||||
modelTpmLimit: readLimit(modelRateLimitPolicy, 'tpm_total'),
|
||||
modelConcurrencyLimit: readLimit(modelRateLimitPolicy, 'concurrent'),
|
||||
modelQueueSize: readLimit(modelRateLimitPolicy, 'queue_size'),
|
||||
modelQueueMaxWaitSeconds: readRateLimitValue(modelRateLimitPolicy, 'queue_size', 'maxWaitSeconds', '600'),
|
||||
modelRateLimitPolicyExtra: modelRateLimitParts.extra,
|
||||
modelRateLimitRuleExtras: modelRateLimitParts.ruleExtras,
|
||||
modelRateLimitPreservedRules: modelRateLimitParts.preservedRules,
|
||||
selectionMode: 'partial',
|
||||
};
|
||||
}
|
||||
@@ -1283,6 +1299,36 @@ function readLimit(policy: Record<string, unknown>, metric: string) {
|
||||
return typeof rule?.limit === 'number' ? String(rule.limit) : '';
|
||||
}
|
||||
|
||||
function readRateLimitValue(policy: Record<string, unknown>, metric: string, key: string, fallback = '') {
|
||||
const rules = Array.isArray(policy.rules) ? policy.rules : [];
|
||||
const rule = rules.find((item): item is Record<string, unknown> => typeof item === 'object' && item !== null && item.metric === metric);
|
||||
const value = rule?.[key];
|
||||
return typeof value === 'number' && Number.isFinite(value) ? String(value) : fallback;
|
||||
}
|
||||
|
||||
function splitRateLimitPolicy(policy: Record<string, unknown>) {
|
||||
const editableMetrics = new Set(['rpm', 'rps', 'tpm_total', 'concurrent', 'queue_size']);
|
||||
const extra = { ...policy };
|
||||
delete extra.rules;
|
||||
const ruleExtras: Record<string, Record<string, unknown>> = {};
|
||||
const preservedRules: Array<Record<string, unknown>> = [];
|
||||
const rules = Array.isArray(policy.rules) ? policy.rules : [];
|
||||
rules.forEach((rawRule) => {
|
||||
const rule = readRecord(rawRule);
|
||||
const metric = typeof rule.metric === 'string' ? rule.metric.trim() : '';
|
||||
if (!editableMetrics.has(metric)) {
|
||||
preservedRules.push({ ...rule });
|
||||
return;
|
||||
}
|
||||
const ruleExtra = { ...rule };
|
||||
delete ruleExtra.metric;
|
||||
delete ruleExtra.limit;
|
||||
delete ruleExtra.maxWaitSeconds;
|
||||
ruleExtras[metric] = ruleExtra;
|
||||
});
|
||||
return { extra, ruleExtras, preservedRules };
|
||||
}
|
||||
|
||||
function numberText(value: unknown, fallback: string) {
|
||||
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? String(value) : fallback;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user