refactor: 全局适配 ApiFamily/EndpointKind 结构化标识体系

将新的 (ApiFamily, EndpointKind) / `family:kind` 签名体系应用到整个代码库:
- API Handlers: 所有 adapter/handler 使用新的签名格式
- Services: provider, model, usage, cache, auth 等服务层适配
- Database: ProviderEndpoint 新增 api_family/endpoint_kind 字段
- Frontend: Provider 管理、Usage 表格等组件适配
- Tests: 更新所有相关测试用例
This commit is contained in:
fawney19
2026-02-01 17:28:00 +08:00
parent c246ccfc91
commit 7b66505634
219 changed files with 4732 additions and 2545 deletions

View File

@@ -2,15 +2,16 @@
from __future__ import annotations
from typing import Any
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy import func
from sqlalchemy.orm import Session
from src.api.base.admin_adapter import AdminApiAdapter
from src.api.base.context import ApiRequestContext
from src.api.base.pagination import PaginationMeta, build_pagination_payload, paginate_query
from src.api.base.pipeline import ApiRequestPipeline
from src.core.logger import logger
@@ -26,8 +27,6 @@ from src.models.database import User as DBUser
from src.services.health.monitor import HealthMonitor
from src.services.system.audit import audit_service
from src.utils.database_helpers import escape_like_pattern
from src.api.base.context import ApiRequestContext
router = APIRouter(prefix="/api/admin/monitoring", tags=["Admin - Monitoring"])
pipeline = ApiRequestPipeline()

View File

