feat(proxy): T-X2 上游有序降级链 + failover 三元组如实上报(采纳 cortiq tier 链)

- routes._fallback_chain:主条目之外的启用真实后端按(档位升序,单价和升序)
  构成有序候选链(≤3),排除 mock/停用/同模型,仅作 D-P4 首 token 前 failover
- _stream_response/_json_response 接入链式候选(原 [entry,entry] 同条目重试升级为真降级链)
- upstream.stream:failover 发生即记 sink[upstream_fallback] 与模块级统计;
  JSON 路径经 X-Upstream-Fallback/Original/Used/Reason 四头如实上报
  (流式路径头已发出不可追溯,由统计聚合暴露)
- /proxy/admin/stats 新增 upstream_failover 块(total + recent 20 条)

pytest 447 passed(T-X1 后 443 + 4)
This commit is contained in:
tzt
2026-09-18 22:35:53 +08:00
parent aa7cb0704c
commit a043493548
5 changed files with 230 additions and 21 deletions
+10 -10
View File
@@ -77,9 +77,9 @@ def test_budget_mode_missing_student_normal(tmp_path):
assert led.budget_mode(999, 100, ts=1789874000.0) == "normal"
def _make_pool() -> PoolStore:
def _make_pool(tmp_path) -> PoolStore:
import gateway.model_pool as mp
store = PoolStore() # 不落盘(path=None 仅内存)
store = PoolStore(path=tmp_path / "pool.json") # 显式隔离,防污染真实池文件
mp.reset_pool()
store.upsert({"id": "p1", "name": "旗舰", "tier": "premium", "backend": "openai",
"base_url": "https://api.example.com", "model": "big-x",
@@ -93,30 +93,30 @@ def _make_pool() -> PoolStore:
return store
def test_downgrade_optimize_one_tier():
pool = _make_pool()
def test_downgrade_optimize_one_tier(tmp_path):
pool = _make_pool(tmp_path)
premium = pool.find_by_model("big-x")
down = _downgrade_entry(pool, premium, "optimize")
assert down is not None and down["tier"] == "budget"
def test_downgrade_cheap_to_lowest():
pool = _make_pool()
def test_downgrade_cheap_to_lowest(tmp_path):
pool = _make_pool(tmp_path)
premium = pool.find_by_model("big-x")
down = _downgrade_entry(pool, premium, "cheap")
assert down is not None and down["tier"] == "local"
def test_downgrade_stops_at_local():
def test_downgrade_stops_at_local(tmp_path):
"""已在最低档:cheap/optimize 均不再降(杜绝反向升档)。"""
pool = _make_pool()
pool = _make_pool(tmp_path)
local = pool.find_by_model("qwen-local")
assert _downgrade_entry(pool, local, "cheap") is None
assert _downgrade_entry(pool, local, "optimize") is None
def test_downgrade_skips_disabled_and_mock():
pool = _make_pool()
def test_downgrade_skips_disabled_and_mock(tmp_path):
pool = _make_pool(tmp_path)
pool.upsert({"id": "b2", "name": "停用", "tier": "budget", "backend": "openai",
"base_url": "https://api.example.com/v3", "model": "mid-z",
"enabled": False})