mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat(models): 添加全局 Key 白名单数据支持模型映射实时匹配
- 新增 GlobalKeyWhitelistItem 类型定义 - 路由预览 API 返回 all_keys_whitelist 字段,包含所有活跃 Provider 的 Key 白名单 - 前端 ModelMappingsTab 改用全局白名单数据进行实时匹配
This commit is contained in:
@@ -17,11 +17,13 @@ from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.pipeline import ApiRequestPipeline
|
||||
from src.core.crypto import CryptoService
|
||||
from src.core.model_permissions import parse_allowed_models_to_list
|
||||
from src.database import get_db
|
||||
from src.models.database import (
|
||||
GlobalModel,
|
||||
Model,
|
||||
Provider,
|
||||
ProviderAPIKey,
|
||||
ProviderEndpoint,
|
||||
)
|
||||
@@ -54,7 +56,9 @@ class RoutingKeyInfo(BaseModel):
|
||||
allowed_models: Optional[List[str]] = Field(None, description="允许的模型列表,null 表示不限制")
|
||||
# 熔断状态
|
||||
circuit_breaker_open: bool = Field(False, description="熔断器是否打开")
|
||||
circuit_breaker_formats: List[str] = Field(default_factory=list, description="熔断的 API 格式列表")
|
||||
circuit_breaker_formats: List[str] = Field(
|
||||
default_factory=list, description="熔断的 API 格式列表"
|
||||
)
|
||||
next_probe_at: Optional[str] = Field(None, description="下次探测时间(ISO格式)")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -108,6 +112,19 @@ class RoutingProviderInfo(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GlobalKeyWhitelistItem(BaseModel):
|
||||
"""全局 Key 白名单项(用于前端实时匹配)"""
|
||||
|
||||
key_id: str = Field(..., description="Key ID")
|
||||
key_name: str = Field(..., description="Key 名称")
|
||||
masked_key: str = Field(..., description="脱敏的 API Key")
|
||||
provider_id: str = Field(..., description="Provider ID")
|
||||
provider_name: str = Field(..., description="Provider 名称")
|
||||
allowed_models: List[str] = Field(default_factory=list, description="Key 白名单模型列表")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class ModelRoutingPreviewResponse(BaseModel):
|
||||
"""模型请求链路预览响应"""
|
||||
|
||||
@@ -124,6 +141,10 @@ class ModelRoutingPreviewResponse(BaseModel):
|
||||
# 调度配置
|
||||
scheduling_mode: str = Field("cache_affinity", description="调度模式")
|
||||
priority_mode: str = Field("provider", description="优先级模式")
|
||||
# 全局 Key 白名单数据(供前端实时匹配,包含所有 Provider 的 Key)
|
||||
all_keys_whitelist: List[GlobalKeyWhitelistItem] = Field(
|
||||
default_factory=list, description="所有 Provider 的 Key 白名单数据"
|
||||
)
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -181,9 +202,7 @@ class AdminGetModelRoutingPreviewAdapter(AdminApiAdapter):
|
||||
db = context.db
|
||||
|
||||
# 获取 GlobalModel
|
||||
global_model = (
|
||||
db.query(GlobalModel).filter(GlobalModel.id == self.global_model_id).first()
|
||||
)
|
||||
global_model = db.query(GlobalModel).filter(GlobalModel.id == self.global_model_id).first()
|
||||
if not global_model:
|
||||
from fastapi import HTTPException
|
||||
|
||||
@@ -217,9 +236,7 @@ class AdminGetModelRoutingPreviewAdapter(AdminApiAdapter):
|
||||
keys_by_provider: Dict[str, List[ProviderAPIKey]] = {}
|
||||
if provider_ids:
|
||||
keys = (
|
||||
db.query(ProviderAPIKey)
|
||||
.filter(ProviderAPIKey.provider_id.in_(provider_ids))
|
||||
.all()
|
||||
db.query(ProviderAPIKey).filter(ProviderAPIKey.provider_id.in_(provider_ids)).all()
|
||||
)
|
||||
for key in keys:
|
||||
if key.provider_id not in keys_by_provider:
|
||||
@@ -282,7 +299,6 @@ class AdminGetModelRoutingPreviewAdapter(AdminApiAdapter):
|
||||
# 生成脱敏 SK(先解密再脱敏)
|
||||
masked_key = ""
|
||||
if key.api_key:
|
||||
from src.core.crypto import CryptoService
|
||||
crypto = CryptoService()
|
||||
try:
|
||||
decrypted_key = crypto.decrypt(key.api_key, silent=True)
|
||||
@@ -384,16 +400,70 @@ class AdminGetModelRoutingPreviewAdapter(AdminApiAdapter):
|
||||
active_providers = sum(1 for p in provider_infos if p.is_active and p.model_is_active)
|
||||
|
||||
# 从数据库获取当前调度配置
|
||||
scheduling_mode = SystemConfigService.get_config(
|
||||
db,
|
||||
"scheduling_mode",
|
||||
CacheAwareScheduler.SCHEDULING_MODE_CACHE_AFFINITY,
|
||||
) or CacheAwareScheduler.SCHEDULING_MODE_CACHE_AFFINITY
|
||||
priority_mode = SystemConfigService.get_config(
|
||||
db,
|
||||
"provider_priority_mode",
|
||||
CacheAwareScheduler.PRIORITY_MODE_PROVIDER,
|
||||
) or CacheAwareScheduler.PRIORITY_MODE_PROVIDER
|
||||
scheduling_mode = (
|
||||
SystemConfigService.get_config(
|
||||
db,
|
||||
"scheduling_mode",
|
||||
CacheAwareScheduler.SCHEDULING_MODE_CACHE_AFFINITY,
|
||||
)
|
||||
or CacheAwareScheduler.SCHEDULING_MODE_CACHE_AFFINITY
|
||||
)
|
||||
priority_mode = (
|
||||
SystemConfigService.get_config(
|
||||
db,
|
||||
"provider_priority_mode",
|
||||
CacheAwareScheduler.PRIORITY_MODE_PROVIDER,
|
||||
)
|
||||
or CacheAwareScheduler.PRIORITY_MODE_PROVIDER
|
||||
)
|
||||
|
||||
# 获取所有活跃 Provider 的 Key 白名单数据(供前端实时匹配)
|
||||
all_keys_whitelist: List[GlobalKeyWhitelistItem] = []
|
||||
crypto = CryptoService()
|
||||
|
||||
# 获取所有活跃的 Key(带白名单),使用 selectinload 避免 N+1 查询
|
||||
all_keys = (
|
||||
db.query(ProviderAPIKey)
|
||||
.join(Provider, ProviderAPIKey.provider_id == Provider.id)
|
||||
.options(selectinload(ProviderAPIKey.provider))
|
||||
.filter(ProviderAPIKey.is_active == True)
|
||||
.filter(Provider.is_active == True)
|
||||
.filter(ProviderAPIKey.allowed_models.isnot(None)) # 只获取有白名单的 Key
|
||||
.all()
|
||||
)
|
||||
|
||||
# 转换为白名单数据
|
||||
for key in all_keys:
|
||||
if not key.allowed_models:
|
||||
continue
|
||||
|
||||
# 解析白名单
|
||||
allowed_models_list = parse_allowed_models_to_list(key.allowed_models)
|
||||
if not allowed_models_list:
|
||||
continue
|
||||
|
||||
# 生成脱敏 Key
|
||||
masked = ""
|
||||
if key.api_key:
|
||||
try:
|
||||
decrypted = crypto.decrypt(key.api_key, silent=True)
|
||||
except Exception:
|
||||
decrypted = key.api_key
|
||||
if len(decrypted) > 8:
|
||||
masked = f"{decrypted[:4]}***{decrypted[-4:]}"
|
||||
else:
|
||||
masked = f"{decrypted[:2]}***"
|
||||
|
||||
all_keys_whitelist.append(
|
||||
GlobalKeyWhitelistItem(
|
||||
key_id=key.id or "",
|
||||
key_name=key.name or "",
|
||||
masked_key=masked,
|
||||
provider_id=key.provider_id or "",
|
||||
provider_name=key.provider.name if key.provider else "",
|
||||
allowed_models=allowed_models_list,
|
||||
)
|
||||
)
|
||||
|
||||
return ModelRoutingPreviewResponse(
|
||||
global_model_id=global_model.id,
|
||||
@@ -405,4 +475,5 @@ class AdminGetModelRoutingPreviewAdapter(AdminApiAdapter):
|
||||
active_providers=active_providers,
|
||||
scheduling_mode=scheduling_mode,
|
||||
priority_mode=priority_mode,
|
||||
all_keys_whitelist=all_keys_whitelist,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user