feat: let a source name its reasoning tier, and say so when it contradicts itself

This commit is contained in:
2026-09-05 01:56:24 -04:00
parent ed563b9ca0
commit 603a835f60
4 changed files with 178 additions and 0 deletions
+94
View File
@@ -9,6 +9,7 @@ from loguru import logger
from polygateway.client import GatewayClient
from polygateway.config import EmbeddingSettings, GatewaySettings, OcrSettings
from polygateway.providers import ProviderProfile, ThinkingWire, register_provider
from polygateway.types import Effort, SourceConfig
_BASE_ENV = {
"LLM__QWEN__1__BASE_URL": "https://gw-a.example/v1",
@@ -133,6 +134,99 @@ class TestExtraBodyParsing:
GatewaySettings.from_env("LLM", env=env)
class TestReasoningEffortParsing:
"""源级推理档位两个键的 env 解析(issue #20 Task 4)。"""
def test_effort_key_parsed(self):
env = _env(**{"LLM__QWEN__1__REASONING_EFFORT": "low"})
s = GatewaySettings.from_env("LLM", env=env)
assert s.sources[0].reasoning_effort is Effort.LOW
def test_absent_keys_keep_the_source_silent(self):
"""未配置 = 不表态,与 `Effort.NONE`(要求不推理)是两回事;映射默认关闭。"""
src = GatewaySettings.from_env("LLM", env=_env()).sources[0]
assert src.reasoning_effort is None
assert src.effort_fallback == "error"
def test_invalid_effort_lists_vocabulary(self):
"""写错档位的人要的是"那该填什么",故报错必须把八档全摆出来。"""
env = _env(**{"LLM__QWEN__1__REASONING_EFFORT": "lowest"})
with pytest.raises(ValueError) as exc:
GatewaySettings.from_env("LLM", env=env)
message = str(exc.value)
assert "REASONING_EFFORT" in message
assert all(tier.value in message for tier in Effort)
def test_effort_fallback_parsed(self):
env = _env(**{"LLM__QWEN__1__EFFORT_FALLBACK": "nearest"})
s = GatewaySettings.from_env("LLM", env=env)
assert s.sources[0].effort_fallback == "nearest"
def test_invalid_effort_fallback_rejected(self):
"""`resolve_thinking` 对未知 fallback 值是 fail-closed,不会替配置兜错。"""
env = _env(**{"LLM__QWEN__1__EFFORT_FALLBACK": "closest"})
with pytest.raises(ValueError) as exc:
GatewaySettings.from_env("LLM", env=env)
message = str(exc.value)
assert "effort_fallback" in message
assert "nearest" in message and "error" in message
class TestThinkingFlagContradiction:
"""`enable_thinking` 与 `reasoning_effort` 说的是同一件事(设计 §4.2 语法糖)。
矛盾时报错而非「后者赢」: 两个字段表达同一件事时,矛盾是配置错误,
静默取其一等于替下游猜它想要哪个。
"""
def _source(self, enable_thinking, effort):
env = _env(
**{
"LLM__QWEN__1__ENABLE_THINKING": enable_thinking,
"LLM__QWEN__1__REASONING_EFFORT": effort,
}
)
return GatewaySettings.from_env("LLM", env=env).sources[0]
@pytest.mark.parametrize(
("enable_thinking", "effort"),
[("true", "none"), ("false", "low"), ("false", "auto"), ("false", "max")],
)
def test_contradictory_thinking_flags_rejected(self, enable_thinking, effort):
with pytest.raises(ValueError) as exc:
self._source(enable_thinking, effort)
message = str(exc.value)
assert "enable_thinking" in message and "reasoning_effort" in message
@pytest.mark.parametrize(
("enable_thinking", "effort"),
[("false", "none"), ("true", "auto"), ("true", "low")],
)
def test_consistent_flags_allowed(self, enable_thinking, effort):
"""语义一致就放行: `False`+`none` 与 `True`+某个开启档都只是说了两遍。"""
src = self._source(enable_thinking, effort)
assert src.enable_thinking is (enable_thinking == "true")
assert src.reasoning_effort is Effort(effort)
def test_one_sided_declaration_never_trips_the_guard(self):
"""只配一个键是常态(存量源全是这样),不得被矛盾守卫误伤。"""
assert self._source("true", None).reasoning_effort is None
assert self._source(None, "high").enable_thinking is None
def test_contradiction_guarded_on_direct_construction(self):
"""守卫挂在构造期而非 env 解析处: 构造函数全量注入那条装配路同样过闸。"""
base = SourceConfig(
name="s1",
provider="qwen",
base_url="https://gw.example/v1",
api_key="sk-x",
model="qwen-max",
timeout_s=60.0,
)
with pytest.raises(ValueError, match="reasoning_effort"):
dataclasses.replace(base, enable_thinking=True, reasoning_effort=Effort.NONE)
class TestResilienceKeys:
def test_flat_legacy_keys(self):
s = GatewaySettings.from_env("LLM", env=_env())