@@ -776,9 +776,9 @@ class AdminListAffinitiesAdapter(AdminApiAdapter):
global_model_map: dict[str, GlobalModel] = {}
if global_model_ids:
# model_name 可能是 UUID 格式的 global_model_id也可能是原始模型名称
global_models = db.query(GlobalModel).filter(
GlobalModel.id.in_(list(global_model_ids))
).all()
global_models = (
db.query(GlobalModel).filter(GlobalModel.id.in_(list(global_model_ids))).all()
)
global_model_map = {str(gm.id): gm for gm in global_models}
keyword_lower = self.keyword.lower() if self.keyword else None
@@ -830,12 +830,14 @@ class AdminListAffinitiesAdapter(AdminApiAdapter):
"global_model_id": affinity.get("model_name"), # 原始的 global_model_id
"model_name": (
global_model_map.get(affinity.get("model_name")).name
if affinity.get("model_name") and global_model_map.get(affinity.get("model_name"))
if affinity.get("model_name")
and global_model_map.get(affinity.get("model_name"))
else affinity.get("model_name") # 如果找不到 GlobalModel显示原始值
),
"model_display_name": (
global_model_map.get(affinity.get("model_name")).display_name
if affinity.get("model_name") and global_model_map.get(affinity.get("model_name"))
if affinity.get("model_name")
and global_model_map.get(affinity.get("model_name"))
else None
),
"api_format": affinity.get("api_format"),
@@ -916,7 +918,9 @@ class AdminClearUserCacheAdapter(AdminApiAdapter):
)
count += 1
logger.info(f"已清除API Key缓存亲和性: api_key_name={api_key.name}, affinity_key={affinity_key[:8]}..., 清除数量={count}")
logger.info(
f"已清除API Key缓存亲和性: api_key_name={api_key.name}, affinity_key={affinity_key[:8]}..., 清除数量={count}"
)
response = {
"status": "ok",
@@ -969,7 +973,9 @@ class AdminClearUserCacheAdapter(AdminApiAdapter):
)
count += 1
logger.info(f"已清除用户缓存亲和性: username={user.username}, user_id={user_id[:8]}..., 清除数量={count}")
logger.info(
f"已清除用户缓存亲和性: username={user.username}, user_id={user_id[:8]}..., 清除数量={count}"
)
response = {
"status": "ok",
@@ -1075,7 +1081,9 @@ class AdminClearProviderCacheAdapter(AdminApiAdapter):
redis_client = get_redis_client_sync()
affinity_mgr = await get_affinity_manager(redis_client)
count = await affinity_mgr.invalidate_all_for_provider(self.provider_id)
logger.info(f"已清除Provider缓存亲和性: provider_id={self.provider_id[:8]}..., count={count}")
logger.info(
f"已清除Provider缓存亲和性: provider_id={self.provider_id[:8]}..., count={count}"
)
context.add_audit_metadata(
action="cache_clear_provider",
provider_id=self.provider_id,
@@ -1094,8 +1102,8 @@ class AdminClearProviderCacheAdapter(AdminApiAdapter):
class AdminCacheConfigAdapter(AdminApiAdapter):
async def handle(self, context: ApiRequestContext) -> dict[str, Any]: # type: ignore[override]
from src.services.cache.affinity_manager import CacheAffinityManager
from src.config.constants import ConcurrencyDefaults
from src.services.cache.affinity_manager import CacheAffinityManager
from src.services.rate_limit.adaptive_reservation import get_adaptive_reservation_manager
# 获取动态预留管理器的配置
@@ -1334,11 +1342,13 @@ class AdminModelMappingCacheStatsAdapter(AdminApiAdapter):
)
if cached_str == "NOT_FOUND":
unmapped_entries.append({
"mapping_name": mapping_name,
"status": "not_found",
"ttl": ttl if ttl > 0 else None,
})
unmapped_entries.append(
{
"mapping_name": mapping_name,
"status": "not_found",
"ttl": ttl if ttl > 0 else None,
}
)
else:
try:
cached_data = json.loads(cached_str)
@@ -1380,27 +1390,33 @@ class AdminModelMappingCacheStatsAdapter(AdminApiAdapter):
provider_names.append(provider.name)
provider_names = sorted(list(set(provider_names)))
mappings.append({
"mapping_name": mapping_name,
"global_model_name": global_model_name,
"global_model_display_name": global_model_display_name,
"providers": provider_names,
"ttl": ttl if ttl > 0 else None,
})
mappings.append(
{
"mapping_name": mapping_name,
"global_model_name": global_model_name,
"global_model_display_name": global_model_display_name,
"providers": provider_names,
"ttl": ttl if ttl > 0 else None,
}
)
except json.JSONDecodeError:
unmapped_entries.append({
"mapping_name": mapping_name,
"status": "invalid",
"ttl": ttl if ttl > 0 else None,
})
unmapped_entries.append(
{
"mapping_name": mapping_name,
"status": "invalid",
"ttl": ttl if ttl > 0 else None,
}
)
except Exception as e:
logger.warning(f"解析缓存键 {key} 失败: {e}")
unmapped_entries.append({
"mapping_name": mapping_name,
"status": "error",
"ttl": None,
})
unmapped_entries.append(
{
"mapping_name": mapping_name,
"status": "error",
"ttl": None,
}
)
# 按 mapping_name 排序
mappings.sort(key=lambda x: x["mapping_name"])
@@ -1408,8 +1424,13 @@ class AdminModelMappingCacheStatsAdapter(AdminApiAdapter):
# 3. 解析 provider_global 缓存Provider 级别的模型解析缓存)
provider_model_mappings = []
# 预加载 Provider 和 GlobalModel 数据
provider_map = {str(p.id): p for p in db.query(Provider).filter(Provider.is_active.is_(True)).all()}
global_model_map = {str(gm.id): gm for gm in db.query(GlobalModel).filter(GlobalModel.is_active.is_(True)).all()}
provider_map = {
str(p.id): p for p in db.query(Provider).filter(Provider.is_active.is_(True)).all()
}
global_model_map = {
str(gm.id): gm
for gm in db.query(GlobalModel).filter(GlobalModel.is_active.is_(True)).all()
}
for key in provider_global_keys[:100]: # 最多处理 100 个
# key 格式: model:provider_global:{provider_id}:{global_model_id}
@@ -1447,7 +1468,9 @@ class AdminModelMappingCacheStatsAdapter(AdminApiAdapter):
mapping_names = []
if cached_model_mappings:
for mapping_entry in cached_model_mappings:
if isinstance(mapping_entry, dict) and mapping_entry.get("name"):
if isinstance(mapping_entry, dict) and mapping_entry.get(
"name"
):
mapping_names.append(mapping_entry["name"])
# provider_model_name 为空时跳过
@@ -1463,19 +1486,23 @@ class AdminModelMappingCacheStatsAdapter(AdminApiAdapter):
if has_name_mapping or has_mappings:
# 构建用于展示的映射列表
# 如果只有名称映射没有额外映射,则用 global_model_name 作为"请求名称"
display_mappings = mapping_names if mapping_names else [global_model.name]
display_mappings = (
mapping_names if mapping_names else [global_model.name]
)
provider_model_mappings.append({
"provider_id": provider_id,
"provider_name": provider.name,
"global_model_id": global_model_id,
"global_model_name": global_model.name,
"global_model_display_name": global_model.display_name,
"provider_model_name": provider_model_name,
"aliases": display_mappings,
"ttl": ttl if ttl > 0 else None,
"hit_count": hit_count,
})
provider_model_mappings.append(
{
"provider_id": provider_id,
"provider_name": provider.name,
"global_model_id": global_model_id,
"global_model_name": global_model.name,
"global_model_display_name": global_model.display_name,
"provider_model_name": provider_model_name,
"aliases": display_mappings,
"ttl": ttl if ttl > 0 else None,
"hit_count": hit_count,
}
)
except json.JSONDecodeError:
pass
except Exception as e:
@@ -1496,7 +1523,9 @@ class AdminModelMappingCacheStatsAdapter(AdminApiAdapter):
"global_model_resolve": len(global_model_resolve_keys),
},
"mappings": mappings,
"provider_model_mappings": provider_model_mappings if provider_model_mappings else None,
"provider_model_mappings": (
provider_model_mappings if provider_model_mappings else None
),
"unmapped": unmapped_entries if unmapped_entries else None,
}

