mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
chore: 升级到 Python 3.14 并现代化代码
- 升级 Docker 基础镜像从 Python 3.12 到 3.14 - 更新 pyproject.toml 支持 Python 3.13/3.14 - 移除 Python 3.8/3.9/3.10/3.11 分类器 - 更新 black 和 mypy 配置目标版本 - 将 get_event_loop() 替换为 get_running_loop() 加上 RuntimeError 处理 - 简化 compute_cost_sync 中的 asyncio.run 使用 - Dict/List/Tuple/Set → dict/list/tuple/set (PEP 585) - Optional[T] → T | None (PEP 604) - Union[A, B] → A | B (PEP 604) - 移除废弃的 typing 导入 - 移除不必要的字符串引号注解
This commit is contained in:
42
src/services/cache/affinity_manager.py
vendored
42
src/services/cache/affinity_manager.py
vendored
@@ -23,7 +23,7 @@ import json
|
||||
import os
|
||||
import time
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
from src.config.constants import CacheTTL
|
||||
from src.core.logger import logger
|
||||
@@ -86,18 +86,18 @@ class CacheAffinityManager:
|
||||
"""
|
||||
self.redis = redis_client
|
||||
self.default_ttl = default_ttl
|
||||
self._memory_store: Dict[str, Dict[str, Any]] = {}
|
||||
self._memory_lock: Optional[asyncio.Lock] = None
|
||||
self._memory_store: dict[str, dict[str, Any]] = {}
|
||||
self._memory_lock: asyncio.Lock | None = None
|
||||
|
||||
# L1 缓存(即使使用 Redis 也启用,减少网络往返)
|
||||
self._l1_cache_ttl = int(os.getenv("CACHE_AFFINITY_L1_TTL", str(CacheTTL.L1_LOCAL)))
|
||||
self._l1_cache: Dict[str, Tuple[float, Dict[str, Any]]] = {}
|
||||
self._l1_cache: dict[str, tuple[float, dict[str, Any]]] = {}
|
||||
self._l1_lock = asyncio.Lock()
|
||||
self._l1_max_size = int(os.getenv("CACHE_AFFINITY_L1_MAX_SIZE", "1000")) # 最大缓存条目数
|
||||
self._l1_last_cleanup = time.time()
|
||||
|
||||
# 请求级别锁,避免同一用户+端点同时更新造成抖动
|
||||
self._request_locks: Dict[str, asyncio.Lock] = {}
|
||||
self._request_locks: dict[str, asyncio.Lock] = {}
|
||||
|
||||
# 统计信息
|
||||
self._stats = {
|
||||
@@ -138,7 +138,7 @@ class CacheAffinityManager:
|
||||
"""
|
||||
return f"cache_affinity:{affinity_key}:{api_format}:{model_name}"
|
||||
|
||||
async def _get_l1_entry(self, cache_key: str) -> Optional[Dict[str, Any]]:
|
||||
async def _get_l1_entry(self, cache_key: str) -> dict[str, Any] | None:
|
||||
async with self._l1_lock:
|
||||
record = self._l1_cache.get(cache_key)
|
||||
if not record:
|
||||
@@ -149,7 +149,7 @@ class CacheAffinityManager:
|
||||
return None
|
||||
return dict(payload)
|
||||
|
||||
async def _set_l1_entry(self, cache_key: str, payload: Optional[Dict[str, Any]]):
|
||||
async def _set_l1_entry(self, cache_key: str, payload: dict[str, Any] | None):
|
||||
async with self._l1_lock:
|
||||
if not payload:
|
||||
self._l1_cache.pop(cache_key, None)
|
||||
@@ -205,7 +205,7 @@ class CacheAffinityManager:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
async def _load_affinity_dict(self, cache_key: str) -> Optional[Dict[str, Any]]:
|
||||
async def _load_affinity_dict(self, cache_key: str) -> dict[str, Any] | None:
|
||||
"""读取缓存亲和性字典"""
|
||||
# 先尝试L1缓存
|
||||
l1_value = await self._get_l1_entry(cache_key)
|
||||
@@ -228,7 +228,7 @@ class CacheAffinityManager:
|
||||
return dict(record) if record else None
|
||||
|
||||
async def _save_affinity_dict(
|
||||
self, cache_key: str, ttl: int, affinity_dict: Dict[str, Any]
|
||||
self, cache_key: str, ttl: int, affinity_dict: dict[str, Any]
|
||||
) -> None:
|
||||
"""存储缓存亲和性字典"""
|
||||
if not self._is_memory_backend():
|
||||
@@ -252,7 +252,7 @@ class CacheAffinityManager:
|
||||
|
||||
await self._set_l1_entry(cache_key, None)
|
||||
|
||||
async def _snapshot_memory_items(self) -> Dict[str, Dict[str, Any]]:
|
||||
async def _snapshot_memory_items(self) -> dict[str, dict[str, Any]]:
|
||||
"""复制内存存储内容(仅内存模式使用)"""
|
||||
lock = self._get_memory_lock()
|
||||
async with lock:
|
||||
@@ -260,7 +260,7 @@ class CacheAffinityManager:
|
||||
|
||||
async def get_affinity(
|
||||
self, affinity_key: str, api_format: str, model_name: str
|
||||
) -> Optional[CacheAffinity]:
|
||||
) -> CacheAffinity | None:
|
||||
"""
|
||||
获取指定亲和性标识符对特定API格式和模型的缓存亲和性
|
||||
|
||||
@@ -315,7 +315,7 @@ class CacheAffinityManager:
|
||||
api_format: str,
|
||||
model_name: str,
|
||||
supports_caching: bool = True,
|
||||
ttl: Optional[int] = None,
|
||||
ttl: int | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
设置指定亲和性标识符对特定API格式和模型的缓存亲和性
|
||||
@@ -345,7 +345,7 @@ class CacheAffinityManager:
|
||||
try:
|
||||
async with self._acquire_request_lock(cache_key):
|
||||
existing_dict = await self._load_affinity_dict(cache_key)
|
||||
existing_affinity: Optional[CacheAffinity] = None
|
||||
existing_affinity: CacheAffinity | None = None
|
||||
if existing_dict and current_time <= existing_dict.get("expire_at", 0):
|
||||
existing_affinity = CacheAffinity(
|
||||
provider_id=existing_dict["provider_id"],
|
||||
@@ -408,9 +408,9 @@ class CacheAffinityManager:
|
||||
affinity_key: str,
|
||||
api_format: str,
|
||||
model_name: str,
|
||||
key_id: Optional[str] = None,
|
||||
provider_id: Optional[str] = None,
|
||||
endpoint_id: Optional[str] = None,
|
||||
key_id: str | None = None,
|
||||
provider_id: str | None = None,
|
||||
endpoint_id: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
失效指定亲和性标识符对特定API格式和模型的缓存亲和性
|
||||
@@ -527,7 +527,7 @@ class CacheAffinityManager:
|
||||
logger.exception(f"清除缓存亲和性失败: {e}")
|
||||
return 0
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
def get_stats(self) -> dict[str, Any]:
|
||||
"""获取统计信息"""
|
||||
cache_hit_rate = 0.0
|
||||
total_requests = self._stats["cache_hits"] + self._stats["cache_misses"]
|
||||
@@ -551,7 +551,7 @@ class CacheAffinityManager:
|
||||
},
|
||||
}
|
||||
|
||||
async def list_affinities(self) -> List[Dict[str, Any]]:
|
||||
async def list_affinities(self) -> list[dict[str, Any]]:
|
||||
"""获取所有缓存亲和性列表
|
||||
|
||||
返回的每条记录包含:
|
||||
@@ -560,7 +560,7 @@ class CacheAffinityManager:
|
||||
- api_format, model_name: API 格式和模型名称
|
||||
- created_at, expire_at, request_count: 缓存元数据
|
||||
"""
|
||||
results: List[Dict[str, Any]] = []
|
||||
results: list[dict[str, Any]] = []
|
||||
|
||||
try:
|
||||
pattern = "cache_affinity:*"
|
||||
@@ -605,7 +605,7 @@ class CacheAffinityManager:
|
||||
break
|
||||
else:
|
||||
snapshot = await self._snapshot_memory_items()
|
||||
expired_keys: List[str] = []
|
||||
expired_keys: list[str] = []
|
||||
current_time = time.time()
|
||||
|
||||
for cache_key, affinity in snapshot.items():
|
||||
@@ -644,7 +644,7 @@ class CacheAffinityManager:
|
||||
|
||||
|
||||
# 全局单例
|
||||
_affinity_manager: Optional[CacheAffinityManager] = None
|
||||
_affinity_manager: CacheAffinityManager | None = None
|
||||
|
||||
|
||||
async def get_affinity_manager(redis_client=None) -> CacheAffinityManager:
|
||||
|
||||
176
src/services/cache/aware_scheduler.py
vendored
176
src/services/cache/aware_scheduler.py
vendored
@@ -28,14 +28,13 @@
|
||||
- 失效缓存亲和性,避免重复选择故障资源
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
@@ -65,7 +64,6 @@ from src.services.rate_limit.adaptive_reservation import (
|
||||
get_adaptive_reservation_manager,
|
||||
)
|
||||
from src.services.rate_limit.concurrency_manager import get_concurrency_manager
|
||||
from src.services.system.config import SystemConfigService
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -77,8 +75,8 @@ class ProviderCandidate:
|
||||
key: ProviderAPIKey
|
||||
is_cached: bool = False
|
||||
is_skipped: bool = False # 是否被跳过
|
||||
skip_reason: Optional[str] = None # 跳过原因
|
||||
mapping_matched_model: Optional[str] = None # 通过映射匹配到的模型名(用于实际请求)
|
||||
skip_reason: str | None = None # 跳过原因
|
||||
mapping_matched_model: str | None = None # 通过映射匹配到的模型名(用于实际请求)
|
||||
needs_conversion: bool = False # 是否需要格式转换
|
||||
provider_api_format: str = "" # Provider 端点实际格式(用于健康度/熔断 bucket)
|
||||
|
||||
@@ -86,7 +84,7 @@ class ProviderCandidate:
|
||||
@dataclass
|
||||
class ConcurrencySnapshot:
|
||||
key_current: int
|
||||
key_limit: Optional[int]
|
||||
key_limit: int | None
|
||||
is_cached_user: bool = False
|
||||
# 动态预留信息
|
||||
reservation_ratio: float = 0.0
|
||||
@@ -134,8 +132,8 @@ class CacheAwareScheduler:
|
||||
def __init__(
|
||||
self,
|
||||
redis_client=None,
|
||||
priority_mode: Optional[str] = None,
|
||||
scheduling_mode: Optional[str] = None,
|
||||
priority_mode: str | None = None,
|
||||
scheduling_mode: str | None = None,
|
||||
):
|
||||
"""
|
||||
初始化调度器
|
||||
@@ -160,7 +158,7 @@ class CacheAwareScheduler:
|
||||
)
|
||||
|
||||
# 初始化子组件(将在第一次使用时异步初始化)
|
||||
self._affinity_manager: Optional[CacheAffinityManager] = None
|
||||
self._affinity_manager: CacheAffinityManager | None = None
|
||||
self._concurrency_manager = None
|
||||
# 动态预留管理器(同步初始化)
|
||||
self._reservation_manager: AdaptiveReservationManager = get_adaptive_reservation_manager()
|
||||
@@ -194,13 +192,13 @@ class CacheAwareScheduler:
|
||||
self,
|
||||
db: Session,
|
||||
affinity_key: str,
|
||||
api_format: Union[str, APIFormat],
|
||||
api_format: str | APIFormat,
|
||||
model_name: str,
|
||||
excluded_endpoints: Optional[List[str]] = None,
|
||||
excluded_keys: Optional[List[str]] = None,
|
||||
excluded_endpoints: list[str] | None = None,
|
||||
excluded_keys: list[str] | None = None,
|
||||
provider_batch_size: int = 20,
|
||||
max_candidates_per_batch: Optional[int] = None,
|
||||
) -> Tuple[Provider, ProviderEndpoint, ProviderAPIKey]:
|
||||
max_candidates_per_batch: int | None = None,
|
||||
) -> tuple[Provider, ProviderEndpoint, ProviderAPIKey]:
|
||||
"""
|
||||
缓存感知选择 - 核心方法
|
||||
|
||||
@@ -318,7 +316,7 @@ class CacheAwareScheduler:
|
||||
|
||||
raise ProviderNotAvailableException("服务暂时繁忙,请稍后重试")
|
||||
|
||||
def _get_effective_rpm_limit(self, key: ProviderAPIKey) -> Optional[int]:
|
||||
def _get_effective_rpm_limit(self, key: ProviderAPIKey) -> int | None:
|
||||
"""
|
||||
获取有效的 RPM 限制
|
||||
|
||||
@@ -348,7 +346,7 @@ class CacheAwareScheduler:
|
||||
self,
|
||||
key: ProviderAPIKey,
|
||||
is_cached_user: bool = False,
|
||||
) -> Tuple[bool, ConcurrencySnapshot]:
|
||||
) -> tuple[bool, ConcurrencySnapshot]:
|
||||
"""
|
||||
检查 RPM 限制是否可用(使用动态预留机制)
|
||||
|
||||
@@ -448,7 +446,7 @@ class CacheAwareScheduler:
|
||||
)
|
||||
can_use = False
|
||||
|
||||
key_limit_for_snapshot: Optional[int]
|
||||
key_limit_for_snapshot: int | None
|
||||
if is_cached_user:
|
||||
key_limit_for_snapshot = effective_key_limit
|
||||
elif effective_key_limit is not None:
|
||||
@@ -471,8 +469,8 @@ class CacheAwareScheduler:
|
||||
|
||||
def _get_effective_restrictions(
|
||||
self,
|
||||
user_api_key: Optional[ApiKey],
|
||||
) -> Dict[str, Any]:
|
||||
user_api_key: ApiKey | None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
获取有效的访问限制(合并 ApiKey 和 User 的限制)
|
||||
|
||||
@@ -549,16 +547,16 @@ class CacheAwareScheduler:
|
||||
async def list_all_candidates(
|
||||
self,
|
||||
db: Session,
|
||||
api_format: Union[str, APIFormat],
|
||||
api_format: str | APIFormat,
|
||||
model_name: str,
|
||||
affinity_key: Optional[str] = None,
|
||||
user_api_key: Optional[ApiKey] = None,
|
||||
affinity_key: str | None = None,
|
||||
user_api_key: ApiKey | None = None,
|
||||
provider_offset: int = 0,
|
||||
provider_limit: Optional[int] = None,
|
||||
max_candidates: Optional[int] = None,
|
||||
provider_limit: int | None = None,
|
||||
max_candidates: int | None = None,
|
||||
is_stream: bool = False,
|
||||
capability_requirements: Optional[Dict[str, bool]] = None,
|
||||
) -> Tuple[List[ProviderCandidate], str]:
|
||||
capability_requirements: dict[str, bool] | None = None,
|
||||
) -> tuple[list[ProviderCandidate], str]:
|
||||
"""
|
||||
预先获取所有可用的 Provider/Endpoint/Key 组合
|
||||
|
||||
@@ -599,7 +597,7 @@ class CacheAwareScheduler:
|
||||
global_model_id: str = str(global_model.id)
|
||||
|
||||
# 提取模型映射(用于 Provider Key 的 allowed_models 匹配)
|
||||
model_mappings: List[str] = (global_model.config or {}).get("model_mappings", [])
|
||||
model_mappings: list[str] = (global_model.config or {}).get("model_mappings", [])
|
||||
if model_mappings:
|
||||
logger.debug(
|
||||
f"[Scheduler] GlobalModel={global_model.name} 配置了映射规则: {model_mappings}"
|
||||
@@ -712,8 +710,8 @@ class CacheAwareScheduler:
|
||||
self,
|
||||
db: Session,
|
||||
provider_offset: int = 0,
|
||||
provider_limit: Optional[int] = None,
|
||||
) -> List[Provider]:
|
||||
provider_limit: int | None = None,
|
||||
) -> list[Provider]:
|
||||
"""
|
||||
查询活跃的 Providers(带预加载)
|
||||
|
||||
@@ -751,10 +749,10 @@ class CacheAwareScheduler:
|
||||
db: Session,
|
||||
provider: Provider,
|
||||
model_name: str,
|
||||
api_format: Optional[str] = None,
|
||||
api_format: str | None = None,
|
||||
is_stream: bool = False,
|
||||
capability_requirements: Optional[Dict[str, bool]] = None,
|
||||
) -> Tuple[bool, Optional[str], Optional[List[str]], Optional[set[str]]]:
|
||||
capability_requirements: dict[str, bool] | None = None,
|
||||
) -> tuple[bool, str | None, list[str] | None, set[str] | None]:
|
||||
"""
|
||||
检查 Provider 是否支持指定模型(可选检查流式支持和能力需求)
|
||||
|
||||
@@ -807,12 +805,12 @@ class CacheAwareScheduler:
|
||||
self,
|
||||
db: Session,
|
||||
provider: Provider,
|
||||
global_model: "GlobalModel",
|
||||
global_model: GlobalModel,
|
||||
model_name: str,
|
||||
api_format: Optional[str] = None,
|
||||
api_format: str | None = None,
|
||||
is_stream: bool = False,
|
||||
capability_requirements: Optional[Dict[str, bool]] = None,
|
||||
) -> Tuple[bool, Optional[str], Optional[List[str]], Optional[set[str]]]:
|
||||
capability_requirements: dict[str, bool] | None = None,
|
||||
) -> tuple[bool, str | None, list[str] | None, set[str] | None]:
|
||||
"""
|
||||
检查 Provider 是否支持指定的 GlobalModel
|
||||
|
||||
@@ -841,7 +839,7 @@ class CacheAwareScheduler:
|
||||
pass
|
||||
|
||||
# 获取模型支持的能力列表
|
||||
model_supported_capabilities: List[str] = list(global_model.supported_capabilities or [])
|
||||
model_supported_capabilities: list[str] = list(global_model.supported_capabilities or [])
|
||||
|
||||
# 查询该 Provider 是否有实现这个 GlobalModel
|
||||
for model in provider.models:
|
||||
@@ -892,12 +890,12 @@ class CacheAwareScheduler:
|
||||
def _check_key_availability(
|
||||
self,
|
||||
key: ProviderAPIKey,
|
||||
api_format: Optional[str],
|
||||
api_format: str | None,
|
||||
model_name: str,
|
||||
capability_requirements: Optional[Dict[str, bool]] = None,
|
||||
model_mappings: Optional[List[str]] = None,
|
||||
candidate_models: Optional[set[str]] = None,
|
||||
) -> Tuple[bool, Optional[str], Optional[str]]:
|
||||
capability_requirements: dict[str, bool] | None = None,
|
||||
model_mappings: list[str] | None = None,
|
||||
candidate_models: set[str] | None = None,
|
||||
) -> tuple[bool, str | None, str | None]:
|
||||
"""
|
||||
检查 API Key 的可用性
|
||||
|
||||
@@ -971,7 +969,7 @@ class CacheAwareScheduler:
|
||||
# 因为 check_capability_match 会检查 Key 的 EXCLUSIVE 能力是否被浪费
|
||||
from src.core.key_capabilities import check_capability_match
|
||||
|
||||
key_caps: Dict[str, bool] = dict(key.capabilities or {})
|
||||
key_caps: dict[str, bool] = dict(key.capabilities or {})
|
||||
is_match, skip_reason = check_capability_match(key_caps, capability_requirements)
|
||||
if not is_match:
|
||||
return False, skip_reason, None
|
||||
@@ -981,16 +979,16 @@ class CacheAwareScheduler:
|
||||
async def _build_candidates(
|
||||
self,
|
||||
db: Session,
|
||||
providers: List[Provider],
|
||||
providers: list[Provider],
|
||||
client_format: APIFormat,
|
||||
model_name: str,
|
||||
affinity_key: Optional[str],
|
||||
model_mappings: Optional[List[str]] = None,
|
||||
max_candidates: Optional[int] = None,
|
||||
affinity_key: str | None,
|
||||
model_mappings: list[str] | None = None,
|
||||
max_candidates: int | None = None,
|
||||
is_stream: bool = False,
|
||||
capability_requirements: Optional[Dict[str, bool]] = None,
|
||||
capability_requirements: dict[str, bool] | None = None,
|
||||
global_conversion_enabled: bool = False,
|
||||
) -> List[ProviderCandidate]:
|
||||
) -> list[ProviderCandidate]:
|
||||
"""
|
||||
构建候选列表
|
||||
|
||||
@@ -1013,18 +1011,18 @@ class CacheAwareScheduler:
|
||||
"""
|
||||
from src.core.api_format.conversion.compatibility import is_format_compatible
|
||||
|
||||
candidates: List[ProviderCandidate] = []
|
||||
candidates: list[ProviderCandidate] = []
|
||||
client_format_str = client_format.value
|
||||
|
||||
for provider in providers:
|
||||
# 按端点格式分别判断兼容性与模型/Key 可用性:
|
||||
# - 同格式端点优先(needs_conversion=False)
|
||||
# - 跨格式端点次之(needs_conversion=True)
|
||||
model_support_cache: Dict[
|
||||
str, Tuple[bool, Optional[str], Optional[List[str]], Optional[Set[str]]]
|
||||
model_support_cache: dict[
|
||||
str, tuple[bool, str | None, list[str] | None, set[str] | None]
|
||||
] = {}
|
||||
exact_candidates: List[ProviderCandidate] = []
|
||||
convertible_candidates: List[ProviderCandidate] = []
|
||||
exact_candidates: list[ProviderCandidate] = []
|
||||
convertible_candidates: list[ProviderCandidate] = []
|
||||
|
||||
for endpoint in provider.endpoints:
|
||||
if not endpoint.is_active:
|
||||
@@ -1130,11 +1128,11 @@ class CacheAwareScheduler:
|
||||
|
||||
async def _apply_cache_affinity(
|
||||
self,
|
||||
candidates: List[ProviderCandidate],
|
||||
candidates: list[ProviderCandidate],
|
||||
affinity_key: str,
|
||||
api_format: APIFormat,
|
||||
global_model_id: str,
|
||||
) -> List[ProviderCandidate]:
|
||||
) -> list[ProviderCandidate]:
|
||||
"""
|
||||
应用缓存亲和性排序
|
||||
|
||||
@@ -1177,7 +1175,7 @@ class CacheAwareScheduler:
|
||||
return True # 需要降级
|
||||
|
||||
# 按是否匹配缓存亲和性分类候选,同时记录是否降级
|
||||
matched_candidate: Optional[ProviderCandidate] = None
|
||||
matched_candidate: ProviderCandidate | None = None
|
||||
matched = False
|
||||
|
||||
for candidate in candidates:
|
||||
@@ -1231,8 +1229,8 @@ class CacheAwareScheduler:
|
||||
matched_should_demote = should_demote(matched_candidate)
|
||||
|
||||
# 分组:非降级类 和 降级类
|
||||
keep_priority_candidates: List[ProviderCandidate] = []
|
||||
demote_candidates: List[ProviderCandidate] = []
|
||||
keep_priority_candidates: list[ProviderCandidate] = []
|
||||
demote_candidates: list[ProviderCandidate] = []
|
||||
|
||||
for c in candidates:
|
||||
if c is matched_candidate:
|
||||
@@ -1258,7 +1256,7 @@ class CacheAwareScheduler:
|
||||
logger.warning(f"检查缓存亲和性失败: {e},继续使用默认排序")
|
||||
return candidates
|
||||
|
||||
def _normalize_priority_mode(self, mode: Optional[str]) -> str:
|
||||
def _normalize_priority_mode(self, mode: str | None) -> str:
|
||||
normalized = (mode or "").strip().lower()
|
||||
if normalized not in self.ALLOWED_PRIORITY_MODES:
|
||||
if normalized:
|
||||
@@ -1266,7 +1264,7 @@ class CacheAwareScheduler:
|
||||
return self.PRIORITY_MODE_PROVIDER
|
||||
return normalized
|
||||
|
||||
def set_priority_mode(self, mode: Optional[str]) -> None:
|
||||
def set_priority_mode(self, mode: str | None) -> None:
|
||||
"""运行时更新候选排序策略"""
|
||||
normalized = self._normalize_priority_mode(mode)
|
||||
if normalized == self.priority_mode:
|
||||
@@ -1274,7 +1272,7 @@ class CacheAwareScheduler:
|
||||
self.priority_mode = normalized
|
||||
logger.debug(f"[CacheAwareScheduler] 切换优先级模式为: {self.priority_mode}")
|
||||
|
||||
def _normalize_scheduling_mode(self, mode: Optional[str]) -> str:
|
||||
def _normalize_scheduling_mode(self, mode: str | None) -> str:
|
||||
normalized = (mode or "").strip().lower()
|
||||
if normalized not in self.ALLOWED_SCHEDULING_MODES:
|
||||
if normalized:
|
||||
@@ -1284,7 +1282,7 @@ class CacheAwareScheduler:
|
||||
return self.SCHEDULING_MODE_CACHE_AFFINITY
|
||||
return normalized
|
||||
|
||||
def set_scheduling_mode(self, mode: Optional[str]) -> None:
|
||||
def set_scheduling_mode(self, mode: str | None) -> None:
|
||||
"""运行时更新调度模式"""
|
||||
normalized = self._normalize_scheduling_mode(mode)
|
||||
if normalized == self.scheduling_mode:
|
||||
@@ -1294,10 +1292,10 @@ class CacheAwareScheduler:
|
||||
|
||||
def _apply_priority_mode_sort(
|
||||
self,
|
||||
candidates: List[ProviderCandidate],
|
||||
affinity_key: Optional[str] = None,
|
||||
api_format: Optional[str] = None,
|
||||
) -> List[ProviderCandidate]:
|
||||
candidates: list[ProviderCandidate],
|
||||
affinity_key: str | None = None,
|
||||
api_format: str | None = None,
|
||||
) -> list[ProviderCandidate]:
|
||||
"""
|
||||
根据优先级模式对候选列表排序(数字越小越优先)
|
||||
|
||||
@@ -1328,8 +1326,8 @@ class CacheAwareScheduler:
|
||||
# 全局未开启:按是否需要降级分组
|
||||
# - 不需要降级:exact 候选 或 provider.keep_priority_on_conversion=True 的 convertible 候选
|
||||
# - 需要降级:convertible 且 provider.keep_priority_on_conversion=False
|
||||
keep_priority_candidates: List[ProviderCandidate] = []
|
||||
demote_candidates: List[ProviderCandidate] = []
|
||||
keep_priority_candidates: list[ProviderCandidate] = []
|
||||
demote_candidates: list[ProviderCandidate] = []
|
||||
|
||||
for c in candidates:
|
||||
if not c.needs_conversion:
|
||||
@@ -1357,10 +1355,10 @@ class CacheAwareScheduler:
|
||||
|
||||
def _sort_by_global_priority_with_hash(
|
||||
self,
|
||||
candidates: List[ProviderCandidate],
|
||||
affinity_key: Optional[str] = None,
|
||||
api_format: Optional[str] = None,
|
||||
) -> List[ProviderCandidate]:
|
||||
candidates: list[ProviderCandidate],
|
||||
affinity_key: str | None = None,
|
||||
api_format: str | None = None,
|
||||
) -> list[ProviderCandidate]:
|
||||
"""
|
||||
按 global_priority_by_format 分组排序,同优先级内通过哈希分散实现负载均衡
|
||||
|
||||
@@ -1382,7 +1380,7 @@ class CacheAwareScheduler:
|
||||
return 999999 # NULL 排在后面
|
||||
|
||||
# 按优先级分组
|
||||
priority_groups: Dict[int, List[ProviderCandidate]] = defaultdict(list)
|
||||
priority_groups: dict[int, list[ProviderCandidate]] = defaultdict(list)
|
||||
for candidate in candidates:
|
||||
priority = get_priority(candidate)
|
||||
priority_groups[priority].append(candidate)
|
||||
@@ -1417,8 +1415,8 @@ class CacheAwareScheduler:
|
||||
return result
|
||||
|
||||
def _apply_load_balance(
|
||||
self, candidates: List[ProviderCandidate], api_format: Optional[str] = None
|
||||
) -> List[ProviderCandidate]:
|
||||
self, candidates: list[ProviderCandidate], api_format: str | None = None
|
||||
) -> list[ProviderCandidate]:
|
||||
"""
|
||||
负载均衡模式:同优先级内随机轮换
|
||||
|
||||
@@ -1432,7 +1430,7 @@ class CacheAwareScheduler:
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
priority_groups: Dict[tuple, List[ProviderCandidate]] = defaultdict(list)
|
||||
priority_groups: dict[tuple, list[ProviderCandidate]] = defaultdict(list)
|
||||
|
||||
# 根据优先级模式选择分组方式
|
||||
if self.priority_mode == self.PRIORITY_MODE_GLOBAL_KEY:
|
||||
@@ -1453,7 +1451,7 @@ class CacheAwareScheduler:
|
||||
)
|
||||
priority_groups[key].append(candidate)
|
||||
|
||||
result: List[ProviderCandidate] = []
|
||||
result: list[ProviderCandidate] = []
|
||||
for priority in sorted(priority_groups.keys()):
|
||||
group = priority_groups[priority]
|
||||
if len(group) > 1:
|
||||
@@ -1468,10 +1466,10 @@ class CacheAwareScheduler:
|
||||
|
||||
def _shuffle_keys_by_internal_priority(
|
||||
self,
|
||||
keys: List[ProviderAPIKey],
|
||||
affinity_key: Optional[str] = None,
|
||||
keys: list[ProviderAPIKey],
|
||||
affinity_key: str | None = None,
|
||||
use_random: bool = False,
|
||||
) -> List[ProviderAPIKey]:
|
||||
) -> list[ProviderAPIKey]:
|
||||
"""
|
||||
对 API Key 按 internal_priority 分组,同优先级内部基于 affinity_key 进行确定性打乱
|
||||
|
||||
@@ -1495,7 +1493,7 @@ class CacheAwareScheduler:
|
||||
# 按 internal_priority 分组
|
||||
from collections import defaultdict
|
||||
|
||||
priority_groups: Dict[int, List[ProviderAPIKey]] = defaultdict(list)
|
||||
priority_groups: dict[int, list[ProviderAPIKey]] = defaultdict(list)
|
||||
|
||||
for key in keys:
|
||||
priority = key.internal_priority if key.internal_priority is not None else 999999
|
||||
@@ -1538,9 +1536,9 @@ class CacheAwareScheduler:
|
||||
affinity_key: str,
|
||||
api_format: str,
|
||||
global_model_id: str,
|
||||
endpoint_id: Optional[str] = None,
|
||||
key_id: Optional[str] = None,
|
||||
provider_id: Optional[str] = None,
|
||||
endpoint_id: str | None = None,
|
||||
key_id: str | None = None,
|
||||
provider_id: str | None = None,
|
||||
):
|
||||
"""
|
||||
失效指定亲和性标识符对特定API格式和模型的缓存亲和性
|
||||
@@ -1571,7 +1569,7 @@ class CacheAwareScheduler:
|
||||
key_id: str,
|
||||
api_format: str,
|
||||
global_model_id: str,
|
||||
ttl: Optional[int] = None,
|
||||
ttl: int | None = None,
|
||||
):
|
||||
"""
|
||||
记录缓存亲和性(供编排器调用)
|
||||
@@ -1641,13 +1639,13 @@ class CacheAwareScheduler:
|
||||
|
||||
|
||||
# 全局单例
|
||||
_scheduler: Optional[CacheAwareScheduler] = None
|
||||
_scheduler: CacheAwareScheduler | None = None
|
||||
|
||||
|
||||
async def get_cache_aware_scheduler(
|
||||
redis_client=None,
|
||||
priority_mode: Optional[str] = None,
|
||||
scheduling_mode: Optional[str] = None,
|
||||
priority_mode: str | None = None,
|
||||
scheduling_mode: str | None = None,
|
||||
) -> CacheAwareScheduler:
|
||||
"""
|
||||
获取全局CacheAwareScheduler实例
|
||||
|
||||
22
src/services/cache/backend.py
vendored
22
src/services/cache/backend.py
vendored
@@ -15,7 +15,7 @@ import json
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
from src.core.logger import logger
|
||||
@@ -28,7 +28,7 @@ class BaseCacheBackend(ABC):
|
||||
"""缓存后端抽象基类"""
|
||||
|
||||
@abstractmethod
|
||||
async def get(self, key: str) -> Optional[Any]:
|
||||
async def get(self, key: str) -> Any | None:
|
||||
"""获取缓存值"""
|
||||
pass
|
||||
|
||||
@@ -43,7 +43,7 @@ class BaseCacheBackend(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def clear(self, pattern: Optional[str] = None) -> None:
|
||||
async def clear(self, pattern: str | None = None) -> None:
|
||||
"""清空缓存(支持模式匹配)"""
|
||||
pass
|
||||
|
||||
@@ -65,12 +65,12 @@ class LocalCache(BaseCacheBackend):
|
||||
default_ttl: 默认过期时间(秒)
|
||||
"""
|
||||
self._cache: OrderedDict = OrderedDict()
|
||||
self._expiry: Dict[str, float] = {}
|
||||
self._expiry: dict[str, float] = {}
|
||||
self._max_size = max_size
|
||||
self._default_ttl = default_ttl
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
async def get(self, key: str) -> Optional[Any]:
|
||||
async def get(self, key: str) -> Any | None:
|
||||
"""获取缓存值(线程安全)"""
|
||||
async with self._lock:
|
||||
if key not in self._cache:
|
||||
@@ -115,7 +115,7 @@ class LocalCache(BaseCacheBackend):
|
||||
if key in self._expiry:
|
||||
del self._expiry[key]
|
||||
|
||||
async def clear(self, pattern: Optional[str] = None) -> None:
|
||||
async def clear(self, pattern: str | None = None) -> None:
|
||||
"""清空缓存(线程安全)"""
|
||||
async with self._lock:
|
||||
if pattern is None:
|
||||
@@ -145,7 +145,7 @@ class LocalCache(BaseCacheBackend):
|
||||
|
||||
return True
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
def get_stats(self) -> dict[str, Any]:
|
||||
"""获取缓存统计信息"""
|
||||
return {
|
||||
"backend": "local",
|
||||
@@ -177,7 +177,7 @@ class RedisCache(BaseCacheBackend):
|
||||
"""构造完整的 Redis 键"""
|
||||
return f"{self._key_prefix}:{key}"
|
||||
|
||||
async def get(self, key: str) -> Optional[Any]:
|
||||
async def get(self, key: str) -> Any | None:
|
||||
"""获取缓存值"""
|
||||
try:
|
||||
redis_key = self._make_key(key)
|
||||
@@ -223,7 +223,7 @@ class RedisCache(BaseCacheBackend):
|
||||
except Exception as e:
|
||||
logger.error(f"[RedisCache] 删除缓存失败: {key}, 错误: {e}")
|
||||
|
||||
async def clear(self, pattern: Optional[str] = None) -> None:
|
||||
async def clear(self, pattern: str | None = None) -> None:
|
||||
"""清空缓存"""
|
||||
try:
|
||||
if pattern is None:
|
||||
@@ -264,7 +264,7 @@ class RedisCache(BaseCacheBackend):
|
||||
except Exception as e:
|
||||
logger.error(f"[RedisCache] 发布缓存失效失败: {channel}, {key}, 错误: {e}")
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
def get_stats(self) -> dict[str, Any]:
|
||||
"""获取缓存统计信息"""
|
||||
return {
|
||||
"backend": "redis",
|
||||
@@ -274,7 +274,7 @@ class RedisCache(BaseCacheBackend):
|
||||
|
||||
|
||||
# 缓存后端工厂
|
||||
_cache_backends: Dict[str, BaseCacheBackend] = {}
|
||||
_cache_backends: dict[str, BaseCacheBackend] = {}
|
||||
|
||||
|
||||
async def get_cache_backend(
|
||||
|
||||
5
src/services/cache/invalidation.py
vendored
5
src/services/cache/invalidation.py
vendored
@@ -4,7 +4,6 @@
|
||||
统一管理各种缓存的失效逻辑
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from src.core.logger import logger
|
||||
|
||||
@@ -21,7 +20,7 @@ class CacheInvalidationService:
|
||||
self._model_mappers.append(model_mapper)
|
||||
|
||||
async def on_global_model_changed(
|
||||
self, model_name: str, global_model_id: Optional[str] = None
|
||||
self, model_name: str, global_model_id: str | None = None
|
||||
) -> None:
|
||||
"""
|
||||
GlobalModel 变更时的缓存失效
|
||||
@@ -96,7 +95,7 @@ class CacheInvalidationService:
|
||||
|
||||
|
||||
# 全局单例
|
||||
_cache_invalidation_service: Optional[CacheInvalidationService] = None
|
||||
_cache_invalidation_service: CacheInvalidationService | None = None
|
||||
|
||||
|
||||
def get_cache_invalidation_service() -> CacheInvalidationService:
|
||||
|
||||
27
src/services/cache/model_cache.py
vendored
27
src/services/cache/model_cache.py
vendored
@@ -19,7 +19,6 @@ Model 映射缓存服务 - 减少模型查询
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -45,7 +44,7 @@ class ModelCacheService:
|
||||
CACHE_TTL = CacheTTL.MODEL
|
||||
|
||||
@staticmethod
|
||||
async def get_model_by_id(db: Session, model_id: str) -> Optional[Model]:
|
||||
async def get_model_by_id(db: Session, model_id: str) -> Model | None:
|
||||
"""
|
||||
获取 Model(带缓存)
|
||||
|
||||
@@ -76,7 +75,7 @@ class ModelCacheService:
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
async def get_global_model_by_id(db: Session, global_model_id: str) -> Optional[GlobalModel]:
|
||||
async def get_global_model_by_id(db: Session, global_model_id: str) -> GlobalModel | None:
|
||||
"""
|
||||
获取 GlobalModel(带缓存)
|
||||
|
||||
@@ -111,7 +110,7 @@ class ModelCacheService:
|
||||
@staticmethod
|
||||
async def get_model_by_provider_and_global_model(
|
||||
db: Session, provider_id: str, global_model_id: str
|
||||
) -> Optional[Model]:
|
||||
) -> Model | None:
|
||||
"""
|
||||
通过 Provider ID 和 GlobalModel ID 获取 Model(带缓存)
|
||||
|
||||
@@ -160,7 +159,7 @@ class ModelCacheService:
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
async def get_global_model_by_name(db: Session, name: str) -> Optional[GlobalModel]:
|
||||
async def get_global_model_by_name(db: Session, name: str) -> GlobalModel | None:
|
||||
"""
|
||||
通过名称获取 GlobalModel(带缓存)
|
||||
|
||||
@@ -195,10 +194,10 @@ class ModelCacheService:
|
||||
@staticmethod
|
||||
async def invalidate_model_cache(
|
||||
model_id: str,
|
||||
provider_id: Optional[str] = None,
|
||||
global_model_id: Optional[str] = None,
|
||||
provider_model_name: Optional[str] = None,
|
||||
provider_model_mappings: Optional[list] = None,
|
||||
provider_id: str | None = None,
|
||||
global_model_id: str | None = None,
|
||||
provider_model_name: str | None = None,
|
||||
provider_model_mappings: list | None = None,
|
||||
) -> None:
|
||||
"""清除 Model 缓存
|
||||
|
||||
@@ -240,7 +239,7 @@ class ModelCacheService:
|
||||
logger.debug(f"Model resolve 缓存已清除: {resolve_keys_to_clear}")
|
||||
|
||||
@staticmethod
|
||||
async def invalidate_global_model_cache(global_model_id: str, name: Optional[str] = None) -> None:
|
||||
async def invalidate_global_model_cache(global_model_id: str, name: str | None = None) -> None:
|
||||
"""清除 GlobalModel 缓存"""
|
||||
await CacheService.delete(f"global_model:id:{global_model_id}")
|
||||
if name:
|
||||
@@ -270,7 +269,7 @@ class ModelCacheService:
|
||||
@staticmethod
|
||||
async def resolve_global_model_by_name_or_mapping(
|
||||
db: Session, model_name: str
|
||||
) -> Optional[GlobalModel]:
|
||||
) -> GlobalModel | None:
|
||||
"""
|
||||
通过名称解析 GlobalModel(带缓存)
|
||||
|
||||
@@ -355,7 +354,7 @@ class ModelCacheService:
|
||||
)
|
||||
|
||||
# 收集匹配的 GlobalModel(只通过 provider_model_name 匹配)
|
||||
matched_global_models: List[GlobalModel] = []
|
||||
matched_global_models: list[GlobalModel] = []
|
||||
seen_global_model_ids: set[str] = set()
|
||||
for model, gm in models_with_global:
|
||||
if gm.id not in seen_global_model_ids:
|
||||
@@ -405,7 +404,7 @@ class ModelCacheService:
|
||||
.all()
|
||||
)
|
||||
|
||||
mapping_matched_global_models: List[GlobalModel] = []
|
||||
mapping_matched_global_models: list[GlobalModel] = []
|
||||
mapping_seen_ids: set[str] = set()
|
||||
for model, gm in models_with_mappings:
|
||||
raw_mappings = model.provider_model_mappings
|
||||
@@ -469,7 +468,7 @@ class ModelCacheService:
|
||||
.all()
|
||||
)
|
||||
|
||||
mapping_matches: List[GlobalModel] = []
|
||||
mapping_matches: list[GlobalModel] = []
|
||||
for gm in mapping_rows:
|
||||
config = gm.config or {}
|
||||
mappings = config.get("model_mappings")
|
||||
|
||||
19
src/services/cache/provider_cache.py
vendored
19
src/services/cache/provider_cache.py
vendored
@@ -5,7 +5,6 @@ Provider 缓存服务 - 减少 Provider 和 ProviderAPIKey 查询
|
||||
这些数据在 UsageService.record_usage() 中被频繁查询但变化不频繁。
|
||||
"""
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -27,8 +26,8 @@ class ProviderCacheService:
|
||||
|
||||
@staticmethod
|
||||
def compute_rate_multiplier(
|
||||
rate_multipliers: Optional[dict],
|
||||
api_format: Optional[str] = None,
|
||||
rate_multipliers: dict | None,
|
||||
api_format: str | None = None,
|
||||
) -> float:
|
||||
"""
|
||||
计算 rate_multiplier 的纯函数(无数据库/缓存依赖)
|
||||
@@ -50,8 +49,8 @@ class ProviderCacheService:
|
||||
|
||||
@staticmethod
|
||||
async def get_provider_api_key_rate_multiplier(
|
||||
db: Session, provider_api_key_id: str, api_format: Optional[str] = None
|
||||
) -> Optional[float]:
|
||||
db: Session, provider_api_key_id: str, api_format: str | None = None
|
||||
) -> float | None:
|
||||
"""
|
||||
获取 ProviderAPIKey 的 rate_multiplier(带缓存)
|
||||
|
||||
@@ -106,7 +105,7 @@ class ProviderCacheService:
|
||||
@staticmethod
|
||||
async def get_provider_billing_type(
|
||||
db: Session, provider_id: str
|
||||
) -> Optional[ProviderBillingType]:
|
||||
) -> ProviderBillingType | None:
|
||||
"""
|
||||
获取 Provider 的 billing_type(带缓存)
|
||||
|
||||
@@ -154,10 +153,10 @@ class ProviderCacheService:
|
||||
@staticmethod
|
||||
async def get_rate_multiplier_and_free_tier(
|
||||
db: Session,
|
||||
provider_api_key_id: Optional[str],
|
||||
provider_id: Optional[str],
|
||||
api_format: Optional[str] = None,
|
||||
) -> Tuple[float, bool]:
|
||||
provider_api_key_id: str | None,
|
||||
provider_id: str | None,
|
||||
api_format: str | None = None,
|
||||
) -> tuple[float, bool]:
|
||||
"""
|
||||
获取费率倍数和是否免费套餐(带缓存)
|
||||
|
||||
|
||||
13
src/services/cache/sync.py
vendored
13
src/services/cache/sync.py
vendored
@@ -11,7 +11,8 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Callable, Dict, Optional
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
from src.core.logger import logger
|
||||
@@ -40,9 +41,9 @@ class CacheSyncService:
|
||||
redis_client: Redis 客户端实例
|
||||
"""
|
||||
self._redis = redis_client
|
||||
self._pubsub: Optional[aioredis.client.PubSub] = None
|
||||
self._listener_task: Optional[asyncio.Task] = None
|
||||
self._handlers: Dict[str, Callable] = {}
|
||||
self._pubsub: aioredis.client.PubSub | None = None
|
||||
self._listener_task: asyncio.Task | None = None
|
||||
self._handlers: dict[str, Callable] = {}
|
||||
self._running = False
|
||||
|
||||
async def start(self):
|
||||
@@ -160,10 +161,10 @@ class CacheSyncService:
|
||||
|
||||
|
||||
# 全局单例
|
||||
_cache_sync_service: Optional[CacheSyncService] = None
|
||||
_cache_sync_service: CacheSyncService | None = None
|
||||
|
||||
|
||||
async def get_cache_sync_service(redis_client: aioredis.Redis = None) -> Optional[CacheSyncService]:
|
||||
async def get_cache_sync_service(redis_client: aioredis.Redis = None) -> CacheSyncService | None:
|
||||
"""
|
||||
获取缓存同步服务实例
|
||||
|
||||
|
||||
7
src/services/cache/user_cache.py
vendored
7
src/services/cache/user_cache.py
vendored
@@ -19,7 +19,6 @@
|
||||
await UserCacheService.invalidate_user_cache(user_id, email)
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -40,7 +39,7 @@ class UserCacheService:
|
||||
CACHE_TTL = CacheTTL.USER
|
||||
|
||||
@staticmethod
|
||||
async def get_user_by_id(db: Session, user_id: str) -> Optional[User]:
|
||||
async def get_user_by_id(db: Session, user_id: str) -> User | None:
|
||||
"""
|
||||
获取用户(带缓存)
|
||||
|
||||
@@ -72,7 +71,7 @@ class UserCacheService:
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
async def get_user_by_email(db: Session, email: str) -> Optional[User]:
|
||||
async def get_user_by_email(db: Session, email: str) -> User | None:
|
||||
"""
|
||||
通过邮箱获取用户(带缓存)
|
||||
|
||||
@@ -103,7 +102,7 @@ class UserCacheService:
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
async def invalidate_user_cache(user_id: str, email: Optional[str] = None):
|
||||
async def invalidate_user_cache(user_id: str, email: str | None = None):
|
||||
"""
|
||||
清除用户缓存
|
||||
|
||||
|
||||
Reference in New Issue
Block a user