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:
2026-09-05 00:31:28 -04:00
parent de261e485d
commit 2a50ddcf12
4 changed files with 308 additions and 16 deletions
+81 -4
View File
@@ -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