feat: add rate-channel breaker config and health_aware default

Threshold auto-raise now uses per-source concurrency only (the M2
global-concurrency formula neutered the breaker at scale). Four new
optional keys: MIN_CALLS, FAIL_RATE, WINDOW_S, MAX_COOLDOWN_S.
This commit is contained in:
2026-07-21 08:49:36 -04:00
parent 8e2673487a
commit 72b25724d8
3 changed files with 83 additions and 10 deletions
+42 -2
View File
@@ -126,8 +126,15 @@ class TestResilienceKeys:
GatewaySettings.from_env("LLM", env=_env(**{"LLM__BREAKER__PROBE_TTL_S": "45"}))
def test_selector_and_quota_full(self):
# M2.5: 缺省选源改 health_aware(生产级默认);显式配置者不变
s = GatewaySettings.from_env("LLM", env=_env())
assert s.selector == "round_robin" and s.quota_full == "wait"
assert s.selector == "health_aware" and s.quota_full == "wait"
assert (
GatewaySettings.from_env(
"LLM", env=_env(**{"LLM__SELECTOR": "round_robin"})
).selector
== "round_robin"
)
s2 = GatewaySettings.from_env(
"LLM", env=_env(**{"LLM__SELECTOR": "least_inflight", "LLM__QUOTA_FULL": "fail_fast"})
)
@@ -172,9 +179,42 @@ class TestAssemblyGuards:
def test_effective_breaker_threshold_auto_raised(self):
env = _env(**{"LLM__QWEN__1__MAX_CONCURRENCY": "8"})
s = GatewaySettings.from_env("LLM", env=env)
# 有效阈值 = max(配置值 5, 并发 8 × 2) = 16(.env 注释约定入库)
# 有效阈值 = max(配置值 5, 源级并发 8 × 2) = 16(M2.5: 抬升只看源级)
assert s.breaker.fail_threshold == 16
def test_breaker_threshold_not_raised_by_global_concurrency(self):
# M2.5 病灶 2 回归: 全局并发不再抬升阈值(M2 曾 max(5, 100×2)=200 使熔断失灵)
env = _env(**{"LLM__GLOBAL__MAX_CONCURRENCY": "100", "LLM__GLOBAL__RPM": "600"})
s = GatewaySettings.from_env("LLM", env=env)
assert s.breaker.fail_threshold == 5
def test_breaker_rate_channel_defaults_and_overrides(self):
# M2.5 失败率通道参数: 库缺省(韧性参数缺省先例)与显式覆盖
s = GatewaySettings.from_env("LLM", env=_env())
assert s.breaker.min_calls == 10
assert s.breaker.fail_rate == pytest.approx(0.6)
assert s.breaker.window_s == pytest.approx(60.0)
assert s.breaker.max_cooldown_s == pytest.approx(300.0) # max(300, cooldown 60)
env = _env(
**{
"LLM__BREAKER__MIN_CALLS": "20",
"LLM__BREAKER__FAIL_RATE": "0.5",
"LLM__BREAKER__WINDOW_S": "30",
"LLM__BREAKER__MAX_COOLDOWN_S": "600",
"LLM_CIRCUIT_BREAKER_COOLDOWN": "400",
}
)
s2 = GatewaySettings.from_env("LLM", env=env)
assert s2.breaker.min_calls == 20 and s2.breaker.fail_rate == pytest.approx(0.5)
assert s2.breaker.window_s == pytest.approx(30.0)
assert s2.breaker.max_cooldown_s == pytest.approx(600.0)
def test_breaker_max_cooldown_floor_follows_cooldown(self):
# 缺省封顶 = max(300, cooldown): 冷却 400s 时封顶随之 400s
env = _env(**{"LLM_CIRCUIT_BREAKER_COOLDOWN": "400"})
s = GatewaySettings.from_env("LLM", env=env)
assert s.breaker.max_cooldown_s == pytest.approx(400.0)
def test_redis_governance_backend_requires_url(self):
"""M2 解禁 redis 后端: 取 redis 时 REDIS_URL 必在,缺则装配报错。"""
env = _env(