- routes:能力位硬过滤(T-X6 语义不变)后对存活候选五维评分—— cost_efficiency=1/(1+均价*2)、capability=上下文余量(不满足为 0)、 speed/quality=档位秩比互补(local 快 / premium 优)、reliability 权重占位; 重定向目标从'取首元素'改为'取最高分';降级链按评分降序稳定重排 - 被淘汰候选带人话拒绝理由(vision/tools/上下文窗口三类)写入评分留痕环 (deque 上限 100),/admin/stats 新增 route_scored 段透出(管理面可观测) - 硬过滤语义不变:评分只改变链内顺序,不改变谁能存活 - 新增 tests/test_route_score.py 4 项(拒绝理由/排序偏好/同分决胜/留痕)
63 lines
3.0 KiB
Python
63 lines
3.0 KiB
Python
"""五维评分选模与拒绝理由留痕(T-X13,采纳 ai-model-router scoring 设计)。"""
|
||
import gateway.proxy.routes as R
|
||
from gateway.proxy.routes import (_entry_reject_reason, _rank_candidates,
|
||
_record_route_score_event,
|
||
reset_route_score_events)
|
||
|
||
|
||
def _entry(model, tier="budget", price_in=1.0, price_out=2.0,
|
||
vision=True, tools=True, ctx=0):
|
||
return {"id": model, "model": model, "enabled": True,
|
||
"backend": "openai", "base_url": "http://127.0.0.1:9/v1",
|
||
"tier": tier, "price_in": price_in, "price_out": price_out,
|
||
"capabilities": {"vision": vision, "tools": tools,
|
||
"context_window": ctx}}
|
||
|
||
|
||
_NEEDS = {"vision": True, "tools": False, "min_context_tokens": 1000}
|
||
|
||
|
||
def test_reject_reasons_are_human_readable():
|
||
"""vision/tools/上下文三类不满足均有对应人话理由;满足则 None。"""
|
||
assert "vision" in (_entry_reject_reason(_entry("a", vision=False), _NEEDS) or "")
|
||
need_tools = {"vision": False, "tools": True, "min_context_tokens": 1000}
|
||
assert "tools" in (_entry_reject_reason(_entry("a", tools=False), need_tools) or "")
|
||
small = _entry("a", ctx=512)
|
||
assert "512" in (_entry_reject_reason(small, _NEEDS) or "")
|
||
assert _entry_reject_reason(_entry("a", ctx=8192), _NEEDS) is None
|
||
|
||
|
||
def test_rank_prefers_capable_and_cheap():
|
||
"""不满足需求者 capability=0 沉底;便宜者靠成本效率维胜出。"""
|
||
good_cheap = _entry("cheap", tier="budget", price_in=0.1, price_out=0.2, ctx=8192)
|
||
good_pricey = _entry("pricey", tier="premium", price_in=8.0, price_out=8.0, ctx=8192)
|
||
ranked = _rank_candidates([good_pricey, good_cheap], _NEEDS)
|
||
assert ranked[0]["model"] == "cheap"
|
||
assert ranked[0]["dims"]["capability"] > 0
|
||
assert ranked[0]["dims"]["cost_efficiency"] > ranked[1]["dims"]["cost_efficiency"]
|
||
# premium 的质量维更高(启发式)
|
||
assert ranked[1]["dims"]["quality"] > ranked[0]["dims"]["quality"]
|
||
|
||
|
||
def test_rank_tie_break_deterministic():
|
||
"""同配置同分按模型名字典序。"""
|
||
a, b = _entry("zeta"), _entry("alpha")
|
||
ranked = _rank_candidates([a, b], _NEEDS)
|
||
assert [s["model"] for s in ranked] == ["alpha", "zeta"]
|
||
|
||
|
||
def test_score_event_records_rejections():
|
||
"""评分留痕:入选排序 + 被淘汰候选的拒绝理由;reset 清空。"""
|
||
reset_route_score_events()
|
||
ranked = _rank_candidates([_entry("good", ctx=8192)], _NEEDS)
|
||
rejected = [{"model": "bad", "reason": "模型不支持视觉输入(vision)"}]
|
||
_record_route_score_event("px-test", ranked, rejected)
|
||
_record_route_score_event("px-test-2", ranked, rejected)
|
||
assert len(R._route_score_events) == 2
|
||
ev = R._route_score_events[-1]
|
||
assert ev["request_id"] == "px-test-2"
|
||
assert ev["ranked"][0]["model"] == "good"
|
||
assert ev["rejected"][0]["reason"].startswith("模型不支持")
|
||
reset_route_score_events()
|
||
assert len(R._route_score_events) == 0
|