mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-02 15:52:26 +08:00
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
This commit is contained in:
@@ -110,6 +110,24 @@ export interface EndpointAPIKey {
|
|||||||
request_results_window?: Array<{ ts: number; ok: boolean }> // 请求结果滑动窗口
|
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<string, boolean> | null
|
||||||
|
cache_ttl_minutes?: number
|
||||||
|
max_probe_interval_minutes?: number
|
||||||
|
note?: string
|
||||||
|
is_active?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface EndpointHealthDetail {
|
export interface EndpointHealthDetail {
|
||||||
api_format: string
|
api_format: string
|
||||||
health_score: number
|
health_score: number
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ import {
|
|||||||
updateEndpointKey,
|
updateEndpointKey,
|
||||||
getAllCapabilities,
|
getAllCapabilities,
|
||||||
type EndpointAPIKey,
|
type EndpointAPIKey,
|
||||||
|
type EndpointAPIKeyUpdate,
|
||||||
type ProviderEndpoint,
|
type ProviderEndpoint,
|
||||||
type CapabilityDefinition
|
type CapabilityDefinition
|
||||||
} from '@/api/endpoints'
|
} from '@/api/endpoints'
|
||||||
@@ -386,10 +387,11 @@ function loadKeyData() {
|
|||||||
api_key: '',
|
api_key: '',
|
||||||
rate_multiplier: props.editingKey.rate_multiplier || 1.0,
|
rate_multiplier: props.editingKey.rate_multiplier || 1.0,
|
||||||
internal_priority: props.editingKey.internal_priority ?? 50,
|
internal_priority: props.editingKey.internal_priority ?? 50,
|
||||||
max_concurrent: props.editingKey.max_concurrent || undefined,
|
// 保留原始的 null/undefined 状态,null 表示自适应模式
|
||||||
rate_limit: props.editingKey.rate_limit || undefined,
|
max_concurrent: props.editingKey.max_concurrent ?? undefined,
|
||||||
daily_limit: props.editingKey.daily_limit || undefined,
|
rate_limit: props.editingKey.rate_limit ?? undefined,
|
||||||
monthly_limit: props.editingKey.monthly_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,
|
cache_ttl_minutes: props.editingKey.cache_ttl_minutes ?? 5,
|
||||||
max_probe_interval_minutes: props.editingKey.max_probe_interval_minutes ?? 32,
|
max_probe_interval_minutes: props.editingKey.max_probe_interval_minutes ?? 32,
|
||||||
note: props.editingKey.note || '',
|
note: props.editingKey.note || '',
|
||||||
@@ -439,12 +441,17 @@ async function handleSave() {
|
|||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
if (props.editingKey) {
|
if (props.editingKey) {
|
||||||
// 更新
|
// 更新模式
|
||||||
const updateData: any = {
|
// 注意:max_concurrent 需要显式发送 null 来切换到自适应模式
|
||||||
|
// undefined 会在 JSON 中被忽略,所以用 null 表示"清空/自适应"
|
||||||
|
const updateData: EndpointAPIKeyUpdate = {
|
||||||
name: form.value.name,
|
name: form.value.name,
|
||||||
rate_multiplier: form.value.rate_multiplier,
|
rate_multiplier: form.value.rate_multiplier,
|
||||||
internal_priority: form.value.internal_priority,
|
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,
|
rate_limit: form.value.rate_limit,
|
||||||
daily_limit: form.value.daily_limit,
|
daily_limit: form.value.daily_limit,
|
||||||
monthly_limit: form.value.monthly_limit,
|
monthly_limit: form.value.monthly_limit,
|
||||||
|
|||||||
@@ -483,9 +483,9 @@
|
|||||||
<span
|
<span
|
||||||
v-if="key.max_concurrent || key.is_adaptive"
|
v-if="key.max_concurrent || key.is_adaptive"
|
||||||
class="text-muted-foreground"
|
class="text-muted-foreground"
|
||||||
:title="key.is_adaptive ? `自适应并发限制(学习值: ${key.learned_max_concurrent ?? '未学习'})` : '固定并发限制'"
|
:title="key.is_adaptive ? `自适应并发限制(学习值: ${key.learned_max_concurrent ?? '未学习'})` : `固定并发限制: ${key.max_concurrent}`"
|
||||||
>
|
>
|
||||||
{{ key.is_adaptive ? '自适应' : '固定' }}并发: {{ key.learned_max_concurrent || key.max_concurrent || 3 }}
|
{{ key.is_adaptive ? '自适应' : '固定' }}并发: {{ key.is_adaptive ? (key.learned_max_concurrent ?? '学习中') : key.max_concurrent }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -246,6 +246,15 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter):
|
|||||||
if "api_key" in update_data:
|
if "api_key" in update_data:
|
||||||
update_data["api_key"] = crypto_service.encrypt(update_data["api_key"])
|
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():
|
for field, value in update_data.items():
|
||||||
setattr(key, field, value)
|
setattr(key, field, value)
|
||||||
key.updated_at = datetime.now(timezone.utc)
|
key.updated_at = datetime.now(timezone.utc)
|
||||||
@@ -253,7 +262,7 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter):
|
|||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(key)
|
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:
|
try:
|
||||||
decrypted_key = crypto_service.decrypt(key.api_key)
|
decrypted_key = crypto_service.decrypt(key.api_key)
|
||||||
|
|||||||
@@ -77,7 +77,10 @@ class ConcurrencyDefaults:
|
|||||||
MAX_CONCURRENT_LIMIT = 200
|
MAX_CONCURRENT_LIMIT = 200
|
||||||
|
|
||||||
# 最小并发限制下限
|
# 最小并发限制下限
|
||||||
MIN_CONCURRENT_LIMIT = 1
|
# 设置为 3 而不是 1,因为预留机制(10%预留给缓存用户)会导致
|
||||||
|
# 当 learned_max_concurrent=1 时新用户实际可用槽位为 0,永远无法命中
|
||||||
|
# 注意:当 limit < 10 时,预留机制实际不生效(预留槽位 = 0),这是可接受的
|
||||||
|
MIN_CONCURRENT_LIMIT = 3
|
||||||
|
|
||||||
# === 探测性扩容参数 ===
|
# === 探测性扩容参数 ===
|
||||||
# 探测性扩容间隔(分钟)- 长时间无 429 且有流量时尝试扩容
|
# 探测性扩容间隔(分钟)- 长时间无 429 且有流量时尝试扩容
|
||||||
|
|||||||
@@ -226,8 +226,11 @@ class EndpointAPIKeyUpdate(BaseModel):
|
|||||||
global_priority: Optional[int] = Field(
|
global_priority: Optional[int] = Field(
|
||||||
default=None, description="全局 Key 优先级(全局 Key 优先模式,数字越小越优先)"
|
default=None, description="全局 Key 优先级(全局 Key 优先模式,数字越小越优先)"
|
||||||
)
|
)
|
||||||
# 注意:max_concurrent=None 表示不更新,要切换为自适应模式请使用专用 API
|
# max_concurrent: 使用特殊标记区分"未提供"和"设置为 null(自适应模式)"
|
||||||
max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数")
|
# - 不提供字段:不更新
|
||||||
|
# - 提供 null:切换为自适应模式
|
||||||
|
# - 提供数字:设置固定并发限制
|
||||||
|
max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数(null=自适应模式)")
|
||||||
rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制")
|
rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制")
|
||||||
daily_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="每月限制")
|
monthly_limit: Optional[int] = Field(default=None, ge=1, description="每月限制")
|
||||||
|
|||||||
Reference in New Issue
Block a user