refactor: make capability a tier list, since "can it be off" is one entry
The boolean could say a model reasons or does not. It could not say what GLM-5.3 and Gemini 3 Pro actually do: refuse to stop reasoning while still letting you ask for less. So capability becomes the list of tiers a model serves, and `none`'s presence in it is what "can_disable" now reads. Effort carries `auto` alongside the strength tiers. Nine of the models on our gateway are pure switches with no tier to name, and without `auto` they would have to borrow a strength tier to mean "on" — which is the exact bug this work exists to remove. Tiers land as documented guesses from four registries that agree; every entry says so in its evidence, and task 10 replaces them with measurements.
This commit is contained in:
@@ -18,7 +18,7 @@ from polygateway.thinking import (
|
||||
register_capability,
|
||||
resolve_thinking,
|
||||
)
|
||||
from polygateway.types import ThinkingObservation
|
||||
from polygateway.types import Effort, ThinkingObservation
|
||||
|
||||
|
||||
def _warnings():
|
||||
@@ -125,7 +125,7 @@ class TestThinkingCapability:
|
||||
assert get_capability("some-brand-new-model") is None
|
||||
|
||||
def test_register_capability_is_pure(self):
|
||||
table = register_capability("x-1", ThinkingCapability(True, "实测"))
|
||||
table = register_capability("x-1", ThinkingCapability((Effort.NONE, Effort.AUTO), "实测"))
|
||||
assert get_capability("x-1", table=table) is not None
|
||||
assert get_capability("x-1") is None # 默认表未被污染
|
||||
|
||||
@@ -175,7 +175,7 @@ class TestResolveThinking:
|
||||
|
||||
def test_unknown_shape_beats_capability_check(self):
|
||||
"""第 2 步先于第 4 步: 形态未知时无从注入,能力如何无关紧要。"""
|
||||
cap = ThinkingCapability(can_disable=False, evidence="构造")
|
||||
cap = ThinkingCapability((Effort.AUTO,), "构造")
|
||||
with pytest.raises(ValueError, match="register_provider"):
|
||||
resolve_thinking(get_provider("openai"), cap, False, model="whatever")
|
||||
|
||||
@@ -188,7 +188,7 @@ class TestReconcileThinking:
|
||||
"""
|
||||
|
||||
_CAP = ThinkingCapability(
|
||||
can_disable=True, evidence="2026-08-02 实测 reasoning_effort=none 可关闭"
|
||||
(Effort.NONE, Effort.AUTO), "2026-08-02 实测 reasoning_effort=none 可关闭"
|
||||
)
|
||||
|
||||
def test_off_but_observed_with_a_registered_capability_blames_the_table(self):
|
||||
@@ -311,3 +311,80 @@ class TestReconcileThinking:
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
class TestEffortVocabulary:
|
||||
"""八档封闭词汇(设计 §3.1);`auto` 不可省——9 个纯开关型模型无强度档可填。"""
|
||||
|
||||
def test_none_and_auto_are_distinct_members(self):
|
||||
assert Effort.NONE != Effort.AUTO
|
||||
assert Effort("none") is Effort.NONE
|
||||
assert Effort("auto") is Effort.AUTO
|
||||
|
||||
def test_vocabulary_is_exactly_eight(self):
|
||||
assert len(list(Effort)) == 8
|
||||
|
||||
def test_values_are_wire_literals(self):
|
||||
# 档位值直接写进请求体,改名即改变发出去的字节
|
||||
assert [e.value for e in Effort] == [
|
||||
"none",
|
||||
"auto",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
"max",
|
||||
]
|
||||
|
||||
|
||||
class TestCapabilityTierList:
|
||||
"""能力表从 bool 变成档位清单(设计 §3.2);三个派生量不存字段,存了必漂移。"""
|
||||
|
||||
def test_capability_derives_can_disable(self):
|
||||
assert ThinkingCapability((Effort.NONE, Effort.AUTO), "实测").can_disable is True
|
||||
assert ThinkingCapability((Effort.LOW, Effort.MAX), "实测").can_disable is False
|
||||
|
||||
def test_cheapest_effort_skips_none(self):
|
||||
# 「关不掉时的可执行替代」取的是除 none 外最弱的一档
|
||||
assert (
|
||||
ThinkingCapability((Effort.LOW, Effort.HIGH, Effort.MAX), "实测").cheapest_effort
|
||||
is Effort.LOW
|
||||
)
|
||||
assert (
|
||||
ThinkingCapability((Effort.NONE, Effort.HIGH, Effort.MAX), "实测").cheapest_effort
|
||||
is Effort.HIGH
|
||||
)
|
||||
assert ThinkingCapability((Effort.NONE, Effort.AUTO), "实测").cheapest_effort is Effort.AUTO
|
||||
assert ThinkingCapability((Effort.AUTO,), "实测").cheapest_effort is Effort.AUTO
|
||||
|
||||
def test_cheapest_effort_is_none_when_only_none(self):
|
||||
# 只能关不能开: 没有可推荐的「最省的开启档」
|
||||
assert ThinkingCapability((Effort.NONE,), "实测").cheapest_effort is None
|
||||
|
||||
def test_is_tiered_excludes_none_and_auto(self):
|
||||
# 纯开关型模型不该被告知「可选档位」——它没有档位
|
||||
assert ThinkingCapability((Effort.NONE, Effort.AUTO), "实测").is_tiered is False
|
||||
assert ThinkingCapability((Effort.AUTO,), "实测").is_tiered is False
|
||||
assert ThinkingCapability((Effort.LOW, Effort.MAX), "实测").is_tiered is True
|
||||
|
||||
def test_empty_efforts_rejected(self):
|
||||
with pytest.raises(ValueError, match="至少"):
|
||||
ThinkingCapability((), "实测")
|
||||
|
||||
def test_duplicate_efforts_rejected(self):
|
||||
with pytest.raises(ValueError, match="重复"):
|
||||
ThinkingCapability((Effort.LOW, Effort.LOW), "实测")
|
||||
|
||||
def test_glm53_cannot_be_disabled(self):
|
||||
# 三源一致(智谱官方文档/cherry-studio/OpenRouter): thinking.type 只接受 enabled
|
||||
cap = get_capability("glm-5.3")
|
||||
assert cap is not None
|
||||
assert cap.can_disable is False
|
||||
assert cap.cheapest_effort is Effort.LOW
|
||||
|
||||
def test_m2_series_still_cannot_be_disabled(self):
|
||||
# 迁移回归: 旧表用 can_disable=False 表达的事实,新表用「none 不在清单里」表达
|
||||
assert get_capability("MiniMax-M2.7").can_disable is False
|
||||
assert get_capability("MiniMax-M2.5").can_disable is False
|
||||
assert get_capability("MiniMax-M3").can_disable is True
|
||||
|
||||
Reference in New Issue
Block a user