mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
refactor: 将模型别名(alias)统一重命名为模型映射(mapping)
- 重命名 API: alias-mapping-preview -> mapping-preview - 重命名类型: AliasMatchedModel -> MappingMatchedModel 等 - 重命名字段: model_aliases -> model_mappings, alias_matched_model -> mapping_matched_model - 重命名前端组件: ModelAliasesTab -> ModelMappingsTab - 重命名验证函数: validate_model_aliases -> validate_model_mappings - 同步更新相关测试用例 - 补充 ModelService 中新增/批量创建模型时的缓存失效逻辑
This commit is contained in:
54
src/services/cache/aware_scheduler.py
vendored
54
src/services/cache/aware_scheduler.py
vendored
@@ -77,7 +77,7 @@ class ProviderCandidate:
|
||||
is_cached: bool = False
|
||||
is_skipped: bool = False # 是否被跳过
|
||||
skip_reason: Optional[str] = None # 跳过原因
|
||||
alias_matched_model: Optional[str] = None # 通过别名匹配到的模型名(用于实际请求)
|
||||
mapping_matched_model: Optional[str] = None # 通过映射匹配到的模型名(用于实际请求)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -580,7 +580,7 @@ class CacheAwareScheduler:
|
||||
target_format = normalize_api_format(api_format)
|
||||
|
||||
# 0. 解析 model_name 到 GlobalModel(支持直接匹配和映射名匹配,使用 ModelCacheService)
|
||||
global_model = await ModelCacheService.resolve_global_model_by_name_or_alias(db, model_name)
|
||||
global_model = await ModelCacheService.resolve_global_model_by_name_or_mapping(db, model_name)
|
||||
|
||||
if not global_model:
|
||||
logger.warning(f"GlobalModel not found: {model_name}")
|
||||
@@ -591,8 +591,8 @@ class CacheAwareScheduler:
|
||||
requested_model_name = model_name
|
||||
resolved_model_name = str(global_model.name)
|
||||
|
||||
# 提取模型别名(用于 Provider Key 的 allowed_models 匹配)
|
||||
model_aliases: List[str] = (global_model.config or {}).get("model_aliases", [])
|
||||
# 提取模型映射(用于 Provider Key 的 allowed_models 匹配)
|
||||
model_mappings: List[str] = (global_model.config or {}).get("model_mappings", [])
|
||||
|
||||
# 获取合并后的访问限制(ApiKey + User)
|
||||
restrictions = self._get_effective_restrictions(user_api_key)
|
||||
@@ -660,7 +660,7 @@ class CacheAwareScheduler:
|
||||
target_format=target_format,
|
||||
model_name=requested_model_name,
|
||||
resolved_model_name=resolved_model_name,
|
||||
model_aliases=model_aliases,
|
||||
model_mappings=model_mappings,
|
||||
affinity_key=affinity_key,
|
||||
max_candidates=max_candidates,
|
||||
is_stream=is_stream,
|
||||
@@ -774,7 +774,7 @@ class CacheAwareScheduler:
|
||||
- provider_model_names: Provider 侧可用的模型名称集合(主名称 + 映射名称,按 api_format 过滤)
|
||||
"""
|
||||
# 使用 ModelCacheService 解析模型名称(支持映射名)
|
||||
global_model = await ModelCacheService.resolve_global_model_by_name_or_alias(db, model_name)
|
||||
global_model = await ModelCacheService.resolve_global_model_by_name_or_mapping(db, model_name)
|
||||
|
||||
if not global_model:
|
||||
# 完全未找到匹配
|
||||
@@ -883,7 +883,7 @@ class CacheAwareScheduler:
|
||||
model_name: str,
|
||||
capability_requirements: Optional[Dict[str, bool]] = None,
|
||||
resolved_model_name: Optional[str] = None,
|
||||
model_aliases: Optional[List[str]] = None,
|
||||
model_mappings: Optional[List[str]] = None,
|
||||
candidate_models: Optional[set[str]] = None,
|
||||
) -> Tuple[bool, Optional[str], Optional[str]]:
|
||||
"""
|
||||
@@ -897,14 +897,14 @@ class CacheAwareScheduler:
|
||||
model_name: 模型名称
|
||||
capability_requirements: 能力需求(可选)
|
||||
resolved_model_name: 解析后的 GlobalModel.name(可选)
|
||||
model_aliases: GlobalModel 的别名列表(用于通配符匹配)
|
||||
candidate_models: Provider 侧可用的模型名称集合(用于限制别名匹配范围)
|
||||
model_mappings: GlobalModel 的映射列表(用于通配符匹配)
|
||||
candidate_models: Provider 侧可用的模型名称集合(用于限制映射匹配范围)
|
||||
|
||||
Returns:
|
||||
(is_available, skip_reason, alias_matched_model)
|
||||
(is_available, skip_reason, mapping_matched_model)
|
||||
- is_available: Key 是否可用
|
||||
- skip_reason: 不可用时的原因
|
||||
- alias_matched_model: 通过别名匹配到的模型名(用于实际请求)
|
||||
- mapping_matched_model: 通过映射匹配到的模型名(用于实际请求)
|
||||
"""
|
||||
# 检查熔断器状态(使用详细状态方法获取更丰富的跳过原因,按 API 格式)
|
||||
is_available, circuit_reason = health_monitor.get_circuit_breaker_status(
|
||||
@@ -915,33 +915,33 @@ class CacheAwareScheduler:
|
||||
|
||||
# 模型权限检查:使用 allowed_models 白名单
|
||||
# None = 允许所有模型,[] = 拒绝所有模型,["a","b"] = 只允许指定模型
|
||||
# 支持通配符别名匹配(通过 model_aliases)
|
||||
# 支持通配符映射匹配(通过 model_mappings)
|
||||
from src.core.model_permissions import (
|
||||
check_model_allowed_with_aliases,
|
||||
check_model_allowed_with_mappings,
|
||||
get_allowed_models_preview,
|
||||
)
|
||||
|
||||
try:
|
||||
is_allowed, alias_matched_model = check_model_allowed_with_aliases(
|
||||
is_allowed, mapping_matched_model = check_model_allowed_with_mappings(
|
||||
model_name=model_name,
|
||||
allowed_models=key.allowed_models,
|
||||
resolved_model_name=resolved_model_name,
|
||||
model_aliases=model_aliases,
|
||||
model_mappings=model_mappings,
|
||||
candidate_models=candidate_models,
|
||||
)
|
||||
except TimeoutError:
|
||||
# 正则匹配超时(可能是 ReDoS 攻击或复杂模式)
|
||||
logger.warning(f"别名匹配超时: key_id={key.id}, model={model_name}")
|
||||
return False, "别名匹配超时,请简化配置", None
|
||||
logger.warning(f"映射匹配超时: key_id={key.id}, model={model_name}")
|
||||
return False, "映射匹配超时,请简化配置", None
|
||||
except re.error as e:
|
||||
# 正则语法错误(配置问题)
|
||||
logger.warning(f"别名规则无效: key_id={key.id}, model={model_name}, error={e}")
|
||||
return False, f"别名规则无效: {str(e)}", None
|
||||
logger.warning(f"映射规则无效: key_id={key.id}, model={model_name}, error={e}")
|
||||
return False, f"映射规则无效: {str(e)}", None
|
||||
except Exception as e:
|
||||
# 其他未知异常
|
||||
logger.error(f"别名匹配异常: key_id={key.id}, model={model_name}, error={e}", exc_info=True)
|
||||
logger.error(f"映射匹配异常: key_id={key.id}, model={model_name}, error={e}", exc_info=True)
|
||||
# 异常时保守处理:不允许使用该 Key
|
||||
return False, "别名匹配失败", None
|
||||
return False, "映射匹配失败", None
|
||||
|
||||
if not is_allowed:
|
||||
return False, f"模型权限不匹配(允许: {get_allowed_models_preview(key.allowed_models)})", None
|
||||
@@ -957,7 +957,7 @@ class CacheAwareScheduler:
|
||||
if not is_match:
|
||||
return False, skip_reason, None
|
||||
|
||||
return True, None, alias_matched_model
|
||||
return True, None, mapping_matched_model
|
||||
|
||||
async def _build_candidates(
|
||||
self,
|
||||
@@ -967,7 +967,7 @@ class CacheAwareScheduler:
|
||||
model_name: str,
|
||||
affinity_key: Optional[str],
|
||||
resolved_model_name: Optional[str] = None,
|
||||
model_aliases: Optional[List[str]] = None,
|
||||
model_mappings: Optional[List[str]] = None,
|
||||
max_candidates: Optional[int] = None,
|
||||
is_stream: bool = False,
|
||||
capability_requirements: Optional[Dict[str, bool]] = None,
|
||||
@@ -984,7 +984,7 @@ class CacheAwareScheduler:
|
||||
model_name: 模型名称(用户请求的名称,可能是映射名)
|
||||
affinity_key: 亲和性标识符(通常为API Key ID)
|
||||
resolved_model_name: 解析后的 GlobalModel.name(用于 Key.allowed_models 校验)
|
||||
model_aliases: GlobalModel 的别名列表(用于 Key.allowed_models 通配符匹配)
|
||||
model_mappings: GlobalModel 的映射列表(用于 Key.allowed_models 通配符匹配)
|
||||
max_candidates: 最大候选数
|
||||
is_stream: 是否是流式请求,如果为 True 则过滤不支持流式的 Provider
|
||||
capability_requirements: 能力需求(可选)
|
||||
@@ -1047,13 +1047,13 @@ class CacheAwareScheduler:
|
||||
|
||||
for key in keys:
|
||||
# Key 级别的能力检查
|
||||
is_available, skip_reason, alias_matched_model = self._check_key_availability(
|
||||
is_available, skip_reason, mapping_matched_model = self._check_key_availability(
|
||||
key,
|
||||
target_format_str,
|
||||
model_name,
|
||||
capability_requirements,
|
||||
resolved_model_name=resolved_model_name,
|
||||
model_aliases=model_aliases,
|
||||
model_mappings=model_mappings,
|
||||
candidate_models=provider_model_names,
|
||||
)
|
||||
|
||||
@@ -1063,7 +1063,7 @@ class CacheAwareScheduler:
|
||||
key=key,
|
||||
is_skipped=not is_available,
|
||||
skip_reason=skip_reason,
|
||||
alias_matched_model=alias_matched_model,
|
||||
mapping_matched_model=mapping_matched_model,
|
||||
)
|
||||
candidates.append(candidate)
|
||||
|
||||
|
||||
4
src/services/cache/model_cache.py
vendored
4
src/services/cache/model_cache.py
vendored
@@ -15,7 +15,7 @@ Model 映射缓存服务 - 减少模型查询
|
||||
|
||||
使用示例
|
||||
--------
|
||||
global_model = await ModelCacheService.resolve_global_model_by_name_or_alias(db, "gpt-4")
|
||||
global_model = await ModelCacheService.resolve_global_model_by_name_or_mapping(db, "gpt-4")
|
||||
"""
|
||||
|
||||
import time
|
||||
@@ -250,7 +250,7 @@ class ModelCacheService:
|
||||
logger.debug(f"GlobalModel 缓存已清除: {global_model_id}")
|
||||
|
||||
@staticmethod
|
||||
async def resolve_global_model_by_name_or_alias(
|
||||
async def resolve_global_model_by_name_or_mapping(
|
||||
db: Session, model_name: str
|
||||
) -> Optional[GlobalModel]:
|
||||
"""
|
||||
|
||||
@@ -102,7 +102,7 @@ class ModelMapperMiddleware:
|
||||
mapping = None
|
||||
|
||||
# 步骤 1: 解析 GlobalModel(支持映射名)
|
||||
global_model = await ModelCacheService.resolve_global_model_by_name_or_alias(
|
||||
global_model = await ModelCacheService.resolve_global_model_by_name_or_mapping(
|
||||
self.db, source_model
|
||||
)
|
||||
|
||||
|
||||
@@ -77,6 +77,24 @@ class ModelService:
|
||||
|
||||
logger.info(f"创建模型成功: provider={provider.name}, model={model.provider_model_name}, global_model_id={model.global_model_id}")
|
||||
|
||||
# 清除 Redis 缓存(异步执行,不阻塞返回)
|
||||
# 重要:新增模型可能需要清除 resolver 的 NOT_FOUND 负缓存(global_model:resolve:*),
|
||||
# 否则请求链路在 TTL 内可能无法立刻解析到新模型。
|
||||
asyncio.create_task(
|
||||
ModelCacheService.invalidate_model_cache(
|
||||
model_id=model.id,
|
||||
provider_id=model.provider_id,
|
||||
global_model_id=model.global_model_id,
|
||||
provider_model_name=model.provider_model_name,
|
||||
provider_model_mappings=model.provider_model_mappings,
|
||||
)
|
||||
)
|
||||
|
||||
# 清除内存缓存(ModelMapperMiddleware 实例)
|
||||
if model.provider_id and model.global_model_id:
|
||||
cache_service = get_cache_invalidation_service()
|
||||
cache_service.on_model_changed(model.provider_id, model.global_model_id)
|
||||
|
||||
# 清除 /v1/models 列表缓存
|
||||
asyncio.create_task(invalidate_models_list_cache())
|
||||
|
||||
@@ -154,6 +172,7 @@ class ModelService:
|
||||
raise NotFoundException(f"模型 {model_id} 不存在")
|
||||
|
||||
# 保存旧的映射,用于清除缓存
|
||||
old_global_model_id = model.global_model_id
|
||||
old_provider_model_name = model.provider_model_name
|
||||
old_provider_model_mappings = model.provider_model_mappings
|
||||
|
||||
@@ -179,14 +198,17 @@ class ModelService:
|
||||
ModelCacheService.invalidate_model_cache(
|
||||
model_id=model.id,
|
||||
provider_id=model.provider_id,
|
||||
global_model_id=model.global_model_id,
|
||||
global_model_id=old_global_model_id,
|
||||
provider_model_name=old_provider_model_name,
|
||||
provider_model_mappings=old_provider_model_mappings,
|
||||
)
|
||||
)
|
||||
# 再清除新的映射缓存(如果有变化)
|
||||
if (model.provider_model_name != old_provider_model_name or
|
||||
model.provider_model_mappings != old_provider_model_mappings):
|
||||
# 再清除新的映射缓存(如果有变化,包括 global_model_id 变更)
|
||||
if (
|
||||
model.provider_model_name != old_provider_model_name
|
||||
or model.provider_model_mappings != old_provider_model_mappings
|
||||
or model.global_model_id != old_global_model_id
|
||||
):
|
||||
asyncio.create_task(
|
||||
ModelCacheService.invalidate_model_cache(
|
||||
model_id=model.id,
|
||||
@@ -354,6 +376,7 @@ class ModelService:
|
||||
provider_id=provider_id,
|
||||
global_model_id=model_data.global_model_id,
|
||||
provider_model_name=model_data.provider_model_name,
|
||||
provider_model_mappings=model_data.provider_model_mappings,
|
||||
price_per_request=model_data.price_per_request,
|
||||
tiered_pricing=model_data.tiered_pricing,
|
||||
supports_vision=model_data.supports_vision,
|
||||
@@ -373,6 +396,23 @@ class ModelService:
|
||||
db.refresh(model)
|
||||
logger.info(f"批量创建 {len(created_models)} 个模型成功")
|
||||
|
||||
# 清除 Redis 缓存(异步执行,不阻塞返回)
|
||||
# 逐个清除 resolver 的映射缓存,避免 NOT_FOUND 负缓存阻塞新模型生效。
|
||||
for model in created_models:
|
||||
asyncio.create_task(
|
||||
ModelCacheService.invalidate_model_cache(
|
||||
model_id=model.id,
|
||||
provider_id=model.provider_id,
|
||||
global_model_id=model.global_model_id,
|
||||
provider_model_name=model.provider_model_name,
|
||||
provider_model_mappings=model.provider_model_mappings,
|
||||
)
|
||||
)
|
||||
|
||||
# 清除内存缓存(ModelMapperMiddleware 实例)
|
||||
cache_service = get_cache_invalidation_service()
|
||||
cache_service.on_model_changed(provider_id, created_models[0].global_model_id)
|
||||
|
||||
# 清除 /v1/models 列表缓存
|
||||
asyncio.create_task(invalidate_models_list_cache())
|
||||
except IntegrityError as e:
|
||||
|
||||
Reference in New Issue
Block a user