feat: model the thinking switch as shape plus capability (issue #5)
enable_thinking=False was a no-op for minimax and openai sources: both profiles had empty dicts on each side, so the payload update injected nothing while the caller believed reasoning had been turned off. A downstream project was blocked on exactly this. The root cause is that an empty dict meant two different things -- "no injection needed" and "we do not know how this provider spells it" -- and that a provider-level table cannot express what turned out to be a per-model property. Live testing showed MiniMax-M3 can disable reasoning via reasoning_effort while M2.7 and M2.5 cannot be disabled at all, which two external registries independently confirm. So the shape stays at provider level and a capability table joins it at model level. Unknown, unsupported and no-opinion are now three distinct values, and resolve_thinking is the single place they meet: it raises at assembly time when a model cannot honour the request, warns and injects for unregistered models, and injects silently otherwise. Every registered capability carries the evidence it was derived from. enable_thinking also joins the cache fingerprint, since it now really does change the request body.
This commit is contained in:
@@ -460,6 +460,36 @@ class TestCrossFieldInvariants:
|
||||
with pytest.raises(ValueError, match="lease_ttl_s"):
|
||||
GatewayClient.from_settings(dataclasses.replace(base, lease_ttl_s=1.0))
|
||||
|
||||
# —— 推理开关的装配守卫(issue #5)——
|
||||
|
||||
def _thinking_sources(self, provider, model, enable_thinking):
|
||||
base = self._base()
|
||||
src = dataclasses.replace(
|
||||
base.sources[0], provider=provider, model=model, enable_thinking=enable_thinking
|
||||
)
|
||||
return dataclasses.replace(base, sources=(src,))
|
||||
|
||||
def test_model_that_cannot_disable_thinking_fails_at_assembly(self):
|
||||
"""M2.x 关不掉推理: 配了 false 必须当场炸,而不是装出一个骗人的 client。"""
|
||||
settings = self._thinking_sources("minimax", "MiniMax-M2.7", False)
|
||||
with pytest.raises(ValueError, match="MiniMax-M2.7"):
|
||||
GatewayClient.from_settings(settings)
|
||||
|
||||
def test_unknown_thinking_shape_fails_at_assembly(self):
|
||||
"""provider=openai 是任意兼容厂商的兜底段名,形态未知即报错并指路。"""
|
||||
settings = self._thinking_sources("openai", "kimi-k3", False)
|
||||
with pytest.raises(ValueError, match="register_provider"):
|
||||
GatewayClient.from_settings(settings)
|
||||
|
||||
def test_supported_combination_assembles(self):
|
||||
settings = self._thinking_sources("minimax", "MiniMax-M3", False)
|
||||
assert GatewayClient.from_settings(settings) is not None
|
||||
|
||||
def test_not_taking_a_position_never_trips_the_guard(self):
|
||||
"""enable_thinking=None(不干预)对任何 provider 都不该被守卫拦下。"""
|
||||
settings = self._thinking_sources("openai", "kimi-k3", None)
|
||||
assert GatewayClient.from_settings(settings) is not None
|
||||
|
||||
def test_ocr_settings_cannot_wrap_invalid_gateway(self):
|
||||
"""OcrSettings/EmbeddingSettings 只是包一层 GatewaySettings,自动继承同一把关。"""
|
||||
base = self._base()
|
||||
|
||||
Reference in New Issue
Block a user