Files
Aether/_deprecated_py_src/services/orchestration/request_dispatcher.py
fawney19 1d9c77522a 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)
2026-04-03 16:26:16 +08:00

171 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
请求分发器
负责执行单个候选请求
"""
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,
)