From d7384e69d9d0af7cb19db2a1c147fe008d2780c2 Mon Sep 17 00:00:00 2001 From: fawney19 Date: Tue, 23 Dec 2025 00:11:10 +0800 Subject: [PATCH] fix: improve code quality and add type safety for Key updates - Replace f-string logging with lazy formatting in keys.py (lines 256, 265) - Add EndpointAPIKeyUpdate type interface for frontend type safety - Use typed EndpointAPIKeyUpdate instead of any in KeyFormDialog.vue --- frontend/src/api/endpoints/types.ts | 18 ++++++++++++++++ .../providers/components/KeyFormDialog.vue | 21 ++++++++++++------- .../components/ProviderDetailDrawer.vue | 4 ++-- src/api/admin/endpoints/keys.py | 11 +++++++++- src/config/constants.py | 5 ++++- src/models/endpoint_models.py | 7 +++++-- 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/frontend/src/api/endpoints/types.ts b/frontend/src/api/endpoints/types.ts index 2655fcf..ea82766 100644 --- a/frontend/src/api/endpoints/types.ts +++ b/frontend/src/api/endpoints/types.ts @@ -110,6 +110,24 @@ export interface EndpointAPIKey { request_results_window?: Array<{ ts: number; ok: boolean }> // 请求结果滑动窗口 } +export interface EndpointAPIKeyUpdate { + name?: string + api_key?: string // 仅在需要更新时提供 + rate_multiplier?: number + internal_priority?: number + global_priority?: number | null + max_concurrent?: number | null // null 表示切换为自适应模式 + rate_limit?: number + daily_limit?: number + monthly_limit?: number + allowed_models?: string[] | null + capabilities?: Record | null + cache_ttl_minutes?: number + max_probe_interval_minutes?: number + note?: string + is_active?: boolean +} + export interface EndpointHealthDetail { api_format: string health_score: number diff --git a/frontend/src/features/providers/components/KeyFormDialog.vue b/frontend/src/features/providers/components/KeyFormDialog.vue index 17931b5..3c2c70c 100644 --- a/frontend/src/features/providers/components/KeyFormDialog.vue +++ b/frontend/src/features/providers/components/KeyFormDialog.vue @@ -260,6 +260,7 @@ import { updateEndpointKey, getAllCapabilities, type EndpointAPIKey, + type EndpointAPIKeyUpdate, type ProviderEndpoint, type CapabilityDefinition } from '@/api/endpoints' @@ -386,10 +387,11 @@ function loadKeyData() { api_key: '', rate_multiplier: props.editingKey.rate_multiplier || 1.0, internal_priority: props.editingKey.internal_priority ?? 50, - max_concurrent: props.editingKey.max_concurrent || undefined, - rate_limit: props.editingKey.rate_limit || undefined, - daily_limit: props.editingKey.daily_limit || undefined, - monthly_limit: props.editingKey.monthly_limit || undefined, + // 保留原始的 null/undefined 状态,null 表示自适应模式 + max_concurrent: props.editingKey.max_concurrent ?? undefined, + rate_limit: props.editingKey.rate_limit ?? undefined, + daily_limit: props.editingKey.daily_limit ?? undefined, + monthly_limit: props.editingKey.monthly_limit ?? undefined, cache_ttl_minutes: props.editingKey.cache_ttl_minutes ?? 5, max_probe_interval_minutes: props.editingKey.max_probe_interval_minutes ?? 32, note: props.editingKey.note || '', @@ -439,12 +441,17 @@ async function handleSave() { saving.value = true try { if (props.editingKey) { - // 更新 - const updateData: any = { + // 更新模式 + // 注意:max_concurrent 需要显式发送 null 来切换到自适应模式 + // undefined 会在 JSON 中被忽略,所以用 null 表示"清空/自适应" + const updateData: EndpointAPIKeyUpdate = { name: form.value.name, rate_multiplier: form.value.rate_multiplier, internal_priority: form.value.internal_priority, - max_concurrent: form.value.max_concurrent, + // 显式使用 null 表示自适应模式,这样后端能区分"未提供"和"设置为 null" + // 注意:只有 max_concurrent 需要这种处理,因为它有"自适应模式"的概念 + // 其他限制字段(rate_limit 等)不支持"清空"操作,undefined 会被 JSON 忽略即不更新 + max_concurrent: form.value.max_concurrent === undefined ? null : form.value.max_concurrent, rate_limit: form.value.rate_limit, daily_limit: form.value.daily_limit, monthly_limit: form.value.monthly_limit, diff --git a/frontend/src/features/providers/components/ProviderDetailDrawer.vue b/frontend/src/features/providers/components/ProviderDetailDrawer.vue index 7f04151..d4aef38 100644 --- a/frontend/src/features/providers/components/ProviderDetailDrawer.vue +++ b/frontend/src/features/providers/components/ProviderDetailDrawer.vue @@ -483,9 +483,9 @@ - {{ key.is_adaptive ? '自适应' : '固定' }}并发: {{ key.learned_max_concurrent || key.max_concurrent || 3 }} + {{ key.is_adaptive ? '自适应' : '固定' }}并发: {{ key.is_adaptive ? (key.learned_max_concurrent ?? '学习中') : key.max_concurrent }} diff --git a/src/api/admin/endpoints/keys.py b/src/api/admin/endpoints/keys.py index a2160ff..f5e39ae 100644 --- a/src/api/admin/endpoints/keys.py +++ b/src/api/admin/endpoints/keys.py @@ -246,6 +246,15 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter): if "api_key" in update_data: update_data["api_key"] = crypto_service.encrypt(update_data["api_key"]) + # 特殊处理 max_concurrent:需要区分"未提供"和"显式设置为 null" + # 当 max_concurrent 被显式设置时(在 model_fields_set 中),即使值为 None 也应该更新 + if "max_concurrent" in self.key_data.model_fields_set: + update_data["max_concurrent"] = self.key_data.max_concurrent + # 切换到自适应模式时,清空学习到的并发限制,让系统重新学习 + if self.key_data.max_concurrent is None: + update_data["learned_max_concurrent"] = None + logger.info("Key %s 切换为自适应并发模式", self.key_id) + for field, value in update_data.items(): setattr(key, field, value) key.updated_at = datetime.now(timezone.utc) @@ -253,7 +262,7 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter): db.commit() db.refresh(key) - logger.info(f"[OK] 更新 Key: ID={self.key_id}, Updates={list(update_data.keys())}") + logger.info("[OK] 更新 Key: ID=%s, Updates=%s", self.key_id, list(update_data.keys())) try: decrypted_key = crypto_service.decrypt(key.api_key) diff --git a/src/config/constants.py b/src/config/constants.py index 70d8467..d9977ab 100644 --- a/src/config/constants.py +++ b/src/config/constants.py @@ -77,7 +77,10 @@ class ConcurrencyDefaults: MAX_CONCURRENT_LIMIT = 200 # 最小并发限制下限 - MIN_CONCURRENT_LIMIT = 1 + # 设置为 3 而不是 1,因为预留机制(10%预留给缓存用户)会导致 + # 当 learned_max_concurrent=1 时新用户实际可用槽位为 0,永远无法命中 + # 注意:当 limit < 10 时,预留机制实际不生效(预留槽位 = 0),这是可接受的 + MIN_CONCURRENT_LIMIT = 3 # === 探测性扩容参数 === # 探测性扩容间隔(分钟)- 长时间无 429 且有流量时尝试扩容 diff --git a/src/models/endpoint_models.py b/src/models/endpoint_models.py index cbbf025..6eb734d 100644 --- a/src/models/endpoint_models.py +++ b/src/models/endpoint_models.py @@ -226,8 +226,11 @@ class EndpointAPIKeyUpdate(BaseModel): global_priority: Optional[int] = Field( default=None, description="全局 Key 优先级(全局 Key 优先模式,数字越小越优先)" ) - # 注意:max_concurrent=None 表示不更新,要切换为自适应模式请使用专用 API - max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数") + # max_concurrent: 使用特殊标记区分"未提供"和"设置为 null(自适应模式)" + # - 不提供字段:不更新 + # - 提供 null:切换为自适应模式 + # - 提供数字:设置固定并发限制 + max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数(null=自适应模式)") rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制") daily_limit: Optional[int] = Field(default=None, ge=1, description="每日限制") monthly_limit: Optional[int] = Field(default=None, ge=1, description="每月限制")