Files
Aether/src/models/endpoint_models.py

631 lines
24 KiB
Python
Raw Normal View History

2025-12-10 20:52:44 +08:00
"""
ProviderEndpoint 相关的 API 模型定义
"""
import re
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
2025-12-10 20:52:44 +08:00
from src.models.admin_requests import ProxyConfig
2025-12-10 20:52:44 +08:00
# ========== ProviderEndpoint CRUD ==========
class ProviderEndpointCreate(BaseModel):
"""创建 Endpoint 请求"""
provider_id: str = Field(..., description="Provider ID")
api_format: str = Field(..., description="API 格式 (CLAUDE, OPENAI, CLAUDE_CLI, OPENAI_CLI)")
base_url: str = Field(..., min_length=1, max_length=500, description="API 基础 URL")
# 请求配置
headers: Optional[Dict[str, str]] = Field(default=None, description="自定义请求头")
timeout: int = Field(default=300, ge=10, le=600, description="超时时间(秒)")
max_retries: int = Field(default=3, ge=0, le=10, description="最大重试次数")
# 限制
max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数")
rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制(请求/秒)")
# 额外配置
config: Optional[Dict[str, Any]] = Field(default=None, description="额外配置JSON")
# 代理配置
proxy: Optional[ProxyConfig] = Field(default=None, description="代理配置")
2025-12-10 20:52:44 +08:00
@field_validator("api_format")
@classmethod
def validate_api_format(cls, v: str) -> str:
"""验证 API 格式"""
from src.core.enums import APIFormat
allowed = [fmt.value for fmt in APIFormat]
v_upper = v.upper()
if v_upper not in allowed:
raise ValueError(f"API 格式必须是 {allowed} 之一")
return v_upper
@field_validator("base_url")
@classmethod
def validate_base_url(cls, v: str) -> str:
if not re.match(r"^https?://", v, re.IGNORECASE):
raise ValueError("URL 必须以 http:// 或 https:// 开头")
return v.rstrip("/") # 移除末尾斜杠
class ProviderEndpointUpdate(BaseModel):
"""更新 Endpoint 请求"""
base_url: Optional[str] = Field(
default=None, min_length=1, max_length=500, description="API 基础 URL"
)
headers: Optional[Dict[str, str]] = Field(default=None, description="自定义请求头")
timeout: Optional[int] = Field(default=None, ge=10, le=600, description="超时时间(秒)")
max_retries: Optional[int] = Field(default=None, ge=0, le=10, description="最大重试次数")
max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数")
rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制")
is_active: Optional[bool] = Field(default=None, description="是否启用")
config: Optional[Dict[str, Any]] = Field(default=None, description="额外配置")
proxy: Optional[ProxyConfig] = Field(default=None, description="代理配置")
2025-12-10 20:52:44 +08:00
@field_validator("base_url")
@classmethod
def validate_base_url(cls, v: Optional[str]) -> Optional[str]:
"""验证 API URL"""
2025-12-10 20:52:44 +08:00
if v is None:
return v
if not re.match(r"^https?://", v, re.IGNORECASE):
raise ValueError("URL 必须以 http:// 或 https:// 开头")
return v.rstrip("/") # 移除末尾斜杠
class ProviderEndpointResponse(BaseModel):
"""Endpoint 响应"""
id: str
provider_id: str
provider_name: str # 冗余字段,方便前端显示
# API 配置
api_format: str
base_url: str
# 请求配置
headers: Optional[Dict[str, str]] = None
timeout: int
max_retries: int
# 限制
max_concurrent: Optional[int] = None
rate_limit: Optional[int] = None
# 状态
is_active: bool
# 额外配置
config: Optional[Dict[str, Any]] = None
# 代理配置
proxy: Optional[Dict[str, Any]] = Field(default=None, description="代理配置")
2025-12-10 20:52:44 +08:00
# 统计(从 Keys 聚合)
total_keys: int = Field(default=0, description="总 Key 数量")
active_keys: int = Field(default=0, description="活跃 Key 数量")
# 时间戳
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
2025-12-10 20:52:44 +08:00
# ========== ProviderAPIKey 相关(新架构) ==========
class EndpointAPIKeyCreate(BaseModel):
"""为 Endpoint 添加 API Key"""
endpoint_id: str = Field(..., description="Endpoint ID")
api_key: str = Field(..., min_length=10, max_length=500, description="API Key将自动加密")
name: str = Field(..., min_length=1, max_length=100, description="密钥名称(必填,用于识别)")
# 成本计算
rate_multiplier: float = Field(
default=1.0, ge=0.01, description="成本倍率(真实成本 = 表面成本 × 倍率)"
)
# 优先级和限制(数字越小越优先)
internal_priority: int = Field(default=50, description="Endpoint 内部优先级(提供商优先模式)")
# max_concurrent: NULL=自适应模式(系统自动学习),数字=固定限制模式
max_concurrent: Optional[int] = Field(
default=None, ge=1, description="最大并发数NULL=自适应模式)"
)
rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制")
daily_limit: Optional[int] = Field(default=None, ge=1, description="每日限制")
monthly_limit: Optional[int] = Field(default=None, ge=1, description="每月限制")
allowed_models: Optional[List[str]] = Field(
default=None, description="允许使用的模型列表null = 支持所有模型)"
)
# 能力标签
capabilities: Optional[Dict[str, bool]] = Field(
default=None, description="Key 能力标签,如 {'cache_1h': true, 'context_1m': true}"
)
# 缓存与熔断配置
cache_ttl_minutes: int = Field(
default=5, ge=0, le=60, description="缓存 TTL分钟0=禁用默认5分钟"
)
max_probe_interval_minutes: int = Field(
default=32, ge=2, le=32, description="熔断探测间隔(分钟),范围 2-32"
)
# 备注
note: Optional[str] = Field(default=None, max_length=500, description="备注说明(可选)")
@field_validator("api_key")
@classmethod
def validate_api_key(cls, v: str) -> str:
"""验证 API Key 安全性"""
# 移除首尾空白
v = v.strip()
# 检查最小长度
if len(v) < 10:
raise ValueError("API Key 长度不能少于 10 个字符")
# 检查危险字符SQL 注入防护)
dangerous_chars = ["'", '"', ";", "--", "/*", "*/", "<", ">"]
for char in dangerous_chars:
if char in v:
raise ValueError(f"API Key 包含非法字符: {char}")
return v
@field_validator("name")
@classmethod
def validate_name(cls, v: str) -> str:
"""验证名称(防止 XSS"""
# 移除危险的 HTML 标签
v = re.sub(r"<script.*?</script>", "", v, flags=re.IGNORECASE | re.DOTALL)
v = re.sub(r"<iframe.*?</iframe>", "", v, flags=re.IGNORECASE | re.DOTALL)
v = re.sub(r"javascript:", "", v, flags=re.IGNORECASE)
v = re.sub(r"on\w+\s*=", "", v, flags=re.IGNORECASE)
return v.strip()
@field_validator("note")
@classmethod
def validate_note(cls, v: Optional[str]) -> Optional[str]:
"""验证备注(防止 XSS"""
if v is None:
return v
# 移除危险的 HTML 标签
v = re.sub(r"<script.*?</script>", "", v, flags=re.IGNORECASE | re.DOTALL)
v = re.sub(r"<iframe.*?</iframe>", "", v, flags=re.IGNORECASE | re.DOTALL)
v = re.sub(r"javascript:", "", v, flags=re.IGNORECASE)
v = re.sub(r"on\w+\s*=", "", v, flags=re.IGNORECASE)
return v.strip()
class EndpointAPIKeyUpdate(BaseModel):
"""更新 Endpoint API Key"""
api_key: Optional[str] = Field(
default=None, min_length=10, max_length=500, description="API Key将自动加密"
)
name: Optional[str] = Field(default=None, min_length=1, max_length=100, description="密钥名称")
rate_multiplier: Optional[float] = Field(default=None, ge=0.01, description="成本倍率")
internal_priority: Optional[int] = Field(
default=None, description="Endpoint 内部优先级(提供商优先模式,数字越小越优先)"
)
global_priority: Optional[int] = Field(
default=None, description="全局 Key 优先级(全局 Key 优先模式,数字越小越优先)"
)
# 注意max_concurrent=None 表示不更新,要切换为自适应模式请使用专用 API
max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数")
rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制")
daily_limit: Optional[int] = Field(default=None, ge=1, description="每日限制")
monthly_limit: Optional[int] = Field(default=None, ge=1, description="每月限制")
allowed_models: Optional[List[str]] = Field(default=None, description="允许使用的模型列表")
capabilities: Optional[Dict[str, bool]] = Field(
default=None, description="Key 能力标签,如 {'cache_1h': true, 'context_1m': true}"
)
cache_ttl_minutes: Optional[int] = Field(
default=None, ge=0, le=60, description="缓存 TTL分钟0=禁用"
)
max_probe_interval_minutes: Optional[int] = Field(
default=None, ge=2, le=32, description="熔断探测间隔(分钟),范围 2-32"
)
is_active: Optional[bool] = Field(default=None, description="是否启用")
note: Optional[str] = Field(default=None, max_length=500, description="备注说明")
@field_validator("api_key")
@classmethod
def validate_api_key(cls, v: Optional[str]) -> Optional[str]:
"""验证 API Key 安全性"""
if v is None:
return v
v = v.strip()
if len(v) < 10:
raise ValueError("API Key 长度不能少于 10 个字符")
dangerous_chars = ["'", '"', ";", "--", "/*", "*/", "<", ">"]
for char in dangerous_chars:
if char in v:
raise ValueError(f"API Key 包含非法字符: {char}")
return v
@field_validator("name")
@classmethod
def validate_name(cls, v: Optional[str]) -> Optional[str]:
"""验证名称(防止 XSS"""
if v is None:
return v
v = re.sub(r"<script.*?</script>", "", v, flags=re.IGNORECASE | re.DOTALL)
v = re.sub(r"<iframe.*?</iframe>", "", v, flags=re.IGNORECASE | re.DOTALL)
v = re.sub(r"javascript:", "", v, flags=re.IGNORECASE)
v = re.sub(r"on\w+\s*=", "", v, flags=re.IGNORECASE)
return v.strip()
@field_validator("note")
@classmethod
def validate_note(cls, v: Optional[str]) -> Optional[str]:
"""验证备注(防止 XSS"""
if v is None:
return v
v = re.sub(r"<script.*?</script>", "", v, flags=re.IGNORECASE | re.DOTALL)
v = re.sub(r"<iframe.*?</iframe>", "", v, flags=re.IGNORECASE | re.DOTALL)
v = re.sub(r"javascript:", "", v, flags=re.IGNORECASE)
v = re.sub(r"on\w+\s*=", "", v, flags=re.IGNORECASE)
return v.strip()
class EndpointAPIKeyResponse(BaseModel):
"""Endpoint API Key 响应"""
id: str
endpoint_id: str
# Key 信息(脱敏)
api_key_masked: str = Field(..., description="脱敏后的 Key")
api_key_plain: Optional[str] = Field(default=None, description="完整的 Key")
name: str = Field(..., description="密钥名称")
# 成本计算
rate_multiplier: float = Field(default=1.0, description="成本倍率")
# 优先级和限制
internal_priority: int = Field(default=50, description="Endpoint 内部优先级")
global_priority: Optional[int] = Field(default=None, description="全局 Key 优先级")
max_concurrent: Optional[int] = None
rate_limit: Optional[int] = None
daily_limit: Optional[int] = None
monthly_limit: Optional[int] = None
allowed_models: Optional[List[str]] = None
capabilities: Optional[Dict[str, bool]] = Field(
default=None, description="Key 能力标签"
)
# 缓存与熔断配置
cache_ttl_minutes: int = Field(default=5, description="缓存 TTL分钟0=禁用")
max_probe_interval_minutes: int = Field(default=32, description="熔断探测间隔(分钟)")
# 健康度
health_score: float
consecutive_failures: int
last_failure_at: Optional[datetime] = None
# 熔断器状态(滑动窗口 + 半开模式)
circuit_breaker_open: bool = Field(default=False, description="熔断器是否打开")
circuit_breaker_open_at: Optional[datetime] = Field(default=None, description="熔断器打开时间")
next_probe_at: Optional[datetime] = Field(default=None, description="下次进入半开状态时间")
half_open_until: Optional[datetime] = Field(default=None, description="半开状态结束时间")
half_open_successes: Optional[int] = Field(default=0, description="半开状态成功次数")
half_open_failures: Optional[int] = Field(default=0, description="半开状态失败次数")
request_results_window: Optional[List[dict]] = Field(None, description="请求结果滑动窗口")
# 使用统计
request_count: int
success_count: int
error_count: int
success_rate: float = Field(default=0.0, description="成功率")
avg_response_time_ms: float = Field(default=0.0, description="平均响应时间(毫秒)")
# 状态
is_active: bool
# 自适应并发信息
is_adaptive: bool = Field(default=False, description="是否为自适应模式max_concurrent=NULL")
learned_max_concurrent: Optional[int] = Field(None, description="学习到的并发限制")
effective_limit: Optional[int] = Field(None, description="当前有效限制")
# 滑动窗口利用率采样
utilization_samples: Optional[List[dict]] = Field(None, description="利用率采样窗口")
last_probe_increase_at: Optional[datetime] = Field(None, description="上次探测性扩容时间")
concurrent_429_count: Optional[int] = None
rpm_429_count: Optional[int] = None
last_429_at: Optional[datetime] = None
last_429_type: Optional[str] = None
# 备注
note: Optional[str] = None
# 时间戳
last_used_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
2025-12-10 20:52:44 +08:00
# ========== 健康监控相关 ==========
class HealthStatusResponse(BaseModel):
"""健康状态响应(仅 Key 级别)"""
# Key 健康状态
key_id: str
key_health_score: float
key_consecutive_failures: int
key_last_failure_at: Optional[datetime] = None
key_is_active: bool
key_statistics: Optional[Dict[str, Any]] = None
# 熔断器状态(滑动窗口 + 半开模式)
circuit_breaker_open: bool = False
circuit_breaker_open_at: Optional[datetime] = None
next_probe_at: Optional[datetime] = None
half_open_until: Optional[datetime] = None
half_open_successes: int = 0
half_open_failures: int = 0
class HealthSummaryResponse(BaseModel):
"""健康状态摘要"""
endpoints: Dict[str, int] = Field(..., description="Endpoint 统计 (total, active, unhealthy)")
keys: Dict[str, int] = Field(..., description="Key 统计 (total, active, unhealthy)")
# ========== 并发控制相关 ==========
class ConcurrencyStatusResponse(BaseModel):
"""并发状态响应"""
endpoint_id: Optional[str] = None
endpoint_current_concurrency: int = Field(default=0, description="Endpoint 当前并发数")
endpoint_max_concurrent: Optional[int] = Field(default=None, description="Endpoint 最大并发数")
key_id: Optional[str] = None
key_current_concurrency: int = Field(default=0, description="Key 当前并发数")
key_max_concurrent: Optional[int] = Field(default=None, description="Key 最大并发数")
class ResetConcurrencyRequest(BaseModel):
"""重置并发计数请求"""
endpoint_id: Optional[str] = Field(default=None, description="Endpoint ID可选")
key_id: Optional[str] = Field(default=None, description="Key ID可选")
class KeyPriorityItem(BaseModel):
"""单个 Key 优先级项"""
key_id: str = Field(..., description="Key ID")
internal_priority: int = Field(..., ge=0, description="Endpoint 内部优先级(数字越小越优先)")
class BatchUpdateKeyPriorityRequest(BaseModel):
"""批量更新 Key 优先级请求"""
priorities: List[KeyPriorityItem] = Field(..., min_length=1, description="Key 优先级列表")
# ========== 提供商摘要(增强版) ==========
class ProviderUpdateRequest(BaseModel):
"""Provider 基础配置更新请求"""
display_name: Optional[str] = Field(None, min_length=1, max_length=100)
description: Optional[str] = None
website: Optional[str] = Field(None, max_length=500, description="主站网站")
priority: Optional[int] = None
weight: Optional[float] = Field(None, gt=0)
provider_priority: Optional[int] = Field(None, description="提供商优先级(数字越小越优先)")
is_active: Optional[bool] = None
billing_type: Optional[str] = Field(
None, description="计费类型monthly_quota/pay_as_you_go/free_tier"
)
monthly_quota_usd: Optional[float] = Field(None, ge=0, description="订阅配额(美元)")
quota_reset_day: Optional[int] = Field(None, ge=1, le=31, description="配额重置日1-31")
quota_expires_at: Optional[datetime] = Field(None, description="配额过期时间")
rpm_limit: Optional[int] = Field(
None, ge=0, description="每分钟请求数限制NULL=无限制0=禁止请求)"
)
class ProviderWithEndpointsSummary(BaseModel):
"""Provider 和 Endpoints 摘要"""
# Provider 基本信息
id: str
name: str
display_name: str
description: Optional[str] = None
website: Optional[str] = None
provider_priority: int = Field(default=100, description="提供商优先级(数字越小越优先)")
is_active: bool
# 计费相关字段
billing_type: Optional[str] = None
monthly_quota_usd: Optional[float] = None
monthly_used_usd: Optional[float] = None
quota_reset_day: Optional[int] = Field(default=None, description="配额重置周期(天数)")
quota_last_reset_at: Optional[datetime] = Field(default=None, description="当前周期开始时间")
quota_expires_at: Optional[datetime] = Field(default=None, description="配额过期时间")
# RPM 限制
rpm_limit: Optional[int] = Field(
default=None, description="每分钟请求数限制NULL=无限制0=禁止请求)"
)
rpm_used: Optional[int] = Field(default=None, description="当前分钟已用请求数")
rpm_reset_at: Optional[datetime] = Field(default=None, description="RPM 重置时间")
# Endpoint 统计
total_endpoints: int = Field(default=0, description="总 Endpoint 数量")
active_endpoints: int = Field(default=0, description="活跃 Endpoint 数量")
# Key 统计(所有 Endpoints 的 Keys
total_keys: int = Field(default=0, description="总 Key 数量")
active_keys: int = Field(default=0, description="活跃 Key 数量")
# Model 统计
total_models: int = Field(default=0, description="总模型数量")
active_models: int = Field(default=0, description="活跃模型数量")
# API 格式列表
api_formats: List[str] = Field(default=[], description="支持的 API 格式列表")
# Endpoint 健康度详情
endpoint_health_details: List[Dict[str, Any]] = Field(
default=[],
description="每个 Endpoint 的健康度详情 [{api_format: str, health_score: float, is_active: bool}]",
)
# 健康度统计
avg_health_score: float = Field(default=1.0, description="平均健康度")
unhealthy_endpoints: int = Field(
default=0, description="不健康的端点数量health_score < 0.5"
)
# 时间戳
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
2025-12-10 20:52:44 +08:00
# ========== 健康监控可视化模型 ==========
class EndpointHealthEvent(BaseModel):
"""单个端点的请求事件"""
timestamp: datetime
status: str
status_code: Optional[int] = None
latency_ms: Optional[int] = None
error_type: Optional[str] = None
error_message: Optional[str] = None
class EndpointHealthMonitor(BaseModel):
"""端点健康监控信息"""
endpoint_id: str
api_format: str
is_active: bool
total_attempts: int
success_count: int
failed_count: int
skipped_count: int
success_rate: float = Field(default=1.0, description="最近事件窗口的成功率")
last_event_at: Optional[datetime] = None
events: List[EndpointHealthEvent] = Field(default_factory=list)
class ProviderEndpointHealthMonitorResponse(BaseModel):
"""Provider 下所有端点的健康监控"""
provider_id: str
provider_name: str
generated_at: datetime
endpoints: List[EndpointHealthMonitor] = Field(default_factory=list)
class ApiFormatHealthMonitor(BaseModel):
"""按 API 格式聚合的健康监控信息"""
api_format: str
total_attempts: int
success_count: int
failed_count: int
skipped_count: int
success_rate: float = Field(default=1.0, description="最近事件窗口的成功率")
provider_count: int = Field(default=0, description="参与统计的 Provider 数量")
key_count: int = Field(default=0, description="参与统计的 API Key 数量")
last_event_at: Optional[datetime] = None
events: List[EndpointHealthEvent] = Field(default_factory=list)
timeline: List[str] = Field(
default_factory=list,
description="Usage 表生成的健康时间线healthy/warning/unhealthy/unknown",
)
time_range_start: Optional[datetime] = Field(
default=None, description="时间线所覆盖区间的开始时间"
)
time_range_end: Optional[datetime] = Field(
default=None, description="时间线所覆盖区间的结束时间"
)
class ApiFormatHealthMonitorResponse(BaseModel):
"""所有 API 格式的健康监控汇总"""
generated_at: datetime
formats: List[ApiFormatHealthMonitor] = Field(default_factory=list)
# ========== 公开健康监控模型(不含敏感信息) ==========
class PublicHealthEvent(BaseModel):
"""公开版单个请求事件(不含敏感信息如 provider_id、key_id"""
timestamp: datetime
status: str
status_code: Optional[int] = None
latency_ms: Optional[int] = None
error_type: Optional[str] = None
class PublicApiFormatHealthMonitor(BaseModel):
"""公开版 API 格式健康监控信息(不含敏感信息)"""
api_format: str
api_path: str = Field(default="/", description="该 API 格式的本站请求路径")
total_attempts: int = Field(default=0, description="总请求次数")
success_count: int = Field(default=0, description="成功次数")
failed_count: int = Field(default=0, description="失败次数")
skipped_count: int = Field(default=0, description="跳过次数")
success_rate: float = Field(default=1.0, description="成功率")
last_event_at: Optional[datetime] = None
events: List[PublicHealthEvent] = Field(default_factory=list, description="事件列表")
timeline: List[str] = Field(
default_factory=list,
description="Usage 表生成的健康时间线healthy/warning/unhealthy/unknown",
)
time_range_start: Optional[datetime] = Field(
default=None, description="时间线覆盖区间开始时间"
)
time_range_end: Optional[datetime] = Field(
default=None, description="时间线覆盖区间结束时间"
)
class PublicApiFormatHealthMonitorResponse(BaseModel):
"""公开版健康监控汇总(不含敏感信息)"""
generated_at: datetime
formats: List[PublicApiFormatHealthMonitor] = Field(default_factory=list)