mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
feat: 缓存计费细分、能力匹配优化、用户模型调用计数
1. 缓存创建 tokens 区分 5min/1h TTL,支持按缓存时长差异化计费 - Usage 表新增 cache_creation_input_tokens_5m/1h 字段 - Claude handler 解析新格式 (ephemeral_5m/1h, claude_cache_creation_5/1h) - 计费规则支持 cache_ttl_pricing 覆盖 cache_creation 价格 2. 能力匹配机制优化 - COMPATIBLE 能力不再硬过滤,改为排序阶段通过 capability_miss_count 优先级处理 - cache_1h 改为 COMPATIBLE + REQUEST_PARAM(自动检测请求体中的 ttl=1h) - gemini_files 改为 EXCLUSIVE + REQUEST_PARAM(自动检测 fileData.fileUri) - 移除前端模型偏好/能力配置 UI(不再需要用户手动配置) 3. 新增用户-模型维度调用次数计数器 (UserModelUsageCount) - 原子递增,避免从 Usage 表聚合查询 - 前端模型目录和用户可用模型列表展示调用次数 4. 其他改进 - global_model_id 改为必填(NOT NULL),清理孤立模型 - 模型映射对话框支持从上游获取模型列表并分组折叠 - 端点测试不再依赖端点启用状态 - 异步任务页面对普通用户隐藏用户信息列 - Dashboard 响应式布局断点调整 (sm -> lg) - 号池管理仅展示已启用号池的提供商
This commit is contained in:
@@ -30,6 +30,7 @@ from src.services.model.fetch_scheduler import (
|
||||
from src.services.model.upstream_fetcher import (
|
||||
EndpointFetchConfig,
|
||||
UpstreamModelsFetchContext,
|
||||
UpstreamModelsFetcherRegistry,
|
||||
build_format_to_config,
|
||||
fetch_models_for_key,
|
||||
get_adapter_for_format,
|
||||
@@ -235,7 +236,15 @@ async def query_available_models(
|
||||
# 构建 api_format -> EndpointFetchConfig 映射(纯数据,不依赖 ORM session)
|
||||
format_to_endpoint = build_format_to_config(provider.endpoints)
|
||||
|
||||
if not format_to_endpoint:
|
||||
# 检查是否有注册自定义 fetcher(如预设模型),有则不依赖活跃 endpoint
|
||||
provider_type = str(getattr(provider, "provider_type", "") or "").lower()
|
||||
# 延迟导入避免循环依赖(与 upstream_fetcher.fetch_models_for_key 保持一致)
|
||||
from src.services.provider.envelope import ensure_providers_bootstrapped
|
||||
|
||||
ensure_providers_bootstrapped()
|
||||
has_custom_fetcher = UpstreamModelsFetcherRegistry.get(provider_type) is not None
|
||||
|
||||
if not format_to_endpoint and not has_custom_fetcher:
|
||||
raise HTTPException(status_code=400, detail="No active endpoints found for this provider")
|
||||
|
||||
# 如果指定了 api_key_id,只获取该 Key 的模型
|
||||
@@ -253,7 +262,6 @@ async def query_available_models(
|
||||
raise HTTPException(status_code=400, detail="No active API Key found for this provider")
|
||||
|
||||
# Antigravity: 按 tier/可用性排序后逐个尝试,成功即停止
|
||||
provider_type = str(getattr(provider, "provider_type", "") or "").lower()
|
||||
if provider_type == ProviderType.ANTIGRAVITY:
|
||||
return await _fetch_models_antigravity_ordered(
|
||||
provider=provider,
|
||||
@@ -610,12 +618,12 @@ async def test_model(
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
|
||||
# 构建 api_format -> endpoint 映射 和 id -> endpoint 映射
|
||||
# 测试不依赖端点启用状态,禁用的端点也可以用于测试连通性
|
||||
format_to_endpoint: dict[str, ProviderEndpoint] = {}
|
||||
id_to_endpoint: dict[str, ProviderEndpoint] = {}
|
||||
for ep in provider.endpoints:
|
||||
if ep.is_active:
|
||||
format_to_endpoint[ep.api_format] = ep
|
||||
id_to_endpoint[ep.id] = ep
|
||||
format_to_endpoint[ep.api_format] = ep
|
||||
id_to_endpoint[ep.id] = ep
|
||||
|
||||
# 找到合适的端点和 API Key
|
||||
endpoint = None
|
||||
@@ -628,7 +636,7 @@ async def test_model(
|
||||
if not endpoint:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"No active endpoint found for API format: {request.api_format}",
|
||||
detail=f"No endpoint found for API format: {request.api_format}",
|
||||
)
|
||||
|
||||
if request.api_key_id:
|
||||
@@ -657,7 +665,7 @@ async def test_model(
|
||||
# 使用指定的端点
|
||||
endpoint = id_to_endpoint.get(request.endpoint_id)
|
||||
if not endpoint:
|
||||
raise HTTPException(status_code=404, detail="Endpoint not found or not active")
|
||||
raise HTTPException(status_code=404, detail="Endpoint not found")
|
||||
|
||||
if request.api_key_id:
|
||||
# 同时指定了 Key,需要校验是否支持该端点格式
|
||||
|
||||
@@ -715,6 +715,7 @@ class AdminImportFromUpstreamAdapter(AdminApiAdapter):
|
||||
# 1. 检查是否已存在同名的 ProviderModel
|
||||
existing = (
|
||||
db.query(Model)
|
||||
.options(joinedload(Model.global_model))
|
||||
.filter(
|
||||
Model.provider_id == self.provider_id,
|
||||
Model.provider_model_name == model_id,
|
||||
@@ -727,10 +728,8 @@ class AdminImportFromUpstreamAdapter(AdminApiAdapter):
|
||||
success.append(
|
||||
ImportFromUpstreamSuccessItem(
|
||||
model_id=model_id,
|
||||
global_model_id=existing.global_model_id or "",
|
||||
global_model_name=(
|
||||
existing.global_model.name if existing.global_model else ""
|
||||
),
|
||||
global_model_id=existing.global_model_id,
|
||||
global_model_name=existing.global_model.name,
|
||||
provider_model_id=existing.id,
|
||||
created_global_model=False,
|
||||
)
|
||||
|
||||
@@ -319,7 +319,6 @@ def _build_provider_summary(db: Session, provider: Provider) -> ProviderWithEndp
|
||||
.filter(
|
||||
Model.provider_id == provider.id,
|
||||
Model.is_active == True,
|
||||
Model.global_model_id.isnot(None),
|
||||
)
|
||||
.distinct()
|
||||
.all()
|
||||
|
||||
@@ -2556,16 +2556,18 @@ def _purge_stats_and_reset_counters(db: Session) -> None:
|
||||
class AdminPurgeUsageAdapter(AdminApiAdapter):
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
"""清空全部使用记录及相关统计数据"""
|
||||
from src.models.database import RequestCandidate
|
||||
from src.models.database import RequestCandidate, UserModelUsageCount
|
||||
|
||||
db = context.db
|
||||
|
||||
usage_count = db.query(Usage).count()
|
||||
candidates_count = db.query(RequestCandidate).count()
|
||||
usage_counts_count = db.query(UserModelUsageCount).count()
|
||||
|
||||
# 清空使用记录
|
||||
db.query(RequestCandidate).delete()
|
||||
db.query(Usage).delete()
|
||||
db.query(UserModelUsageCount).delete()
|
||||
|
||||
_purge_stats_and_reset_counters(db)
|
||||
db.commit()
|
||||
@@ -2575,6 +2577,7 @@ class AdminPurgeUsageAdapter(AdminApiAdapter):
|
||||
"deleted": {
|
||||
"usage_records": usage_count,
|
||||
"request_candidates": candidates_count,
|
||||
"user_model_usage_counts": usage_counts_count,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1259,6 +1259,8 @@ class AdminUsageDetailAdapter(AdminApiAdapter):
|
||||
},
|
||||
"cache_creation_input_tokens": usage_record.cache_creation_input_tokens,
|
||||
"cache_read_input_tokens": usage_record.cache_read_input_tokens,
|
||||
"cache_creation_input_tokens_5m": usage_record.cache_creation_input_tokens_5m or 0,
|
||||
"cache_creation_input_tokens_1h": usage_record.cache_creation_input_tokens_1h or 0,
|
||||
"cache_creation_cost": getattr(usage_record, "cache_creation_cost_usd", 0.0),
|
||||
"cache_read_cost": getattr(usage_record, "cache_read_cost_usd", 0.0),
|
||||
"request_cost": getattr(usage_record, "request_cost_usd", 0.0),
|
||||
|
||||
Reference in New Issue
Block a user