mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
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:
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user