2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
封装请求执行逻辑,包含并发控制与链路追踪。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-03-12 16:17:22 +08:00
|
|
|
|
import asyncio
|
2026-02-15 16:32:23 +08:00
|
|
|
|
import math
|
2025-12-10 20:52:44 +08:00
|
|
|
|
import time
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from collections.abc import Callable
|
2025-12-10 20:52:44 +08:00
|
|
|
|
from dataclasses import dataclass
|
2026-01-30 03:10:21 +08:00
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from src.core.api_format.signature import make_signature_key
|
2025-12-10 20:52:44 +08:00
|
|
|
|
from src.core.exceptions import ConcurrencyLimitError
|
|
|
|
|
|
from src.core.logger import logger
|
2026-03-14 11:59:07 +08:00
|
|
|
|
from src.services.health.monitor import get_health_monitor
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from src.services.provider.format import normalize_endpoint_signature
|
2025-12-10 20:52:44 +08:00
|
|
|
|
from src.services.rate_limit.adaptive_reservation import get_adaptive_reservation_manager
|
2026-02-10 14:58:15 +08:00
|
|
|
|
from src.services.rate_limit.adaptive_rpm import get_adaptive_rpm_manager
|
2025-12-10 20:52:44 +08:00
|
|
|
|
from src.services.request.candidate import RequestCandidateService
|
2026-03-19 23:52:17 +08:00
|
|
|
|
from src.services.request.model_test_debug import (
|
|
|
|
|
|
get_candidate_model_test_debug,
|
|
|
|
|
|
merge_model_test_debug,
|
|
|
|
|
|
)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class ExecutionContext:
|
|
|
|
|
|
candidate_id: str
|
|
|
|
|
|
candidate_index: int
|
|
|
|
|
|
provider_id: str
|
|
|
|
|
|
endpoint_id: str
|
|
|
|
|
|
key_id: str
|
2026-01-30 03:10:21 +08:00
|
|
|
|
user_id: str | None
|
|
|
|
|
|
api_key_id: str | None
|
2025-12-10 20:52:44 +08:00
|
|
|
|
is_cached_user: bool
|
2026-01-30 03:10:21 +08:00
|
|
|
|
start_time: float | None = None
|
|
|
|
|
|
elapsed_ms: int | None = None
|
|
|
|
|
|
concurrent_requests: int | None = None
|
2026-02-15 16:32:23 +08:00
|
|
|
|
rpm_current: int | None = None
|
|
|
|
|
|
rpm_limit: int | None = None
|
|
|
|
|
|
rpm_available_for_new: int | None = None
|
|
|
|
|
|
reservation_ratio: float | None = None
|
|
|
|
|
|
reservation_phase: str | None = None
|
|
|
|
|
|
reservation_confidence: float | None = None
|
|
|
|
|
|
reservation_load_factor: float | None = None
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class ExecutionResult:
|
|
|
|
|
|
response: Any
|
|
|
|
|
|
context: ExecutionContext
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExecutionError(Exception):
|
|
|
|
|
|
def __init__(self, cause: Exception, context: ExecutionContext):
|
|
|
|
|
|
super().__init__(str(cause))
|
|
|
|
|
|
self.cause = cause
|
|
|
|
|
|
self.context = context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RequestExecutor:
|
2026-01-30 14:30:57 +08:00
|
|
|
|
def __init__(self, db: Session, concurrency_manager: Any, adaptive_manager: Any) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
self.db = db
|
|
|
|
|
|
self.concurrency_manager = concurrency_manager
|
|
|
|
|
|
self.adaptive_manager = adaptive_manager
|
|
|
|
|
|
|
|
|
|
|
|
async def execute(
|
|
|
|
|
|
self,
|
|
|
|
|
|
*,
|
2026-01-30 14:30:57 +08:00
|
|
|
|
candidate: Any,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
candidate_id: str,
|
|
|
|
|
|
candidate_index: int,
|
2026-03-06 21:06:45 +08:00
|
|
|
|
user_api_key: Any | None,
|
|
|
|
|
|
user_id: str | None = None,
|
2026-01-30 14:30:57 +08:00
|
|
|
|
request_func: Callable[..., Any],
|
2026-01-30 03:10:21 +08:00
|
|
|
|
request_id: str | None,
|
2026-02-01 17:28:00 +08:00
|
|
|
|
api_format: str,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
model_name: str,
|
|
|
|
|
|
is_stream: bool = False,
|
|
|
|
|
|
) -> ExecutionResult:
|
|
|
|
|
|
provider = candidate.provider
|
|
|
|
|
|
endpoint = candidate.endpoint
|
|
|
|
|
|
key = candidate.key
|
|
|
|
|
|
is_cached_user = bool(candidate.is_cached)
|
|
|
|
|
|
|
|
|
|
|
|
# 标记候选开始执行
|
|
|
|
|
|
RequestCandidateService.mark_candidate_started(
|
|
|
|
|
|
db=self.db,
|
|
|
|
|
|
candidate_id=candidate_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
context = ExecutionContext(
|
|
|
|
|
|
candidate_id=candidate_id,
|
|
|
|
|
|
candidate_index=candidate_index,
|
|
|
|
|
|
provider_id=provider.id,
|
|
|
|
|
|
endpoint_id=endpoint.id,
|
|
|
|
|
|
key_id=key.id,
|
2026-03-06 21:06:45 +08:00
|
|
|
|
user_id=user_id if user_id is not None else getattr(user_api_key, "user_id", None),
|
|
|
|
|
|
api_key_id=getattr(user_api_key, "id", None),
|
2025-12-10 20:52:44 +08:00
|
|
|
|
is_cached_user=is_cached_user,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 计算动态预留比例
|
|
|
|
|
|
reservation_manager = get_adaptive_reservation_manager()
|
2026-01-10 18:43:53 +08:00
|
|
|
|
# 获取当前 RPM 计数用于计算负载
|
|
|
|
|
|
# 注意:key 侧返回的是 RPM 计数(不会在请求结束时减少,靠 TTL 过期)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
try:
|
2026-02-02 21:16:28 +08:00
|
|
|
|
current_key_rpm = await self.concurrency_manager.get_key_rpm_count(
|
2025-12-10 20:52:44 +08:00
|
|
|
|
key_id=key.id,
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
2026-02-15 16:32:23 +08:00
|
|
|
|
logger.debug("获取 RPM 计数失败(用于预留计算): {}", e)
|
2026-01-10 18:43:53 +08:00
|
|
|
|
current_key_rpm = 0
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2026-02-15 16:32:23 +08:00
|
|
|
|
# 在获取 guard 之前记录当前 RPM 计数,便于并发拒绝场景落库
|
|
|
|
|
|
context.concurrent_requests = current_key_rpm
|
|
|
|
|
|
context.rpm_current = current_key_rpm
|
|
|
|
|
|
|
2026-01-10 18:43:53 +08:00
|
|
|
|
# 获取有效的 RPM 限制(自适应或固定)
|
2026-02-10 14:58:15 +08:00
|
|
|
|
effective_key_limit = get_adaptive_rpm_manager().get_effective_limit(key)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
reservation_result = reservation_manager.calculate_reservation(
|
|
|
|
|
|
key=key,
|
2026-01-10 18:43:53 +08:00
|
|
|
|
current_usage=current_key_rpm,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
effective_limit=effective_key_limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
dynamic_reservation_ratio = reservation_result.ratio
|
|
|
|
|
|
|
2026-02-15 16:32:23 +08:00
|
|
|
|
context.rpm_limit = effective_key_limit
|
|
|
|
|
|
context.reservation_ratio = dynamic_reservation_ratio
|
|
|
|
|
|
context.reservation_phase = reservation_result.phase
|
|
|
|
|
|
context.reservation_confidence = reservation_result.confidence
|
|
|
|
|
|
context.reservation_load_factor = reservation_result.load_factor
|
|
|
|
|
|
|
|
|
|
|
|
if effective_key_limit is not None and not is_cached_user:
|
|
|
|
|
|
context.rpm_available_for_new = max(
|
|
|
|
|
|
1, math.floor(effective_key_limit * (1 - dynamic_reservation_ratio))
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-22 01:48:56 +08:00
|
|
|
|
logger.debug(
|
2026-02-15 16:32:23 +08:00
|
|
|
|
"[Executor] 动态预留: key={}..., ratio={:.0%}, phase={}, confidence={:.0%}",
|
|
|
|
|
|
key.id[:8],
|
|
|
|
|
|
dynamic_reservation_ratio,
|
|
|
|
|
|
reservation_result.phase,
|
|
|
|
|
|
reservation_result.confidence,
|
2026-01-22 01:48:56 +08:00
|
|
|
|
)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2026-01-10 18:43:53 +08:00
|
|
|
|
async with self.concurrency_manager.rpm_guard(
|
2025-12-10 20:52:44 +08:00
|
|
|
|
key_id=key.id,
|
2026-01-10 18:43:53 +08:00
|
|
|
|
key_rpm_limit=effective_key_limit,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
is_cached_user=is_cached_user,
|
|
|
|
|
|
cache_reservation_ratio=dynamic_reservation_ratio,
|
|
|
|
|
|
):
|
2026-01-10 18:43:53 +08:00
|
|
|
|
# 获取当前 RPM 计数(guard 内再次获取以获得最新值)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
try:
|
2026-01-10 18:43:53 +08:00
|
|
|
|
key_rpm_count = await self.concurrency_manager.get_key_rpm_count(
|
2025-12-10 20:52:44 +08:00
|
|
|
|
key_id=key.id,
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
2026-02-15 16:32:23 +08:00
|
|
|
|
logger.debug("获取 RPM 计数失败(guard 内): {}", e)
|
2026-01-10 18:43:53 +08:00
|
|
|
|
key_rpm_count = None
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2026-02-15 16:32:23 +08:00
|
|
|
|
if key_rpm_count is not None:
|
|
|
|
|
|
context.concurrent_requests = key_rpm_count # 用于记录,实际是 RPM 计数
|
2025-12-10 20:52:44 +08:00
|
|
|
|
context.start_time = time.time()
|
|
|
|
|
|
|
2026-01-13 16:04:15 +08:00
|
|
|
|
response = await request_func(provider, endpoint, key, candidate)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
context.elapsed_ms = int((time.time() - context.start_time) * 1000)
|
|
|
|
|
|
|
2026-02-01 17:28:00 +08:00
|
|
|
|
fam = str(getattr(endpoint, "api_family", "")).strip().lower()
|
|
|
|
|
|
kind = str(getattr(endpoint, "endpoint_kind", "")).strip().lower()
|
|
|
|
|
|
provider_format_str = make_signature_key(fam, kind) if fam and kind else ""
|
|
|
|
|
|
client_format_str = normalize_endpoint_signature(api_format)
|
|
|
|
|
|
health_format = provider_format_str or client_format_str
|
2026-01-22 01:48:56 +08:00
|
|
|
|
|
2026-03-12 16:17:22 +08:00
|
|
|
|
await asyncio.to_thread(
|
2026-03-14 11:59:07 +08:00
|
|
|
|
get_health_monitor().record_success,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
db=self.db,
|
|
|
|
|
|
key_id=key.id,
|
2026-01-22 01:48:56 +08:00
|
|
|
|
api_format=health_format,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
response_time_ms=context.elapsed_ms,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-10 18:43:53 +08:00
|
|
|
|
# 自适应模式:rpm_limit = NULL
|
|
|
|
|
|
if key.rpm_limit is None and key_rpm_count is not None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
self.adaptive_manager.handle_success(
|
|
|
|
|
|
db=self.db,
|
|
|
|
|
|
key=key,
|
2026-01-10 18:43:53 +08:00
|
|
|
|
current_rpm=key_rpm_count,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 根据是否为流式请求,标记不同状态
|
|
|
|
|
|
if is_stream:
|
|
|
|
|
|
# 流式请求:标记为 streaming 状态
|
|
|
|
|
|
# 此时连接已建立但流传输尚未完成
|
|
|
|
|
|
# success 状态会在流完成后由 _record_stream_stats 方法标记
|
|
|
|
|
|
RequestCandidateService.mark_candidate_streaming(
|
|
|
|
|
|
db=self.db,
|
|
|
|
|
|
candidate_id=candidate_id,
|
2026-01-10 18:43:53 +08:00
|
|
|
|
concurrent_requests=key_rpm_count,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 非流式请求:标记为 success 状态
|
2026-02-11 03:16:53 +08:00
|
|
|
|
from src.services.proxy_node.resolver import (
|
|
|
|
|
|
resolve_effective_proxy,
|
2026-03-12 13:59:21 +08:00
|
|
|
|
resolve_proxy_info_async,
|
2026-02-11 03:16:53 +08:00
|
|
|
|
)
|
2026-02-08 01:18:00 +08:00
|
|
|
|
|
2026-02-11 03:16:53 +08:00
|
|
|
|
_eff_proxy = resolve_effective_proxy(
|
|
|
|
|
|
getattr(provider, "proxy", None), getattr(key, "proxy", None)
|
|
|
|
|
|
)
|
2026-02-08 01:18:00 +08:00
|
|
|
|
_extra: dict[str, Any] = {
|
|
|
|
|
|
"is_cached_user": is_cached_user,
|
|
|
|
|
|
"model_name": model_name,
|
|
|
|
|
|
"api_format": api_format,
|
|
|
|
|
|
}
|
2026-03-12 13:59:21 +08:00
|
|
|
|
_pi = await resolve_proxy_info_async(_eff_proxy)
|
2026-02-08 01:18:00 +08:00
|
|
|
|
if _pi:
|
|
|
|
|
|
_extra["proxy"] = _pi
|
2026-03-19 23:52:17 +08:00
|
|
|
|
_extra = (
|
|
|
|
|
|
merge_model_test_debug(
|
|
|
|
|
|
_extra,
|
|
|
|
|
|
get_candidate_model_test_debug(candidate),
|
|
|
|
|
|
)
|
|
|
|
|
|
or _extra
|
|
|
|
|
|
)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
RequestCandidateService.mark_candidate_success(
|
|
|
|
|
|
db=self.db,
|
|
|
|
|
|
candidate_id=candidate_id,
|
|
|
|
|
|
status_code=200,
|
|
|
|
|
|
latency_ms=context.elapsed_ms,
|
2026-01-10 18:43:53 +08:00
|
|
|
|
concurrent_requests=key_rpm_count,
|
2026-02-08 01:18:00 +08:00
|
|
|
|
extra_data=_extra,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return ExecutionResult(response=response, context=context)
|
|
|
|
|
|
except ConcurrencyLimitError as exc:
|
|
|
|
|
|
raise ExecutionError(exc, context) from exc
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
context.elapsed_ms = (
|
|
|
|
|
|
int((time.time() - context.start_time) * 1000)
|
|
|
|
|
|
if context.start_time is not None
|
|
|
|
|
|
else None
|
|
|
|
|
|
)
|
|
|
|
|
|
raise ExecutionError(exc, context) from exc
|