refactor: 重构限流系统和健康监控,支持按 API 格式区分

- 将 adaptive_concurrency 重命名为 adaptive_rpm,从并发控制改为 RPM 控制
- 健康监控器支持按 API 格式独立管理健康度和熔断器状态
- 新增 model_permissions 模块,支持按格式配置允许的模型
- 重构前端提供商相关表单组件,新增 Collapsible UI 组件
- 新增数据库迁移脚本支持新的数据结构
This commit is contained in:
fawney19
2026-01-10 18:43:53 +08:00
parent dd2fbf4424
commit 09e0f594ff
97 changed files with 6642 additions and 4169 deletions

View File

@@ -89,24 +89,29 @@ class RequestExecutor:
try:
# 计算动态预留比例
reservation_manager = get_adaptive_reservation_manager()
# 获取当前并发数用于计算负载
# 获取当前 RPM 计数用于计算负载
# 注意key 侧返回的是 RPM 计数(不会在请求结束时减少,靠 TTL 过期)
try:
_, current_key_concurrent = await self.concurrency_manager.get_current_concurrency(
_, current_key_rpm = await self.concurrency_manager.get_current_concurrency(
endpoint_id=endpoint.id,
key_id=key.id,
)
except Exception as e:
logger.debug(f"获取并发数失败(用于预留计算): {e}")
current_key_concurrent = 0
logger.debug(f"获取 RPM 计数失败(用于预留计算): {e}")
current_key_rpm = 0
# 获取有效的并发限制(自适应或固定)
effective_key_limit = (
key.learned_max_concurrent if key.max_concurrent is None else key.max_concurrent
)
# 获取有效的 RPM 限制(自适应或固定)
if key.rpm_limit is None:
# 自适应模式:优先使用学习值,否则使用默认初始限制,避免无限制打爆上游
from src.config.constants import RPMDefaults
effective_key_limit = int(key.learned_rpm_limit or RPMDefaults.INITIAL_LIMIT)
else:
effective_key_limit = int(key.rpm_limit)
reservation_result = reservation_manager.calculate_reservation(
key=key,
current_concurrent=current_key_concurrent,
current_usage=current_key_rpm,
effective_limit=effective_key_limit,
)
dynamic_reservation_ratio = reservation_result.ratio
@@ -115,24 +120,22 @@ class RequestExecutor:
f"ratio={dynamic_reservation_ratio:.0%}, phase={reservation_result.phase}, "
f"confidence={reservation_result.confidence:.0%}")
async with self.concurrency_manager.concurrency_guard(
endpoint_id=endpoint.id,
endpoint_max_concurrent=endpoint.max_concurrent,
async with self.concurrency_manager.rpm_guard(
key_id=key.id,
key_max_concurrent=effective_key_limit,
key_rpm_limit=effective_key_limit,
is_cached_user=is_cached_user,
cache_reservation_ratio=dynamic_reservation_ratio,
):
# 获取当前 RPM 计数guard 内再次获取以获得最新值)
try:
_, key_concurrent = await self.concurrency_manager.get_current_concurrency(
endpoint_id=endpoint.id,
key_rpm_count = await self.concurrency_manager.get_key_rpm_count(
key_id=key.id,
)
except Exception as e:
logger.debug(f"获取并发数失败guard 内): {e}")
key_concurrent = None
logger.debug(f"获取 RPM 计数失败guard 内): {e}")
key_rpm_count = None
context.concurrent_requests = key_concurrent
context.concurrent_requests = key_rpm_count # 用于记录,实际是 RPM 计数
context.start_time = time.time()
response = await request_func(provider, endpoint, key)
@@ -142,15 +145,18 @@ class RequestExecutor:
health_monitor.record_success(
db=self.db,
key_id=key.id,
api_format=(
api_format.value if isinstance(api_format, APIFormat) else api_format
),
response_time_ms=context.elapsed_ms,
)
# 自适应模式:max_concurrent = NULL
if key.max_concurrent is None and key_concurrent is not None:
# 自适应模式:rpm_limit = NULL
if key.rpm_limit is None and key_rpm_count is not None:
self.adaptive_manager.handle_success(
db=self.db,
key=key,
current_concurrent=key_concurrent,
current_rpm=key_rpm_count,
)
# 根据是否为流式请求,标记不同状态
@@ -162,7 +168,7 @@ class RequestExecutor:
db=self.db,
candidate_id=candidate_id,
status_code=200,
concurrent_requests=key_concurrent,
concurrent_requests=key_rpm_count,
)
else:
# 非流式请求:标记为 success 状态
@@ -171,7 +177,7 @@ class RequestExecutor:
candidate_id=candidate_id,
status_code=200,
latency_ms=context.elapsed_ms,
concurrent_requests=key_concurrent,
concurrent_requests=key_rpm_count,
extra_data={
"is_cached_user": is_cached_user,
"model_name": model_name,

View File

@@ -289,10 +289,10 @@ class RequestResult:
status_code = 500
error_type = "internal_error"
# 构建错误消息:优先使用上游响应作为主要错误信息
if isinstance(exception, ProviderNotAvailableException) and exception.upstream_response:
error_message = exception.upstream_response
else:
# 构建错误消息:优先使用友好的 message 属性
# upstream_response 仅用于调试/链路追踪,不作为客户端错误消息
error_message = getattr(exception, "message", None)
if not error_message or not isinstance(error_message, str):
error_message = str(exception)
return cls(