fix: put the tier back where "on" by itself is not on
The minimax wire lost its tier value on the assumption that these models
reason by default, so injecting nothing still reads as "on". T10 measured
the real gateway and the assumption does not hold: MiniMax-M3 with no
reasoning parameter did not reason in 5 of 5 rounds, while all six
strength values worked. Existing downstreams on ENABLE_THINKING=true
went from reasoning to silently not reasoning, and the capability table
cannot catch it because phase 5 lets auto through unconditionally.
Restore on_base to the old {"reasoning_effort": "medium"} verbatim. This
is a stopgap - it hands the tier choice back to the library, which this
work set out to remove. The real fix is to constrain auto by the
capability table, a public behaviour change tracked as issue #21.
The assertions that said "minimax injects no tier on the on-tier" go
back with it; each carries a note on why it moved twice.
This commit is contained in:
@@ -258,10 +258,12 @@ class TestReasoningEffortPriority:
|
||||
async def test_legacy_on_tier_matches_old_fragment(self, provider, model, fragment):
|
||||
"""存量 `ENABLE_THINKING=true` 的回归门: 发出去的字节逐字不变。
|
||||
|
||||
**只覆盖 `on_base` 自己就说全了"开"的四段**。minimax/openai/anthropic/google
|
||||
的开档旧版硬编码 `{"reasoning_effort": "medium"}`,新版不注入任何档位——那是
|
||||
设计 §4.2 声明过的**有意变更**(medium 在 GLM/kimi/deepseek 的档位表里根本
|
||||
不存在,是库替下游做的档位判断),不是本门要守的不变量。
|
||||
**只覆盖 `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 登记的形态。
|
||||
@@ -275,6 +277,23 @@ class TestReasoningEffortPriority:
|
||||
# `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"
|
||||
|
||||
|
||||
class TestEffortFallbackWiring:
|
||||
"""源级 `effort_fallback` 必须真的走到 `resolve_thinking`(issue #20)。
|
||||
|
||||
@@ -724,9 +724,11 @@ class TestRequestShaping:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("enable_thinking", "expected"),
|
||||
# 开档不再附 medium(2026-09-04): 那是替下游做的档位判断,且 medium 不在
|
||||
# GLM/kimi/deepseek 的档位表里。MiniMax 开启档本就无需参数,要强度请配档位
|
||||
[(True, None), (False, "none")],
|
||||
# 本条断言反复过一次,记下原委以免第三次改回去:
|
||||
# T2(2026-09-04)按"MiniMax 开启档本就无需参数"的**推定**把 medium 改成不注入;
|
||||
# T10(2026-09-05)真实网关实测推翻该推定——M3 不发任何推理参数时 5/5 轮不推理,
|
||||
# 故 medium 回归(issue #21 的权宜之计,正解是让 auto 受能力表约束)
|
||||
[(True, "medium"), (False, "none")],
|
||||
)
|
||||
async def test_minimax_injects_reasoning_effort(self, enable_thinking, expected):
|
||||
"""issue #5: MiniMax 认的是 reasoning_effort,不是 enable_thinking。"""
|
||||
@@ -750,9 +752,9 @@ class TestRequestShaping:
|
||||
"""注入顺序即优先级: profile → extra_body → overlay,两行不可调换。
|
||||
|
||||
固定用 **zhipu + glm-5.3 + 源级 low** 这组: 判据必须落在一个 profile
|
||||
**真的写了值**的键上,两边写同一个键才谈得上谁覆盖谁。用 minimax +
|
||||
`enable_thinking=True` 是测不出来的——T5 起 `True ≡ auto`,而 auto 的注入
|
||||
片段就是 minimax 的 `on_base`(空字典),两行怎么调换结果都一样,断言恒真。
|
||||
**真的写了值**的键上,两边写同一个键才谈得上谁覆盖谁。不挑 minimax 是因为
|
||||
它的 `on_base` 只写 `reasoning_effort` 一个键(issue #21 的权宜之计),
|
||||
覆盖发生后看不见"profile 独有的那半边仍在",判据少一半。
|
||||
"""
|
||||
seen = {}
|
||||
|
||||
|
||||
@@ -46,13 +46,28 @@ class TestDefaultProfiles:
|
||||
assert w.effort_key == "reasoning_effort"
|
||||
|
||||
def test_openai_family_sends_the_standard_field_only(self):
|
||||
"""gpt/claude/gemini 经网关都吃 OpenAI 标准的 reasoning_effort,不下发厂商方言。"""
|
||||
for name in ("openai", "anthropic", "google", "minimax"):
|
||||
"""gpt/claude/gemini 经网关都吃 OpenAI 标准的 reasoning_effort,不下发厂商方言。
|
||||
|
||||
minimax 2026-09-05 起不在本组: 它的形态相同,但"开"这一档被迫带上了一个
|
||||
档位值(见 `test_minimax_on_tier_carries_a_tier_value`)。
|
||||
"""
|
||||
for name in ("openai", "anthropic", "google"):
|
||||
w = get_provider(name).thinking
|
||||
assert w.on_base == {}, name
|
||||
assert w.off == {"reasoning_effort": "none"}, name
|
||||
assert w.effort_key == "reasoning_effort", name
|
||||
|
||||
def test_minimax_on_tier_carries_a_tier_value(self):
|
||||
"""issue #21 的权宜之计: minimax 的"开"必须真写一个档位值,不能是空片段。
|
||||
|
||||
断言反复过一次: T2 按"这些模型默认就推理"的推定把它改成 `{}`,T10 真实
|
||||
网关实测推翻推定(M3 不发推理参数时 5/5 轮不推理),故逐字恢复旧版的 medium。
|
||||
"""
|
||||
w = get_provider("minimax").thinking
|
||||
assert w.on_base == {"reasoning_effort": "medium"}
|
||||
assert w.off == {"reasoning_effort": "none"}
|
||||
assert w.effort_key == "reasoning_effort"
|
||||
|
||||
def test_unknown_provider_fails_loudly(self):
|
||||
"""消灭子串猜测: 未注册 provider 装配期即报错,不做模糊匹配。"""
|
||||
with pytest.raises(ValueError, match="glm"):
|
||||
@@ -82,8 +97,12 @@ class TestWireNoneSemantics:
|
||||
assert get_provider("qwen").thinking.effort_key is None
|
||||
|
||||
def test_empty_on_base_is_not_none(self):
|
||||
"""`{}` = 已知无需注入任何参数即处于该档;`None` = 不知道怎么表达。"""
|
||||
w = get_provider("minimax").thinking
|
||||
"""`{}` = 已知无需注入任何参数即处于该档;`None` = 不知道怎么表达。
|
||||
|
||||
样本 2026-09-05 由 minimax 换成 openai: minimax 的 `on_base` 因 issue #21
|
||||
改回带值,不再是空片段;openai 段是现存 `{}` 语义的代表。
|
||||
"""
|
||||
w = get_provider("openai").thinking
|
||||
assert w.on_base == {} and w.on_base is not None
|
||||
|
||||
|
||||
|
||||
@@ -286,10 +286,15 @@ class TestResolveThinking:
|
||||
)
|
||||
|
||||
def test_phase4_only_blocks_the_off_direction(self):
|
||||
"""关不掉 ≠ 开不了: M2.x 默认就在推理,开的方向不该被拦。"""
|
||||
"""关不掉 ≠ 开不了: M2.x 默认就在推理,开的方向不该被拦。
|
||||
|
||||
期望片段 2026-09-05 由 `{}` 改成 minimax 的 `on_base` 实际值: issue #21 把
|
||||
该段的"开"改回带 medium(T2 的"开档不注入"是推定,T10 实测推翻)。本用例守的
|
||||
是 Phase 4 只拦关闭方向,注入什么由 wire 决定,故随 wire 走。
|
||||
"""
|
||||
cap = get_capability("MiniMax-M2.7")
|
||||
got = resolve_thinking(get_provider("minimax"), cap, Effort.AUTO, model="MiniMax-M2.7")
|
||||
assert got.payload == {}
|
||||
assert got.payload == {"reasoning_effort": "medium"}
|
||||
assert got.applied_effort is Effort.AUTO
|
||||
|
||||
def test_phase4_passes_when_none_is_registered(self):
|
||||
|
||||
Reference in New Issue
Block a user