mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
- 删除全部 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)
171 lines
5.7 KiB
Python
171 lines
5.7 KiB
Python
"""
|
||
请求分发器
|
||
|
||
负责执行单个候选请求
|
||
"""
|
||
|
||
from collections.abc import Callable
|
||
from typing import Any
|
||
|
||
from sqlalchemy.orm import Session
|
||
|
||
from src.core.logger import logger
|
||
from src.models.database import ApiKey
|
||
from src.services.request.candidate import RequestCandidateService
|
||
from src.services.request.executor import RequestExecutor
|
||
from src.services.scheduling.aware_scheduler import CacheAwareScheduler, ProviderCandidate
|
||
|
||
|
||
class RequestDispatcher:
|
||
"""
|
||
请求分发器 - 负责执行单个候选请求
|
||
|
||
职责:
|
||
1. 执行请求并返回结果
|
||
2. 更新候选状态(pending -> success/failed)
|
||
3. 设置缓存亲和性(成功时)
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
db: Session,
|
||
request_executor: RequestExecutor,
|
||
cache_scheduler: CacheAwareScheduler | None = None,
|
||
) -> None:
|
||
"""
|
||
初始化请求分发器
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
request_executor: 请求执行器
|
||
cache_scheduler: 缓存调度器(可选)
|
||
"""
|
||
self.db = db
|
||
self.request_executor = request_executor
|
||
self.cache_scheduler = cache_scheduler
|
||
|
||
async def dispatch(
|
||
self,
|
||
candidate: ProviderCandidate,
|
||
candidate_index: int,
|
||
retry_index: int,
|
||
candidate_record_id: str,
|
||
user_api_key: ApiKey | None,
|
||
user_id: str | None,
|
||
request_func: Callable[..., Any],
|
||
request_id: str | None,
|
||
api_format: str,
|
||
model_name: str,
|
||
affinity_key: str,
|
||
global_model_id: str,
|
||
attempt_counter: int,
|
||
max_attempts: int,
|
||
is_stream: bool = False,
|
||
) -> tuple[Any, str, str, str, str, str, int | None]:
|
||
"""
|
||
执行请求并返回结果
|
||
|
||
Args:
|
||
candidate: 候选对象
|
||
candidate_index: 候选索引
|
||
retry_index: 重试索引
|
||
candidate_record_id: 候选记录 ID
|
||
user_api_key: 用户 API Key
|
||
request_func: 请求函数
|
||
request_id: 请求 ID
|
||
api_format: API 格式
|
||
model_name: 模型名称
|
||
affinity_key: 亲和性标识符(通常为API Key ID)
|
||
global_model_id: GlobalModel ID(规范化的模型标识,用于缓存亲和性)
|
||
attempt_counter: 尝试计数
|
||
max_attempts: 最大尝试次数
|
||
is_stream: 是否为流式请求
|
||
|
||
Returns:
|
||
(response, provider_name, candidate_record_id, provider_id, endpoint_id, key_id, ttfb_ms)
|
||
|
||
Raises:
|
||
ExecutionError: 执行失败时
|
||
"""
|
||
provider = candidate.provider
|
||
endpoint = candidate.endpoint
|
||
key = candidate.key
|
||
|
||
# 显式转换为 str
|
||
provider_id = str(provider.id)
|
||
provider_name = str(provider.name)
|
||
endpoint_id = str(endpoint.id)
|
||
key_id = str(key.id)
|
||
cache_ttl_minutes = int(key.cache_ttl_minutes or 0)
|
||
provider_supports_caching = cache_ttl_minutes > 0
|
||
provider_cache_ttl_seconds: int | None = (
|
||
cache_ttl_minutes * 60 if cache_ttl_minutes > 0 else None
|
||
)
|
||
|
||
# 更新状态为 pending
|
||
RequestCandidateService.update_candidate_status(
|
||
db=self.db, candidate_id=candidate_record_id, status="pending"
|
||
)
|
||
|
||
# 执行请求
|
||
execution_result = await self.request_executor.execute(
|
||
candidate=candidate,
|
||
candidate_id=candidate_record_id,
|
||
candidate_index=candidate_index,
|
||
user_api_key=user_api_key,
|
||
user_id=user_id,
|
||
request_func=request_func,
|
||
request_id=request_id,
|
||
api_format=api_format,
|
||
model_name=model_name,
|
||
is_stream=is_stream,
|
||
)
|
||
|
||
context = execution_result.context
|
||
elapsed_ms = context.elapsed_ms or 0
|
||
|
||
# 流式请求:标记为 streaming 状态(请求尚未完成)
|
||
# 非流式请求:标记为 success 状态
|
||
# 注意:executor.execute() 内部已经处理了状态标记,这里不再重复
|
||
# 流式请求的 success 状态会在流完成后由 _record_stream_stats 方法标记
|
||
|
||
# 设置缓存亲和性
|
||
if provider_supports_caching and self.cache_scheduler is not None:
|
||
try:
|
||
await self.cache_scheduler.set_cache_affinity(
|
||
affinity_key=affinity_key,
|
||
provider_id=provider_id,
|
||
endpoint_id=endpoint_id,
|
||
key_id=key_id,
|
||
api_format=api_format,
|
||
global_model_id=global_model_id,
|
||
ttl=provider_cache_ttl_seconds,
|
||
)
|
||
except Exception as cache_exc:
|
||
logger.warning(f" [{request_id}] 设置缓存亲和性失败: {cache_exc}")
|
||
|
||
logger.debug(f" [{request_id}] 请求成功: Provider={provider_name}, 耗时={elapsed_ms}ms")
|
||
|
||
# Non-stream requests don't have first-byte telemetry in this path.
|
||
# Use elapsed latency as a conservative fallback for pool latency sampling.
|
||
ttfb_ms: int | None = None
|
||
if not is_stream:
|
||
raw_ttfb = getattr(execution_result.response, "first_byte_time_ms", None)
|
||
try:
|
||
if raw_ttfb is not None:
|
||
ttfb_ms = max(int(raw_ttfb), 0)
|
||
except (TypeError, ValueError):
|
||
ttfb_ms = None
|
||
if ttfb_ms is None and elapsed_ms >= 0:
|
||
ttfb_ms = int(elapsed_ms)
|
||
|
||
return (
|
||
execution_result.response,
|
||
provider_name,
|
||
candidate_record_id,
|
||
provider_id,
|
||
endpoint_id,
|
||
key_id,
|
||
ttfb_ms,
|
||
)
|