View File

@@ -4,21 +4,21 @@
from __future__ import annotations
from typing import Any
from dataclasses import dataclass
from datetime import datetime
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import BaseModel, ConfigDict
from sqlalchemy.orm import Session
from src.api.base.admin_adapter import AdminApiAdapter
from src.api.base.pipeline import ApiRequestPipeline
from src.database import get_db
from src.models.database import Provider, ProviderEndpoint, ProviderAPIKey
from src.core.crypto import crypto_service
from src.services.request.candidate import RequestCandidateService
from src.api.base.context import ApiRequestContext
from src.api.base.pipeline import ApiRequestPipeline
from src.core.crypto import crypto_service
from src.database import get_db
from src.models.database import Provider, ProviderAPIKey, ProviderEndpoint
from src.services.request.candidate import RequestCandidateService
router = APIRouter(prefix="/api/admin/monitoring/trace", tags=["Admin - Monitoring: Trace"])
pipeline = ApiRequestPipeline()
@@ -184,8 +184,7 @@ class AdminGetRequestTraceAdapter(AdminApiAdapter):
# 4. status="pending" 表示请求尚未开始执行
# 5. status="cancelled" 表示客户端主动断开连接(不算失败)
has_success = any(
c.status == "success"
or (c.status_code is not None and 200 <= c.status_code < 300)
c.status == "success" or (c.status_code is not None and 200 <= c.status_code < 300)
for c in candidates
)
has_streaming = any(c.status == "streaming" for c in candidates)
@@ -221,7 +220,9 @@ class AdminGetRequestTraceAdapter(AdminApiAdapter):
endpoint_ids = {c.endpoint_id for c in candidates if c.endpoint_id}
endpoint_map = {}
if endpoint_ids:
endpoints = db.query(ProviderEndpoint).filter(ProviderEndpoint.id.in_(endpoint_ids)).all()
endpoints = (
db.query(ProviderEndpoint).filter(ProviderEndpoint.id.in_(endpoint_ids)).all()
)
endpoint_map = {e.id: e.api_format for e in endpoints}
# 批量加载 key 信息
@@ -245,7 +246,9 @@ class AdminGetRequestTraceAdapter(AdminApiAdapter):
prefix_end = len(prefix)
break
if prefix_end > 0:
key_preview_map[k.id] = f"{decrypted_key[:prefix_end]}***{decrypted_key[-4:]}"
key_preview_map[k.id] = (
f"{decrypted_key[:prefix_end]}***{decrypted_key[-4:]}"
)
else:
key_preview_map[k.id] = f"{decrypted_key[:4]}***{decrypted_key[-4:]}"
elif len(decrypted_key) > 4:
@@ -267,12 +270,8 @@ class AdminGetRequestTraceAdapter(AdminApiAdapter):
endpoint_name = (
endpoint_map.get(candidate.endpoint_id) if candidate.endpoint_id else None
)
key_name = (
key_map.get(candidate.key_id) if candidate.key_id else None
)
key_preview = (
key_preview_map.get(candidate.key_id) if candidate.key_id else None
)
key_name = key_map.get(candidate.key_id) if candidate.key_id else None
key_preview = key_preview_map.get(candidate.key_id) if candidate.key_id else None
key_capabilities = (
key_capabilities_map.get(candidate.key_id) if candidate.key_id else None
)