docs: disprove the issue #16/#17 diagnosis with live gateway probes

The four red e2e cases were blamed on MiniMax-M3 no longer reasoning.
Raw gateway probes show the opposite: M3 reasons fine (124 chars of
reasoning_content, prompt 194 to 216, completion 3 to 60). What changed
is that the MiniMax route stopped returning completion_tokens_details,
while qwen and deepseek still do on the same gateway and key. The
library already holds 185 chars of proof in LLMResponse.thinking and
never feeds it into any verdict.

The design turns that verdict into a first-class return value judged
from multiple signals, says UNKNOWN when a single response cannot tell,
and reconciles it against the capability table so a stale declaration
becomes a warning instead of a silent illusion.
This commit is contained in:
2026-08-25 22:02:03 -04:00
parent f5cf69a1ac
commit 37b4a557c2
2 changed files with 303 additions and 0 deletions
@@ -0,0 +1,94 @@
---
type: finding
node_id: finding:2026-08-25-thinking-observability-regression
title: "issue #16/#17 实测: M3 推理正常,失效的是推理的可观测信号"
date: 2026-08-25
---
# issue #16/#17 实测:M3 推理正常,失效的是推理的**可观测信号**
> 类型:finding|日期:2026-08-25|网关 `newapi.iomgaa.online`
> 本文推翻 issue #16/#17 的原始诊断("模型不再推理"),是 `designs/2026-08-25-thinking-observability-design.md` 的事实基础。
## 1. 为什么要重测
issue #16/#17 判定 MiniMax-M3 的开启推理"静默失效:模型没有推理",依据是 `tests/e2e/test_thinking_live.py` 的 L2/L3b/L4/L5 四条全红,四条的共同判据是 `reasoning_tokens > 0`。issue 自己留了一个未区分的岔路:网关侧模型行为变了,还是库的注入失效了。区分方法写得很清楚——抓一次真实请求体与原始响应。本文就是那次抓取。
## 2. 方法
两层探针,都不走 slow 套件:
其一**绕开库**,用裸 `httpx` 直接 POST `/chat/completions`,矩阵化七种参数形态 × 流式/非流式,记录完整 `usage``message` 的键集合。绕开库是必要的——要证的命题之一正是"库有没有把参数弄丢",用库测这一条是循环论证。
其二**用库本身**跑 `GatewayClient.chat`,记录 `LLMResponse``reasoning_tokens``thinking` 两个字段。两层对照才能定位缺口落在哪一层。
对照组取 `qwen3.7-plus``deepseek-v4-pro`——同一网关、同一 key,用来区分"MiniMax 这一路变了"与"网关全局变了"。
## 3. 原始观测
### 3.1 MiniMax-M3,裸 httpx,非流式
| 变体 | prompt | completion | `completion_tokens_details` | `reasoning_content` |
|---|---|---|---|---|
| 不注入(基线) | 194 | 3 | **整个容器缺失** | 无 |
| `reasoning_effort=medium` | **216** | **48** | 整个容器缺失 | 无 |
| `reasoning_effort=high` | **216** | **65** | 整个容器缺失 | 无 |
| `reasoning_effort=none` | 194 | 3 | 整个容器缺失 | 无 |
| `thinking={"type":"enabled"}` | 194 | 3 | 整个容器缺失 | 无 |
| `enable_thinking=true` | 194 | 3 | 整个容器缺失 | 无 |
| 非法值 `definitely-not-a-real-level` | 207 | 87 | 整个容器缺失 | 无 |
### 3.2 MiniMax-M3,裸 httpx,流式
| 变体 | delta 的键集合 | `reasoning_content` 累计 | usage |
|---|---|---|---|
| 不注入 | `content`,`role` | 0 字符 | prompt 194 / completion 3,无 ctd |
| `reasoning_effort=medium` | `content`,**`reasoning_content`**,`role` | **124 字符,完整推理过程** | prompt 216 / completion 60,无 ctd |
| `reasoning_effort=none` | `content`,`role` | 0 字符 | prompt 194 / completion 3,无 ctd |
流式 medium 档抓到的推理正文(前 120 字符):`We need answer Chinese, only two digits. Chickens x rabbits y. x+y=35,2x+4y=94 => x+y*? 2*35+2y=94 y=12, x=23. Output 23`
### 3.3 对照组(流式)
| 模型 | 变体 | `reasoning_content` | `completion_tokens_details.reasoning_tokens` |
|---|---|---|---|
| deepseek-v4-pro | 不注入 | 135 字符 | **88** |
| deepseek-v4-pro | `effort=medium` | 134 字符 | **89** |
| deepseek-v4-pro | `effort=none` | 0 | 容器缺失 |
| qwen3.7-plus | 不注入 | 350 字符 | **158** |
| qwen3.7-plus | `effort=medium` | 606 字符 | **229** |
| qwen3.7-plus | `effort=none` | 0 | 容器缺失 |
| qwen3.7-plus | 非法值 | — | **HTTP 400** |
### 3.4 用库跑(`LLMResponse` 字段)
| 场景 | `reasoning_tokens` | `thinking` 字符数 | completion |
|---|---|---|---|
| M3 开启,流式 | None | **185** | 69 |
| M3 开启,非流式 | None | **0** | 53 |
| M3 关闭,流式/非流式 | None | 0 | 3 |
| M3 不干预 | None | 0 | 3 |
| qwen 开启,流式 | **205** | 484 | 213 |
| qwen 关闭,流式 | None | 0 | 5 |
## 4. 五条结论
**① M3 的推理完全正常,issue 的诊断是错的。** 流式 medium 档抓到 124 字符完整推理过程;`prompt_tokens` 194→216(供应商注入推理指令)、`completion_tokens` 3→60(推理段被计费)。三个独立信号一致。
**② 真正变的是 MiniMax 这一路不再返回 `usage.completion_tokens_details`。** 而 qwen 与 deepseek 在同一网关同一 key 上照常返回。所以这不是网关全局改了 usage 处理,是 MiniMax 这一路上游的 usage 形态变了。`reasoning_tokens` 恒 NULL 由此而来。
**③ 库自己已经握有决定性证据,却没有用。** `LLMResponse.thinking` 在 M3 开启档流式路径下是 185 字符的实打实推理正文。e2e 的 `_reasoning_on` 只看 `reasoning_tokens``completion_tokens` 长度,从不看 `thinking`——四条红是判据的盲区,不是功能的失效。
**④ M3 非流式路径下推理内容整体丢失,且下游在付费。** `completion_tokens` 53 vs 关闭档 3,说明推理段确实产生并计费;而 `message` 的键集合只有 `content`/`role``reasoning_content` 不存在。下游用非流式调 M3 开推理 = 付钱买看不见的东西,且当前库不告诉它。这不是库能修的(上游不返回),但库必须让它可见。
**⑤ 三家供应商在"未推理"时都是整个 `completion_tokens_details` 缺失,无人上报 `0`。** 与 2026-08-02 findings §4c 的记录一致。推论:**"容器在不在"不能当作"有没有推理"的判据**——它与真实信号高度混淆,拿它做裁定等于把噪声当信号。
## 5. 顺带纠正的两处既有认识
**`enable_thinking` / `thinking:{type:enabled}` 对 M3 无效这一条仍然成立**(prompt 恒 194 = 基线),只有 `reasoning_effort` 是真开关。`providers.py` 的 minimax profile 用的正是 `reasoning_effort`,选型至今正确。
**L3b 的"非法值反证"手法只对不校验值的 provider 成立。** minimax 对非法 `reasoning_effort` 返回 200 且照常推理(prompt 207,介于基线 194 与 medium 216 之间,说明走了第三条模板路径);qwen 对同样的非法值直接 **HTTP 400**。这条手法写进测试时只在 minimax 上验过,它不可移植——若哪天把 L3b 套到别的 provider 上会得到假红。
## 6. `can_disable` 复测
M3 的 `ThinkingCapability(can_disable=True)` 的 evidence 停在 2026-08-02。2026-08-25 复测:`reasoning_effort=none` → prompt 194= 基线)、completion 3、无 `reasoning_content`。**声明依然成立**,只需刷新 evidence 日期并补记本文新发现的两条限制(非流式不可观测、仅 `reasoning_effort` 有效)。