fix: reject conflicting raw reasoning overrides before sending
This commit is contained in:
+76
-40
@@ -247,52 +247,35 @@ class TestReasoningEffortPriority:
|
||||
assert "reasoning_effort" not in captured[0]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("provider", "model", "fragment"),
|
||||
"provider,model,tier",
|
||||
[
|
||||
("qwen", "qwen-max", {"enable_thinking": True}),
|
||||
("deepseek", "deepseek-v4-pro", {"thinking": {"type": "enabled"}}),
|
||||
("zhipu", "glm-5.3", {"thinking": {"type": "enabled"}}),
|
||||
("moonshot", "kimi-k3", {"thinking": {"type": "enabled"}}),
|
||||
("deepseek", "deepseek-v4-pro", "high"),
|
||||
("zhipu", "glm-5.3", "low"),
|
||||
("moonshot", "kimi-k3", "low"),
|
||||
("minimax", "MiniMax-M3", "medium"),
|
||||
],
|
||||
)
|
||||
async def test_legacy_on_tier_matches_old_fragment(self, provider, model, fragment):
|
||||
"""存量 `ENABLE_THINKING=true` 的回归门: 发出去的字节逐字不变。
|
||||
async def test_legacy_auto_requires_explicit_migration(self, provider, model, tier):
|
||||
"""旧糖配置明确拒绝,显式选择才能恢复可执行请求。"""
|
||||
from dataclasses import replace
|
||||
|
||||
**只覆盖 `on_base` 自己就说全了"开"的四段**。openai/anthropic/google 的开档
|
||||
旧版硬编码 `{"reasoning_effort": "medium"}`,新版不注入任何档位——那是设计
|
||||
§4.2 声明过的**有意变更**(medium 在 GLM/kimi/deepseek 的档位表里根本不存在,
|
||||
是库替下游做的档位判断),不是本门要守的不变量;这三家的模型经 OpenRouter
|
||||
登记均为默认推理,不注入也仍是"开"。minimax 不在此列: 它的模型不满足该前提,
|
||||
已按 issue #21 改回 medium,由下一条用例单独守。
|
||||
|
||||
qwen/deepseek 两条字面量逐字取自升级前的 `ProviderProfile.thinking_on`;
|
||||
zhipu/moonshot 升级前没有对应段,断言的是它们 2026-09-04 登记的形态。
|
||||
"""
|
||||
captured = []
|
||||
source = _source(provider=provider, model=model, enable_thinking=True)
|
||||
async with self._capturing_client(captured, sources=[source]) as client:
|
||||
await client.chat([{"role": "user", "content": "hi"}])
|
||||
body = captured[0]
|
||||
assert {k: body[k] for k in fragment} == fragment
|
||||
# `auto` = 开启但不指定强度: 语法糖不得替调用方挑一个档
|
||||
assert "reasoning_effort" not in body
|
||||
|
||||
async def test_legacy_minimax_on_tier_actually_turns_reasoning_on(self):
|
||||
"""回归门(issue #21): minimax 段的存量 `ENABLE_THINKING=true` 必须真开推理。
|
||||
|
||||
本次换代一度把这段的开启形态改成 `on_base={}`(什么参数都不注入),依据是
|
||||
"这些模型默认就推理,不注入也仍是'开'"。T10 真实网关实测推翻了该前提:
|
||||
MiniMax-M3 不带任何推理参数时 5/5 轮**不推理**(六个强度值则全部生效)。
|
||||
于是存量下游从"真开推理"静默变成"不推理",而 `resolve_thinking` 的 Phase 5
|
||||
无条件放行 `auto`、能力表也堵不住这条路。
|
||||
|
||||
断言落在**发出去的字节**上而非中间态: 静默不推理这件事只有在请求体里才看得见。
|
||||
"""
|
||||
captured = []
|
||||
source = _source(provider="minimax", model="MiniMax-M3", enable_thinking=True)
|
||||
async with self._capturing_client(captured, sources=[source]) as client:
|
||||
await client.chat([{"role": "user", "content": "hi"}])
|
||||
assert captured[0]["reasoning_effort"] == "medium"
|
||||
client = self._capturing_client(captured, sources=[source])
|
||||
try:
|
||||
with pytest.raises(RequestRejectedError):
|
||||
await client.chat([])
|
||||
assert captured == []
|
||||
finally:
|
||||
await client._transport.aclose()
|
||||
client = self._capturing_client(
|
||||
captured, sources=[replace(source, enable_thinking=None, reasoning_effort=tier)]
|
||||
)
|
||||
try:
|
||||
await client.chat([])
|
||||
assert captured[0]["reasoning_effort"] == tier
|
||||
finally:
|
||||
await client._transport.aclose()
|
||||
|
||||
|
||||
class TestEffortFallbackWiring:
|
||||
@@ -1280,3 +1263,56 @@ class TestTelemetryStatusExposure:
|
||||
assert _ocr_client().telemetry_status is None
|
||||
assert _ocr_client(telemetry=_Closable()).telemetry_status is None
|
||||
self._assert_snapshot(_ocr_client(telemetry=self._recorder(tmp_path)).telemetry_status)
|
||||
|
||||
|
||||
class TestManagedReasoningAdmission:
|
||||
"""入口前置与实际准入边界保持明确。"""
|
||||
|
||||
@pytest.mark.parametrize("factory", ["env", "settings"])
|
||||
def test_factory_rejects_conflict_before_building_backends(self, monkeypatch, factory):
|
||||
env = {
|
||||
**_ENV,
|
||||
"LLM__QWEN__1__MODEL": "qwen3.7-plus",
|
||||
"LLM__QWEN__1__ENABLE_THINKING": "true",
|
||||
"LLM__QWEN__1__EXTRA_BODY": '{"enable_thinking":true}',
|
||||
}
|
||||
built = []
|
||||
|
||||
def forbidden(*args, **kwargs):
|
||||
built.append(True)
|
||||
raise AssertionError("后端不得构造")
|
||||
|
||||
monkeypatch.setattr("polygateway.client._build_limiter", forbidden)
|
||||
with pytest.raises(ValueError, match="冲突"):
|
||||
if factory == "env":
|
||||
GatewayClient.from_env(env=env)
|
||||
else:
|
||||
GatewayClient.from_settings(GatewaySettings.from_env(env=env))
|
||||
assert not built
|
||||
|
||||
async def test_request_conflict_does_not_enter_onion(self):
|
||||
client = _client()
|
||||
|
||||
async def forbidden(request):
|
||||
raise AssertionError("不得进入洋葱")
|
||||
|
||||
client._handler = forbidden
|
||||
try:
|
||||
with pytest.raises(ValueError, match="冲突"):
|
||||
await client.chat([], reasoning_effort="high", overlay={"reasoning_effort": "high"})
|
||||
finally:
|
||||
await client._transport.aclose()
|
||||
|
||||
async def test_source_conflict_releases_admitted_permit(self):
|
||||
source = _source(
|
||||
reasoning_effort="high", provider="openai", extra_body={"reasoning_effort": "high"}
|
||||
)
|
||||
sent = []
|
||||
client = _client(sources=[source], handler=lambda request: sent.append(request) or _sse())
|
||||
try:
|
||||
with pytest.raises(RequestRejectedError, match="冲突"):
|
||||
await client.chat([])
|
||||
assert sent == []
|
||||
assert (await client._limiter_backend.source_stats(source.name)).inflight == 0
|
||||
finally:
|
||||
await client._transport.aclose()
|
||||
|
||||
Reference in New Issue
Block a user