mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
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)
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
"""Pool scheduling dimension registry and evaluation helpers.
|
||||
|
||||
This module keeps pool scheduling scoring isolated from API layer code.
|
||||
Callers build a :class:`PoolSchedulingSnapshot` and evaluate it against
|
||||
registered dimensions to obtain a normalized summary.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Protocol
|
||||
|
||||
PoolDimensionStatus = str # ok / degraded / blocked
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PoolSchedulingSnapshot:
|
||||
"""Point-in-time scheduling inputs for one key."""
|
||||
|
||||
is_active: bool
|
||||
cooldown_reason: str | None
|
||||
cooldown_ttl_seconds: int | None
|
||||
circuit_breaker_open: bool
|
||||
cost_window_usage: int
|
||||
cost_limit: int | None
|
||||
cost_soft_threshold_percent: int = 80
|
||||
health_score: float = 1.0
|
||||
latency_avg_ms: float | None = None
|
||||
account_blocked: bool = False
|
||||
account_block_label: str | None = None
|
||||
account_block_reason: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PoolSchedulingDimensionResult:
|
||||
"""Evaluation output for one scheduling dimension."""
|
||||
|
||||
code: str
|
||||
label: str
|
||||
status: PoolDimensionStatus = "ok"
|
||||
blocking: bool = False
|
||||
source: str = "pool"
|
||||
weight: int = 1
|
||||
score: float = 1.0
|
||||
detail: str | None = None
|
||||
ttl_seconds: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PoolSchedulingSummary:
|
||||
"""Merged scheduling state across all dimensions."""
|
||||
|
||||
status: str # available / degraded / blocked
|
||||
reason: str
|
||||
label: str
|
||||
candidate_eligible: bool
|
||||
blocked_count: int
|
||||
degraded_count: int
|
||||
|
||||
|
||||
class PoolSchedulingDimension(Protocol):
|
||||
"""Dimension evaluator protocol."""
|
||||
|
||||
code: str
|
||||
label: str
|
||||
source: str
|
||||
weight: int
|
||||
|
||||
def evaluate(self, snapshot: PoolSchedulingSnapshot) -> PoolSchedulingDimensionResult:
|
||||
"""Evaluate one dimension from snapshot."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _AccountStateDimension:
|
||||
code: str = "account_state"
|
||||
label: str = "账号状态"
|
||||
source: str = "policy"
|
||||
weight: int = 10
|
||||
|
||||
def evaluate(self, snapshot: PoolSchedulingSnapshot) -> PoolSchedulingDimensionResult:
|
||||
if not snapshot.account_blocked:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
)
|
||||
|
||||
blocked_label = snapshot.account_block_label or "账号异常"
|
||||
if blocked_label == "账号封禁":
|
||||
blocked_code = "account_banned"
|
||||
elif blocked_label == "工作区停用":
|
||||
blocked_code = "workspace_deactivated"
|
||||
elif blocked_label == "账号停用":
|
||||
blocked_code = "account_disabled"
|
||||
elif blocked_label == "需要验证":
|
||||
blocked_code = "account_verification"
|
||||
elif blocked_label == "访问受限":
|
||||
blocked_code = "account_forbidden"
|
||||
else:
|
||||
blocked_code = "account_blocked"
|
||||
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=blocked_code,
|
||||
label=blocked_label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="blocked",
|
||||
blocking=True,
|
||||
score=0.0,
|
||||
detail=snapshot.account_block_reason or blocked_label,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _ManualEnableDimension:
|
||||
code: str = "manual_disabled"
|
||||
label: str = "禁用"
|
||||
source: str = "manual"
|
||||
weight: int = 8
|
||||
|
||||
def evaluate(self, snapshot: PoolSchedulingSnapshot) -> PoolSchedulingDimensionResult:
|
||||
if snapshot.is_active:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
)
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="blocked",
|
||||
blocking=True,
|
||||
score=0.0,
|
||||
detail="账号被手动禁用",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _CooldownDimension:
|
||||
code: str = "cooldown"
|
||||
label: str = "冷却中"
|
||||
source: str = "pool"
|
||||
weight: int = 7
|
||||
|
||||
def evaluate(self, snapshot: PoolSchedulingSnapshot) -> PoolSchedulingDimensionResult:
|
||||
if not snapshot.cooldown_reason:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
)
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="blocked",
|
||||
blocking=True,
|
||||
score=0.0,
|
||||
detail=snapshot.cooldown_reason,
|
||||
ttl_seconds=snapshot.cooldown_ttl_seconds,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _CircuitBreakerDimension:
|
||||
code: str = "circuit_open"
|
||||
label: str = "熔断中"
|
||||
source: str = "health"
|
||||
weight: int = 6
|
||||
|
||||
def evaluate(self, snapshot: PoolSchedulingSnapshot) -> PoolSchedulingDimensionResult:
|
||||
if not snapshot.circuit_breaker_open:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
)
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="blocked",
|
||||
blocking=True,
|
||||
score=0.0,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _CostDimension:
|
||||
code: str = "cost"
|
||||
label: str = "成本"
|
||||
source: str = "pool"
|
||||
weight: int = 5
|
||||
|
||||
def evaluate(self, snapshot: PoolSchedulingSnapshot) -> PoolSchedulingDimensionResult:
|
||||
limit = snapshot.cost_limit
|
||||
usage = max(snapshot.cost_window_usage, 0)
|
||||
if limit is None or limit <= 0:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
detail=f"{usage}/-",
|
||||
)
|
||||
|
||||
ratio = usage / limit
|
||||
detail = f"{usage}/{limit}"
|
||||
if ratio >= 1.0:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code="cost_exhausted",
|
||||
label="成本超限",
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="blocked",
|
||||
blocking=True,
|
||||
score=0.0,
|
||||
detail=detail,
|
||||
)
|
||||
|
||||
soft_threshold = max(1, min(snapshot.cost_soft_threshold_percent, 100))
|
||||
if ratio * 100 >= soft_threshold:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code="cost_soft",
|
||||
label="成本接近上限",
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="degraded",
|
||||
score=0.45,
|
||||
detail=detail,
|
||||
)
|
||||
|
||||
if ratio >= 0.6:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="degraded",
|
||||
score=0.72,
|
||||
detail=detail,
|
||||
)
|
||||
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
detail=detail,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _HealthDimension:
|
||||
code: str = "health"
|
||||
label: str = "健康度"
|
||||
source: str = "health"
|
||||
weight: int = 4
|
||||
|
||||
def evaluate(self, snapshot: PoolSchedulingSnapshot) -> PoolSchedulingDimensionResult:
|
||||
score = max(0.0, min(snapshot.health_score, 1.0))
|
||||
detail = f"{score:.2f}"
|
||||
if score < 0.5:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code="health_low",
|
||||
label="健康度过低",
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="degraded",
|
||||
score=0.3,
|
||||
detail=detail,
|
||||
)
|
||||
if score < 0.8:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code="health_degraded",
|
||||
label="健康度下降",
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="degraded",
|
||||
score=0.65,
|
||||
detail=detail,
|
||||
)
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
detail=detail,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _LatencyDimension:
|
||||
code: str = "latency"
|
||||
label: str = "延迟"
|
||||
source: str = "runtime"
|
||||
weight: int = 3
|
||||
|
||||
def evaluate(self, snapshot: PoolSchedulingSnapshot) -> PoolSchedulingDimensionResult:
|
||||
latency = snapshot.latency_avg_ms
|
||||
if latency is None:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
detail="-",
|
||||
)
|
||||
|
||||
value = max(float(latency), 0.0)
|
||||
detail = f"{value:.0f}ms"
|
||||
if value >= 3000:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code="latency_high",
|
||||
label="延迟偏高",
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="degraded",
|
||||
score=0.5,
|
||||
detail=detail,
|
||||
)
|
||||
if value >= 1200:
|
||||
return PoolSchedulingDimensionResult(
|
||||
code="latency_slow",
|
||||
label="延迟较慢",
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="degraded",
|
||||
score=0.72,
|
||||
detail=detail,
|
||||
)
|
||||
return PoolSchedulingDimensionResult(
|
||||
code=self.code,
|
||||
label=self.label,
|
||||
source=self.source,
|
||||
weight=self.weight,
|
||||
status="ok",
|
||||
score=1.0,
|
||||
detail=detail,
|
||||
)
|
||||
|
||||
|
||||
_POOL_DIMENSION_REGISTRY: dict[str, PoolSchedulingDimension] = {}
|
||||
_POOL_DIMENSION_ORDER: list[str] = []
|
||||
|
||||
|
||||
def register_pool_scheduling_dimension(name: str, dimension: PoolSchedulingDimension) -> None:
|
||||
"""Register a dimension evaluator by name."""
|
||||
normalized = name.strip()
|
||||
if not normalized:
|
||||
return
|
||||
if normalized not in _POOL_DIMENSION_ORDER:
|
||||
_POOL_DIMENSION_ORDER.append(normalized)
|
||||
_POOL_DIMENSION_REGISTRY[normalized] = dimension
|
||||
|
||||
|
||||
def get_pool_scheduling_dimension(name: str) -> PoolSchedulingDimension | None:
|
||||
"""Fetch a registered dimension evaluator."""
|
||||
return _POOL_DIMENSION_REGISTRY.get(name.strip())
|
||||
|
||||
|
||||
def list_pool_scheduling_dimensions() -> tuple[str, ...]:
|
||||
"""List registered dimension names in evaluation order."""
|
||||
return tuple(_POOL_DIMENSION_ORDER)
|
||||
|
||||
|
||||
def evaluate_pool_scheduling_dimensions(
|
||||
snapshot: PoolSchedulingSnapshot,
|
||||
*,
|
||||
dimension_names: tuple[str, ...] | None = None,
|
||||
) -> list[PoolSchedulingDimensionResult]:
|
||||
"""Evaluate snapshot across all registered dimensions."""
|
||||
names = dimension_names or list_pool_scheduling_dimensions()
|
||||
results: list[PoolSchedulingDimensionResult] = []
|
||||
for name in names:
|
||||
dimension = get_pool_scheduling_dimension(name)
|
||||
if dimension is None:
|
||||
continue
|
||||
results.append(dimension.evaluate(snapshot))
|
||||
return results
|
||||
|
||||
|
||||
def summarize_pool_scheduling_dimensions(
|
||||
dimensions: list[PoolSchedulingDimensionResult],
|
||||
) -> PoolSchedulingSummary:
|
||||
"""Summarize dimension outputs into a unified scheduling state."""
|
||||
if not dimensions:
|
||||
return PoolSchedulingSummary(
|
||||
status="available",
|
||||
reason="available",
|
||||
label="可用",
|
||||
candidate_eligible=True,
|
||||
blocked_count=0,
|
||||
degraded_count=0,
|
||||
)
|
||||
|
||||
blocked = [item for item in dimensions if item.status == "blocked" or item.blocking]
|
||||
degraded = [item for item in dimensions if item.status == "degraded"]
|
||||
|
||||
if blocked:
|
||||
primary = blocked[0]
|
||||
return PoolSchedulingSummary(
|
||||
status="blocked",
|
||||
reason=primary.code,
|
||||
label=primary.label,
|
||||
candidate_eligible=False,
|
||||
blocked_count=len(blocked),
|
||||
degraded_count=len(degraded),
|
||||
)
|
||||
|
||||
if degraded:
|
||||
primary = degraded[0]
|
||||
return PoolSchedulingSummary(
|
||||
status="degraded",
|
||||
reason=primary.code,
|
||||
label=primary.label,
|
||||
candidate_eligible=True,
|
||||
blocked_count=0,
|
||||
degraded_count=len(degraded),
|
||||
)
|
||||
|
||||
return PoolSchedulingSummary(
|
||||
status="available",
|
||||
reason="available",
|
||||
label="可用",
|
||||
candidate_eligible=True,
|
||||
blocked_count=0,
|
||||
degraded_count=0,
|
||||
)
|
||||
|
||||
|
||||
def _register_default_dimensions() -> None:
|
||||
register_pool_scheduling_dimension("account_state", _AccountStateDimension())
|
||||
register_pool_scheduling_dimension("manual", _ManualEnableDimension())
|
||||
register_pool_scheduling_dimension("cooldown", _CooldownDimension())
|
||||
register_pool_scheduling_dimension("circuit", _CircuitBreakerDimension())
|
||||
register_pool_scheduling_dimension("cost", _CostDimension())
|
||||
register_pool_scheduling_dimension("latency", _LatencyDimension())
|
||||
register_pool_scheduling_dimension("health", _HealthDimension())
|
||||
|
||||
|
||||
_register_default_dimensions()
|
||||
Reference in New Issue
Block a user