refactor(model-permissions): 简化 allowed_models 为纯列表格式

移除按 API 格式区分的字典模式(Dict[str, List[str]]),统一使用简单列表格式。

- 删除 normalize_allowed_models 的 api_format 参数
- 删除 check_model_allowed 的 api_format 参数
- 简化 merge_allowed_models 为直接列表交集
- 移除前端的字典模式兼容代码和警告 UI
- 删除 is_format_mode、convert_to_format_mode 等辅助函数
This commit is contained in:
fawney19
2026-01-14 17:57:21 +08:00
parent 133f3108f0
commit b272109055
9 changed files with 62 additions and 345 deletions

View File

@@ -4,7 +4,7 @@ ProviderEndpoint 相关的 API 模型定义
import re
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -145,9 +145,9 @@ class EndpointAPIKeyCreate(BaseModel):
rpm_limit: Optional[int] = Field(
default=None, ge=1, le=10000, description="RPM 限制NULL=自适应模式)"
)
allowed_models: Optional[Union[List[str], Dict[str, List[str]]]] = Field(
allowed_models: Optional[List[str]] = Field(
default=None,
description="允许使用的模型列表null=不限制,列表=简单白名单,字典=按API格式区分",
description="允许使用的模型列表null=不限制)",
)
# 能力标签
@@ -200,67 +200,27 @@ class EndpointAPIKeyCreate(BaseModel):
@field_validator("allowed_models")
@classmethod
def validate_allowed_models(
cls, v: Optional[Union[List[str], Dict[str, List[str]]]]
) -> Optional[Union[List[str], Dict[str, List[str]]]]:
def validate_allowed_models(cls, v: Optional[List[str]]) -> Optional[List[str]]:
"""
规范化 allowed_models
- 列表模式:去空、去重、保留顺序
- 字典模式key 统一大写(支持 "*"value 去空、去重、保留顺序
规范化 allowed_models去空、去重、保留顺序
"""
if v is None:
return v
if isinstance(v, list):
cleaned: List[str] = []
seen: set[str] = set()
for item in v:
if not isinstance(item, str):
raise ValueError("allowed_models 列表必须为字符串数组")
name = item.strip()
if not name or name in seen:
continue
seen.add(name)
cleaned.append(name)
return cleaned
if not isinstance(v, list):
raise ValueError("allowed_models 必须是列表")
if isinstance(v, dict):
from src.core.enums import APIFormat
allowed_formats = {fmt.value for fmt in APIFormat}
normalized: Dict[str, List[str]] = {}
for raw_key, models in v.items():
if not isinstance(raw_key, str):
raise ValueError("allowed_models 字典的 key 必须为字符串")
key = raw_key.upper()
if key != "*" and key not in allowed_formats:
raise ValueError(
f"allowed_models 字典的 key 必须是 {sorted(allowed_formats)}'*',当前值: {raw_key}"
)
if models is None:
# null 表示该格式不限制,跳过(不加入字典)
continue
if not isinstance(models, list):
raise ValueError("allowed_models 字典的 value 必须为字符串数组")
cleaned: List[str] = []
seen: set[str] = set()
for item in models:
if not isinstance(item, str):
raise ValueError("allowed_models 字典的 value 必须为字符串数组")
name = item.strip()
if not name or name in seen:
continue
seen.add(name)
cleaned.append(name)
normalized[key] = cleaned
return normalized
raise ValueError("allowed_models 必须是列表或字典")
cleaned: List[str] = []
seen: set[str] = set()
for item in v:
if not isinstance(item, str):
raise ValueError("allowed_models 列表元素必须为字符串")
name = item.strip()
if not name or name in seen:
continue
seen.add(name)
cleaned.append(name)
return cleaned
@field_validator("api_key")
@classmethod
@@ -330,9 +290,9 @@ class EndpointAPIKeyUpdate(BaseModel):
rpm_limit: Optional[int] = Field(
default=None, ge=1, le=10000, description="RPM 限制null=自适应模式)"
)
allowed_models: Optional[Union[List[str], Dict[str, List[str]]]] = Field(
allowed_models: Optional[List[str]] = Field(
default=None,
description="允许使用的模型列表null=不限制,列表=简单白名单,字典=按API格式区分",
description="允许使用的模型列表null=不限制)",
)
capabilities: Optional[Dict[str, bool]] = Field(
default=None, description="Key 能力标签,如 {'cache_1h': true, 'context_1m': true}"
@@ -376,9 +336,7 @@ class EndpointAPIKeyUpdate(BaseModel):
@field_validator("allowed_models")
@classmethod
def validate_allowed_models(
cls, v: Optional[Union[List[str], Dict[str, List[str]]]]
) -> Optional[Union[List[str], Dict[str, List[str]]]]:
def validate_allowed_models(cls, v: Optional[List[str]]) -> Optional[List[str]]:
# 与 EndpointAPIKeyCreate 保持一致
return EndpointAPIKeyCreate.validate_allowed_models(v)
@@ -450,7 +408,7 @@ class EndpointAPIKeyResponse(BaseModel):
internal_priority: int = Field(default=50, description="Endpoint 内部优先级")
global_priority: Optional[int] = Field(default=None, description="全局 Key 优先级")
rpm_limit: Optional[int] = None
allowed_models: Optional[Union[List[str], Dict[str, List[str]]]] = None
allowed_models: Optional[List[str]] = None
capabilities: Optional[Dict[str, bool]] = Field(default=None, description="Key 能力标签")
# 缓存与熔断配置