mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor: 用 EndpointFetchConfig 纯数据类替代 ORM 对象传递,统一上游模型缓存管理
- 引入 EndpointFetchConfig dataclass 替代直接传递 ProviderEndpoint ORM 对象, 避免 DB session 关闭后 DetachedInstanceError - 新增 build_format_to_config() 统一构建 api_format -> EndpointFetchConfig 映射 - KeyAllowedModelsDialog 改用 useUpstreamModelsCache composable 管理上游模型获取 - useUpstreamModelsCache 增加 error 字段透传部分格式获取失败的 warning - 删除废弃的 queryProviderUpstreamModels API 函数 - ProviderCandidate 添加 __lt__ 方法支持排序比较
This commit is contained in:
@@ -27,7 +27,9 @@ from src.services.model.fetch_scheduler import (
|
||||
set_upstream_models_to_cache,
|
||||
)
|
||||
from src.services.model.upstream_fetcher import (
|
||||
EndpointFetchConfig,
|
||||
UpstreamModelsFetchContext,
|
||||
build_format_to_config,
|
||||
fetch_models_for_key,
|
||||
get_adapter_for_format,
|
||||
)
|
||||
@@ -171,11 +173,8 @@ async def query_available_models(
|
||||
if not provider:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
|
||||
# 构建 api_format -> endpoint 映射
|
||||
format_to_endpoint: dict[str, ProviderEndpoint] = {}
|
||||
for endpoint in provider.endpoints:
|
||||
if endpoint.is_active:
|
||||
format_to_endpoint[endpoint.api_format] = endpoint
|
||||
# 构建 api_format -> EndpointFetchConfig 映射(纯数据,不依赖 ORM session)
|
||||
format_to_endpoint = build_format_to_config(provider.endpoints)
|
||||
|
||||
if not format_to_endpoint:
|
||||
raise HTTPException(status_code=400, detail="No active endpoints found for this provider")
|
||||
@@ -327,7 +326,7 @@ def _aggregate_models_by_id(models: list[dict]) -> list[dict]:
|
||||
async def _fetch_models_for_single_key(
|
||||
provider: Provider,
|
||||
api_key_id: str,
|
||||
format_to_endpoint: dict[str, ProviderEndpoint],
|
||||
format_to_endpoint: dict[str, EndpointFetchConfig],
|
||||
force_refresh: bool,
|
||||
) -> Any:
|
||||
"""获取单个 Key 的模型列表"""
|
||||
|
||||
@@ -28,6 +28,16 @@ class ProviderCandidate:
|
||||
if self.metadata is None:
|
||||
self.metadata = {}
|
||||
|
||||
def __lt__(self, other: object) -> bool:
|
||||
if not isinstance(other, ProviderCandidate):
|
||||
return NotImplemented
|
||||
# 优先级数字越大越优先,权重越大越优先
|
||||
return (-self.priority, -self.weight, str(getattr(self.provider, "id", ""))) < (
|
||||
-other.priority,
|
||||
-other.weight,
|
||||
str(getattr(other.provider, "id", "")),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SelectionResult:
|
||||
|
||||
@@ -28,7 +28,9 @@ from src.core.provider_types import ProviderType
|
||||
from src.database import create_session
|
||||
from src.models.database import Provider, ProviderAPIKey
|
||||
from src.services.model.upstream_fetcher import (
|
||||
EndpointFetchConfig,
|
||||
UpstreamModelsFetchContext,
|
||||
build_format_to_config,
|
||||
fetch_models_for_key,
|
||||
merge_upstream_metadata,
|
||||
)
|
||||
@@ -62,7 +64,7 @@ class PreparedModelsFetchContext:
|
||||
auth_type: str
|
||||
encrypted_api_key: str
|
||||
encrypted_auth_config: str | None
|
||||
format_to_endpoint: dict[str, Any]
|
||||
format_to_endpoint: dict[str, EndpointFetchConfig]
|
||||
proxy_config: dict[str, Any] | None
|
||||
|
||||
|
||||
@@ -422,11 +424,8 @@ class ModelFetchScheduler:
|
||||
db.commit()
|
||||
return "error"
|
||||
|
||||
# 构建 api_format -> endpoint 映射
|
||||
format_to_endpoint: dict[str, Any] = {}
|
||||
for endpoint in provider.endpoints: # type: ignore[attr-defined]
|
||||
if endpoint.is_active:
|
||||
format_to_endpoint[endpoint.api_format] = endpoint
|
||||
# 构建 api_format -> EndpointFetchConfig 映射(纯数据,session 无关)
|
||||
format_to_endpoint = build_format_to_config(provider.endpoints) # type: ignore[attr-defined]
|
||||
|
||||
if not format_to_endpoint:
|
||||
logger.warning(f"Provider {provider.name} 没有活跃的端点,跳过 Key {key.id}")
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
@@ -14,7 +15,6 @@ import httpx
|
||||
|
||||
from src.core.api_format import get_extra_headers_from_endpoint
|
||||
from src.core.logger import logger
|
||||
from src.models.database import ProviderEndpoint
|
||||
from src.utils.ssl_utils import get_ssl_context
|
||||
|
||||
# 并发请求限制
|
||||
@@ -35,13 +35,42 @@ _ModelsFetcher = Callable[
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class EndpointFetchConfig:
|
||||
"""端点获取配置(纯数据,不依赖 DB session)。
|
||||
|
||||
从 ProviderEndpoint ORM 对象提取必要字段,确保在 DB session 关闭后
|
||||
仍可安全使用(避免 DetachedInstanceError)。
|
||||
"""
|
||||
|
||||
base_url: str
|
||||
extra_headers: dict[str, str] | None = None
|
||||
|
||||
|
||||
def build_format_to_config(endpoints: Iterable[Any]) -> dict[str, EndpointFetchConfig]:
|
||||
"""将活跃的 ProviderEndpoint 转换为 api_format -> EndpointFetchConfig 映射。
|
||||
|
||||
应在 DB session 活跃时调用,提取 ORM 对象上的 base_url 和 header_rules,
|
||||
转换为 session 无关的纯数据结构。
|
||||
"""
|
||||
result: dict[str, EndpointFetchConfig] = {}
|
||||
for ep in endpoints:
|
||||
if not getattr(ep, "is_active", False):
|
||||
continue
|
||||
result[ep.api_format] = EndpointFetchConfig(
|
||||
base_url=ep.base_url,
|
||||
extra_headers=get_extra_headers_from_endpoint(ep),
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UpstreamModelsFetchContext:
|
||||
"""上游模型获取上下文(Key 级别)。"""
|
||||
|
||||
provider_type: str
|
||||
api_key_value: str
|
||||
format_to_endpoint: dict[str, Any]
|
||||
format_to_endpoint: dict[str, EndpointFetchConfig]
|
||||
proxy_config: dict[str, Any] | None = None
|
||||
auth_config: dict[str, Any] | None = None
|
||||
|
||||
@@ -149,7 +178,7 @@ def get_adapter_for_format(api_format: str) -> type | None:
|
||||
|
||||
def build_all_format_configs(
|
||||
api_key_value: str,
|
||||
format_to_endpoint: dict[str, ProviderEndpoint],
|
||||
format_to_endpoint: dict[str, EndpointFetchConfig],
|
||||
) -> list[dict]:
|
||||
"""
|
||||
构建所有 API 格式的端点配置
|
||||
@@ -159,7 +188,7 @@ def build_all_format_configs(
|
||||
|
||||
Args:
|
||||
api_key_value: 解密后的 API Key
|
||||
format_to_endpoint: API 格式到端点的映射
|
||||
format_to_endpoint: API 格式到 EndpointFetchConfig 的映射
|
||||
|
||||
Returns:
|
||||
端点配置列表,每个配置包含 api_key, base_url, api_format, extra_headers
|
||||
@@ -172,13 +201,13 @@ def build_all_format_configs(
|
||||
for candidates in MODEL_FETCH_FORMAT_PRIORITY:
|
||||
fmt = next((f for f in candidates if f in format_to_endpoint), None)
|
||||
if fmt is not None:
|
||||
ep = format_to_endpoint[fmt]
|
||||
cfg = format_to_endpoint[fmt]
|
||||
configs.append(
|
||||
{
|
||||
"api_key": api_key_value,
|
||||
"base_url": ep.base_url,
|
||||
"base_url": cfg.base_url,
|
||||
"api_format": fmt,
|
||||
"extra_headers": get_extra_headers_from_endpoint(ep),
|
||||
"extra_headers": cfg.extra_headers,
|
||||
}
|
||||
)
|
||||
return configs
|
||||
|
||||
Reference in New Issue
Block a user