mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/ - 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层 - 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构 - 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations) - 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image - 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
This commit is contained in:
352
_deprecated_py_src/api/admin/provider_strategy.py
Normal file
352
_deprecated_py_src/api/admin/provider_strategy.py
Normal file
@@ -0,0 +1,352 @@
|
||||
"""
|
||||
提供商策略管理 API 端点
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
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.pipeline import get_pipeline
|
||||
from src.core.enums import ProviderBillingType
|
||||
from src.core.exceptions import InvalidRequestException, translate_pydantic_error
|
||||
from src.core.logger import logger
|
||||
from src.database import get_db
|
||||
from src.models.database import Provider
|
||||
from src.models.database_extensions import ProviderUsageTracking
|
||||
|
||||
router = APIRouter(prefix="/api/admin/provider-strategy", tags=["Provider Strategy"])
|
||||
pipeline = get_pipeline()
|
||||
|
||||
|
||||
class ProviderBillingUpdate(BaseModel):
|
||||
billing_type: ProviderBillingType
|
||||
monthly_quota_usd: float | None = None
|
||||
quota_reset_day: int = Field(default=30, ge=1, le=365) # 重置周期(天数)
|
||||
quota_last_reset_at: str | None = None # 当前周期开始时间
|
||||
quota_expires_at: str | None = None
|
||||
rpm_limit: int | None = Field(default=None, ge=0)
|
||||
provider_priority: int = Field(default=100, ge=0, le=10000)
|
||||
|
||||
|
||||
@router.put("/providers/{provider_id}/billing")
|
||||
async def update_provider_billing(
|
||||
provider_id: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
) -> Any:
|
||||
"""
|
||||
更新提供商计费配置
|
||||
|
||||
更新指定提供商的计费策略、配额设置和优先级配置。
|
||||
|
||||
**路径参数**:
|
||||
- `provider_id`: 提供商 ID
|
||||
|
||||
**请求体字段**:
|
||||
- `billing_type`: 计费类型(pay_as_you_go、subscription、prepaid、monthly_quota)
|
||||
- `monthly_quota_usd`: 月度配额(美元),可选
|
||||
- `quota_reset_day`: 配额重置周期(天数,1-365),默认 30
|
||||
- `quota_last_reset_at`: 当前周期开始时间,可选(设置后会自动同步该周期内的历史使用量)
|
||||
- `quota_expires_at`: 配额过期时间,可选
|
||||
- `rpm_limit`: 每分钟请求数限制,可选
|
||||
- `provider_priority`: 提供商优先级(0-200),默认 100
|
||||
|
||||
**返回字段**:
|
||||
- `message`: 操作结果信息
|
||||
- `provider`: 更新后的提供商信息
|
||||
- `id`: 提供商 ID
|
||||
- `name`: 提供商名称
|
||||
- `billing_type`: 计费类型
|
||||
- `provider_priority`: 提供商优先级
|
||||
"""
|
||||
adapter = AdminProviderBillingAdapter(provider_id=provider_id)
|
||||
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
|
||||
|
||||
|
||||
@router.get("/providers/{provider_id}/stats")
|
||||
async def get_provider_stats(
|
||||
provider_id: str,
|
||||
request: Request,
|
||||
hours: int = 24,
|
||||
db: Session = Depends(get_db),
|
||||
) -> Any:
|
||||
"""
|
||||
获取提供商统计数据
|
||||
|
||||
获取指定提供商的计费信息和使用统计数据。
|
||||
|
||||
**路径参数**:
|
||||
- `provider_id`: 提供商 ID
|
||||
|
||||
**查询参数**:
|
||||
- `hours`: 统计时间范围(小时),默认 24
|
||||
|
||||
**返回字段**:
|
||||
- `provider_id`: 提供商 ID
|
||||
- `provider_name`: 提供商名称
|
||||
- `period_hours`: 统计时间范围
|
||||
- `billing_info`: 计费信息
|
||||
- `billing_type`: 计费类型
|
||||
- `monthly_quota_usd`: 月度配额
|
||||
- `monthly_used_usd`: 月度已使用
|
||||
- `quota_remaining_usd`: 剩余配额
|
||||
- `quota_expires_at`: 配额过期时间
|
||||
- `usage_stats`: 使用统计
|
||||
- `total_requests`: 总请求数
|
||||
- `successful_requests`: 成功请求数
|
||||
- `failed_requests`: 失败请求数
|
||||
- `success_rate`: 成功率
|
||||
- `avg_response_time_ms`: 平均响应时间(毫秒)
|
||||
- `total_cost_usd`: 总成本(美元)
|
||||
"""
|
||||
adapter = AdminProviderStatsAdapter(provider_id=provider_id, hours=hours)
|
||||
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
|
||||
|
||||
|
||||
@router.delete("/providers/{provider_id}/quota")
|
||||
async def reset_provider_quota(
|
||||
provider_id: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
) -> Any:
|
||||
"""Reset provider quota usage to zero"""
|
||||
adapter = AdminProviderResetQuotaAdapter(provider_id=provider_id)
|
||||
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
|
||||
|
||||
|
||||
@router.get("/strategies")
|
||||
async def list_available_strategies(request: Request, db: Session = Depends(get_db)) -> Any:
|
||||
"""
|
||||
获取可用负载均衡策略列表
|
||||
|
||||
列出系统中所有已注册的负载均衡策略插件。
|
||||
|
||||
**返回字段**:
|
||||
- `strategies`: 策略列表
|
||||
- `name`: 策略名称
|
||||
- `priority`: 策略优先级
|
||||
- `version`: 策略版本
|
||||
- `description`: 策略描述
|
||||
- `author`: 策略作者
|
||||
- `total`: 策略总数
|
||||
"""
|
||||
adapter = AdminListStrategiesAdapter()
|
||||
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
|
||||
|
||||
|
||||
class AdminProviderBillingAdapter(AdminApiAdapter):
|
||||
def __init__(self, provider_id: str):
|
||||
self.provider_id = provider_id
|
||||
|
||||
async def handle(self, context: ApiRequestContext) -> Any:
|
||||
db = context.db
|
||||
payload = context.ensure_json_body()
|
||||
try:
|
||||
config = ProviderBillingUpdate.model_validate(payload)
|
||||
except ValidationError as e:
|
||||
errors = e.errors()
|
||||
if errors:
|
||||
raise InvalidRequestException(translate_pydantic_error(errors[0]))
|
||||
raise InvalidRequestException("请求数据验证失败")
|
||||
|
||||
provider = db.query(Provider).filter(Provider.id == self.provider_id).first()
|
||||
if not provider:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
|
||||
provider.billing_type = config.billing_type
|
||||
provider.monthly_quota_usd = config.monthly_quota_usd
|
||||
provider.quota_reset_day = config.quota_reset_day
|
||||
provider.provider_priority = config.provider_priority
|
||||
|
||||
from sqlalchemy import func
|
||||
|
||||
from src.models.database import Usage
|
||||
|
||||
if config.quota_last_reset_at:
|
||||
new_reset_at = datetime.fromisoformat(config.quota_last_reset_at)
|
||||
# 确保有时区信息,如果没有则假设为 UTC
|
||||
if new_reset_at.tzinfo is None:
|
||||
new_reset_at = new_reset_at.replace(tzinfo=timezone.utc)
|
||||
provider.quota_last_reset_at = new_reset_at
|
||||
|
||||
# 自动同步该周期内的历史使用量
|
||||
period_usage = (
|
||||
db.query(func.coalesce(func.sum(Usage.total_cost_usd), 0))
|
||||
.filter(
|
||||
Usage.provider_id == self.provider_id,
|
||||
Usage.created_at >= new_reset_at,
|
||||
)
|
||||
.scalar()
|
||||
)
|
||||
provider.monthly_used_usd = float(period_usage or 0)
|
||||
logger.info(
|
||||
f"Synced usage for provider {provider.name}: ${period_usage:.4f} since {new_reset_at}"
|
||||
)
|
||||
|
||||
if config.quota_expires_at:
|
||||
expires_at = datetime.fromisoformat(config.quota_expires_at)
|
||||
# 确保有时区信息,如果没有则假设为 UTC
|
||||
if expires_at.tzinfo is None:
|
||||
expires_at = expires_at.replace(tzinfo=timezone.utc)
|
||||
provider.quota_expires_at = expires_at
|
||||
|
||||
db.commit()
|
||||
db.refresh(provider)
|
||||
|
||||
logger.info(f"Updated billing config for provider {provider.name}")
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"message": "Provider billing config updated successfully",
|
||||
"provider": {
|
||||
"id": provider.id,
|
||||
"name": provider.name,
|
||||
"billing_type": provider.billing_type.value,
|
||||
"provider_priority": provider.provider_priority,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AdminProviderStatsAdapter(AdminApiAdapter):
|
||||
def __init__(self, provider_id: str, hours: int):
|
||||
self.provider_id = provider_id
|
||||
self.hours = hours
|
||||
|
||||
async def handle(self, context: ApiRequestContext) -> Any:
|
||||
db = context.db
|
||||
provider = db.query(Provider).filter(Provider.id == self.provider_id).first()
|
||||
if not provider:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
|
||||
since = datetime.now(timezone.utc) - timedelta(hours=self.hours)
|
||||
row = (
|
||||
db.query(
|
||||
func.coalesce(func.sum(ProviderUsageTracking.total_requests), 0),
|
||||
func.coalesce(func.sum(ProviderUsageTracking.successful_requests), 0),
|
||||
func.coalesce(func.sum(ProviderUsageTracking.failed_requests), 0),
|
||||
func.coalesce(func.avg(ProviderUsageTracking.avg_response_time_ms), 0),
|
||||
func.coalesce(func.sum(ProviderUsageTracking.total_cost_usd), 0),
|
||||
)
|
||||
.filter(
|
||||
ProviderUsageTracking.provider_id == self.provider_id,
|
||||
ProviderUsageTracking.window_start >= since,
|
||||
)
|
||||
.one()
|
||||
)
|
||||
|
||||
total_requests = int(row[0])
|
||||
total_success = int(row[1])
|
||||
total_failures = int(row[2])
|
||||
avg_response_time = float(row[3])
|
||||
total_cost = float(row[4])
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"provider_id": self.provider_id,
|
||||
"provider_name": provider.name,
|
||||
"period_hours": self.hours,
|
||||
"billing_info": {
|
||||
"billing_type": provider.billing_type.value,
|
||||
"monthly_quota_usd": (
|
||||
float(provider.monthly_quota_usd)
|
||||
if provider.monthly_quota_usd is not None
|
||||
else None
|
||||
),
|
||||
"monthly_used_usd": float(provider.monthly_used_usd or 0),
|
||||
"quota_remaining_usd": (
|
||||
float(provider.monthly_quota_usd - provider.monthly_used_usd)
|
||||
if provider.monthly_quota_usd is not None
|
||||
else None
|
||||
),
|
||||
"quota_expires_at": (
|
||||
provider.quota_expires_at.isoformat() if provider.quota_expires_at else None
|
||||
),
|
||||
},
|
||||
"usage_stats": {
|
||||
"total_requests": total_requests,
|
||||
"successful_requests": total_success,
|
||||
"failed_requests": total_failures,
|
||||
"success_rate": total_success / total_requests if total_requests > 0 else 0,
|
||||
"avg_response_time_ms": round(avg_response_time, 2),
|
||||
"total_cost_usd": round(total_cost, 4),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AdminProviderResetQuotaAdapter(AdminApiAdapter):
|
||||
def __init__(self, provider_id: str):
|
||||
self.provider_id = provider_id
|
||||
|
||||
async def handle(self, context: ApiRequestContext) -> Any:
|
||||
db = context.db
|
||||
provider = db.query(Provider).filter(Provider.id == self.provider_id).first()
|
||||
if not provider:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
|
||||
if provider.billing_type != ProviderBillingType.MONTHLY_QUOTA:
|
||||
raise HTTPException(status_code=400, detail="Only monthly quota providers can be reset")
|
||||
|
||||
old_used = provider.monthly_used_usd
|
||||
provider.monthly_used_usd = 0.0
|
||||
db.commit()
|
||||
|
||||
logger.info(f"Manually reset quota for provider {provider.name}")
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"message": "Provider quota reset successfully",
|
||||
"provider_name": provider.name,
|
||||
"previous_used": old_used,
|
||||
"current_used": 0.0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AdminListStrategiesAdapter(AdminApiAdapter):
|
||||
async def handle(self, context: ApiRequestContext) -> Any:
|
||||
from src.plugins.manager import get_plugin_manager
|
||||
|
||||
plugin_manager = get_plugin_manager()
|
||||
lb_plugins = plugin_manager.plugins.get("load_balancer", {})
|
||||
|
||||
strategies = []
|
||||
for name, plugin in lb_plugins.items():
|
||||
try:
|
||||
strategies.append(
|
||||
{
|
||||
"name": getattr(plugin, "name", name),
|
||||
"priority": getattr(plugin, "priority", 0),
|
||||
"version": (
|
||||
getattr(plugin.metadata, "version", "1.0.0")
|
||||
if hasattr(plugin, "metadata")
|
||||
else "1.0.0"
|
||||
),
|
||||
"description": (
|
||||
getattr(plugin.metadata, "description", "")
|
||||
if hasattr(plugin, "metadata")
|
||||
else ""
|
||||
),
|
||||
"author": (
|
||||
getattr(plugin.metadata, "author", "Unknown")
|
||||
if hasattr(plugin, "metadata")
|
||||
else "Unknown"
|
||||
),
|
||||
}
|
||||
)
|
||||
except Exception as exc: # pragma: no cover
|
||||
logger.error(f"Error accessing plugin {name}: {exc}")
|
||||
continue
|
||||
|
||||
strategies.sort(key=lambda x: x["priority"], reverse=True)
|
||||
return JSONResponse({"strategies": strategies, "total": len(strategies)})
|
||||
Reference in New Issue
Block a user