feat: add health-aware P2C selector as default

Score is success-rate EWMA over (1 + inflight) with a 0.05 exploration
floor so quarantined sources can prove recovery; EWMA climb doubles as
slow-start. New optional OutcomeAwareSelector port feeds attempt
outcomes.
This commit is contained in:
2026-07-21 09:22:37 -04:00
parent e1678e4ff5
commit e69dc05fd5
4 changed files with 141 additions and 4 deletions
+14 -4
View File
@@ -24,7 +24,12 @@ from polygateway.middleware.structured import StructuredMW
from polygateway.middleware.telemetry import TelemetryEmitter, TelemetryMW
from polygateway.pricing import PricingTable
from polygateway.providers import get_provider
from polygateway.sources import LeastInflightSelector, RoundRobinSelector, SourceCooldownMemo
from polygateway.sources import (
HealthAwareSelector,
LeastInflightSelector,
RoundRobinSelector,
SourceCooldownMemo,
)
from polygateway.transports.openai_compat import OpenAICompatTransport
from polygateway.types import ChatRequest, LLMResponse
@@ -193,6 +198,7 @@ class GatewayClient:
cache: CacheBackend | None = None,
telemetry: TelemetryRecorder | None = None,
registry: Mapping[str, ProviderProfile] | None = None,
rng: Any = random.random,
) -> GatewayClient:
"""按配置装配;显式传入的后端实例即共享(None 项按配置自建私有实例)。"""
sources = list(settings.sources)
@@ -201,7 +207,7 @@ class GatewayClient:
return cls(
scope=settings.scope,
sources=sources,
selector=_build_selector(settings.selector),
selector=_build_selector(settings.selector, rng=rng),
limiter=limiter or _build_limiter(settings, sources),
breaker=breaker or _build_breaker(settings),
transport=OpenAICompatTransport(registry=registry),
@@ -272,8 +278,12 @@ def _build_breaker(settings: GatewaySettings) -> ProviderGate:
return InMemoryGate(config=settings.breaker)
def _build_selector(name: str) -> SourceSelector:
return RoundRobinSelector() if name == "round_robin" else LeastInflightSelector()
def _build_selector(name: str, *, rng: Any = random.random) -> SourceSelector:
if name == "round_robin":
return RoundRobinSelector()
if name == "least_inflight":
return LeastInflightSelector()
return HealthAwareSelector(rng=rng)
def _build_cache(settings: GatewaySettings) -> CacheBackend | None: