test: pin the tier fallback and the assembly guard to real behaviour
Both were wired but unwitnessed: hardcoding the transport's fallback, or blanking the source tier the assembly guard reads, left the whole unit suite green. Cover them where the value is visible -- the bytes on the wire for nearest-vs-error, and the assembly-time refusal that must name low as the executable alternative.
This commit is contained in:
@@ -12,6 +12,7 @@ from polygateway import (
|
|||||||
AllSourcesExhausted,
|
AllSourcesExhausted,
|
||||||
GatewayClient,
|
GatewayClient,
|
||||||
GatewaySettings,
|
GatewaySettings,
|
||||||
|
RequestRejectedError,
|
||||||
gather_bounded,
|
gather_bounded,
|
||||||
)
|
)
|
||||||
from polygateway.backends.memory.breaker import InMemoryGate
|
from polygateway.backends.memory.breaker import InMemoryGate
|
||||||
@@ -275,6 +276,42 @@ class TestReasoningEffortPriority:
|
|||||||
assert "reasoning_effort" not in body
|
assert "reasoning_effort" not in body
|
||||||
|
|
||||||
|
|
||||||
|
class TestEffortFallbackWiring:
|
||||||
|
"""源级 `effort_fallback` 必须真的走到 `resolve_thinking`(issue #20)。
|
||||||
|
|
||||||
|
只测配置值域与纯函数是不够的: 变异实测显示把 transport 里的
|
||||||
|
`fallback=source.effort_fallback` 换成硬编码 `"error"`,全套单测依然全绿——
|
||||||
|
`nearest` 是人类明确要求实现的功能,没有这条端到端用例它会在零告警下变成
|
||||||
|
死代码(2026-09-05 独立验证查出)。故断言看**发出去的字节**。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _capturing_client(self, captured, **overrides):
|
||||||
|
def handler(request):
|
||||||
|
captured.append(json.loads(request.content))
|
||||||
|
return _sse()
|
||||||
|
|
||||||
|
return _client(handler=handler, **overrides)
|
||||||
|
|
||||||
|
async def test_nearest_sends_the_mapped_tier(self):
|
||||||
|
"""glm-5.3 只有 low/high/max: 请求 `medium` 等距,按"取弱"落到 low。"""
|
||||||
|
captured = []
|
||||||
|
source = _source(provider="zhipu", model="glm-5.3", effort_fallback="nearest")
|
||||||
|
async with self._capturing_client(captured, sources=[source]) as client:
|
||||||
|
await client.chat([{"role": "user", "content": "hi"}], reasoning_effort=Effort.MEDIUM)
|
||||||
|
assert captured[0]["reasoning_effort"] == "low"
|
||||||
|
|
||||||
|
async def test_error_fallback_refuses_the_same_request(self):
|
||||||
|
"""默认 `error` 必须报错而不是映射: 一次静默的换档就是一笔没打招呼的账单。"""
|
||||||
|
captured = []
|
||||||
|
source = _source(provider="zhipu", model="glm-5.3")
|
||||||
|
async with self._capturing_client(captured, sources=[source]) as client:
|
||||||
|
with pytest.raises(RequestRejectedError, match="medium"):
|
||||||
|
await client.chat(
|
||||||
|
[{"role": "user", "content": "hi"}], reasoning_effort=Effort.MEDIUM
|
||||||
|
)
|
||||||
|
assert captured == [] # 请求根本没发出去
|
||||||
|
|
||||||
|
|
||||||
class TestRequestTierNormalization:
|
class TestRequestTierNormalization:
|
||||||
"""`chat(reasoning_effort=...)` 是公共入口,裸字符串必须在此归一(issue #20)。
|
"""`chat(reasoning_effort=...)` 是公共入口,裸字符串必须在此归一(issue #20)。
|
||||||
|
|
||||||
|
|||||||
@@ -758,6 +758,24 @@ class TestCrossFieldInvariants:
|
|||||||
with pytest.raises(ValueError, match="register_provider"):
|
with pytest.raises(ValueError, match="register_provider"):
|
||||||
GatewayClient.from_settings(settings, registry=register_provider(mystery))
|
GatewayClient.from_settings(settings, registry=register_provider(mystery))
|
||||||
|
|
||||||
|
def test_source_tier_the_model_cannot_satisfy_fails_at_assembly(self):
|
||||||
|
"""守卫必须读**源级档位**,而不只是 `enable_thinking`(设计 §4.2 三层优先级)。
|
||||||
|
|
||||||
|
变异实测: 把 `_guard_thinking` 的 `source_effort=source.reasoning_effort`
|
||||||
|
改成 `None`,全套单测依然全绿(2026-09-05 独立验证查出)。文案里必须出现
|
||||||
|
可执行替代 `low`——glm-5.3 官方关不掉推理,只报"不行"会把人推回
|
||||||
|
`extra_body` 那条绕过治理的老路(issue #20 的成因)。
|
||||||
|
"""
|
||||||
|
base = self._base()
|
||||||
|
src = dataclasses.replace(
|
||||||
|
base.sources[0], provider="zhipu", model="glm-5.3", reasoning_effort=Effort.NONE
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError) as exc:
|
||||||
|
GatewayClient.from_settings(dataclasses.replace(base, sources=(src,)))
|
||||||
|
message = str(exc.value)
|
||||||
|
assert "glm-5.3" in message
|
||||||
|
assert "low" in message
|
||||||
|
|
||||||
def test_openai_segment_now_assembles_with_the_standard_field(self):
|
def test_openai_segment_now_assembles_with_the_standard_field(self):
|
||||||
# 行为变更(2026-09-04): reasoning_effort 是 OpenAI 官方字段而非厂商方言,
|
# 行为变更(2026-09-04): reasoning_effort 是 OpenAI 官方字段而非厂商方言,
|
||||||
# 经网关的兼容端点收得下,故兜底段不再把"关闭"判为形态未知
|
# 经网关的兼容端点收得下,故兜底段不再把"关闭"判为形态未知
|
||||||
|
|||||||
Reference in New Issue
Block a user