Files
PolyGateway/research-wiki/findings/2026-08-02-thinking-switch-and-reasoning-tokens.md
T
iomgaa 4c135075b3 test: verify the thinking switch against the live API (issue #5, #6)
A sixteen-row matrix over 127 real calls: disable and enable on
MiniMax-M3 in both streaming and non-streaming mode, extra_body winning
over the profile slot, qwen and deepseek still disabling correctly, a
drift sentinel that re-derives every registered capability from live
behaviour, and the assembly guard refusing the models that cannot
comply.

Two judgement criteria had to be corrected by the data they were meant
to judge. Output length cannot separate the two regimes at all -- the
disabled runs reach 46 tokens when the model narrates its working in
the visible answer, and the enabled runs drop to 13 when medium effort
barely thinks. reasoning_tokens separates them cleanly in both
directions, which is precisely what issue #6 was collected for. A
second anchor compares prompt_tokens between the two regimes: the
vendor injects a reasoning instruction when thinking is on, so the
input side grows, and comparing the two runs relatively avoids
hardcoding any vendor number.

Provider names are mapped explicitly rather than guessed from the model
string; guessing had silently skipped the qwen row behind a "source
unavailable" reason that was not true.
2026-08-02 06:55:38 -04:00

14 KiB
Raw Blame History

type, node_id, title, date
type node_id title date
finding finding:2026-08-02-thinking-switch-and-reasoning-tokens 推理开关与 reasoning_tokens: 供应商实测与业界做法 2026-08-02

推理开关与 reasoning_tokens:供应商实测与业界做法

类型:findings(事实基础)|日期:2026-08-02|来源:issue #5 / #6 调研 本文只记录已验证的事实与其证据,设计取舍见 designs/2026-08-02-thinking-capability-design.md。 本文的价值不限于这两条 issue——「同一语义、形态因模型而异」是本库长期要面对的一类问题,此处的结论与方法可复用。

1. 实验环境与方法

端点 自建 new-api 中转(newapi.iomgaa.online/v1OpenAI 兼容)
参数 temperature=0max_tokens=800、非流式为主,流式单独验证
题目 固定一道鸡兔同笼题,要求"只输出两个数字"
判据 usage.completion_tokens_details.reasoning_tokens唯一可靠的判别量,见 §2.5
旁证 prompt_tokens 变化——注入生效的参数会改变模型侧模板,输入侧 token 数随之变化

方法论要点(可复用):判断一个参数"是否被上游真正消费",prompt_tokens 比输出长度可靠得多。输出长度受采样影响、方差大;而输入侧 token 数在同一请求体下是确定的,一旦变化就说明服务端换了模板,即参数确实到达了模型。本次三条关键结论全部由这个旁证锁定。

2. MiniMax:真开关是 reasoning_effort

2.1 M3 参数矩阵(非流式)

注入参数 prompt completion reasoning_tokens 判定
默认(不传) 194 4 无 ctd 不推理
reasoning_effort=none 194 10 无 ctd 不推理
reasoning_effort=minimal 207 129 123 推理
reasoning_effort=low 207 98 93 推理
reasoning_effort=medium 207 183 177 推理
reasoning_effort=high 207 158 142 推理
thinking={"type":"enabled"} 194 5 无 ctd 被静默丢弃
thinking={"type":"disabled"} 194 4 无 ctd 被静默丢弃
enable_thinking=true 194 5 无 ctd 被静默丢弃
enable_thinking=false 194 5 无 ctd 被静默丢弃

prompt_tokens 194→207 的 13 token 差是硬证据:reasoning_effort 被消费时模型注入了推理指令;另四种写法 prompt 恒为 194,参数根本没到达模型。

2.2 none 是被识别的真值,不是被当非法值丢弃

这是一个必须排除的伪解释——若中转把不认识的值直接丢掉,none 的表现会与"不传"无异,我们就会误以为它生效。

反证实验:传乱码值 reasoning_effort="xyzzy" → 返回 200、prompt=207、reasoning_tokens=180。未知值不但没被丢弃,反而开启了推理。 既然无效值的行为是"开推理",而 none 的行为是"不推理",两者不同,none 就必然是被识别的枚举值。

对照组:完全未知的 zzz_bogus_param=1 → prompt=194、无 ctd、无报错,确认未知才会被静默吞掉。

2.3 M2.7 / M2.5 的推理关不掉

三种参数形态各 3 次,completion_tokens 全部落在推理区间:

模型 默认(基线) reasoning_effort=none thinking:{disabled} thinking:{adaptive}
MiniMax-M2.7 372/283/285 275/301/248 310/190/219 299/269/246
MiniMax-M2.5 273//256 363/353/264 286/278/320 278/228/259

真关闭应为 510"23 12" 两个数字),实测无一接近。

三个独立外部来源与实测完全吻合

来源 M3 M2.7 / M2.5
OpenRouter /api/v1/modelsreasoning 描述符 mandatory: false mandatory: true
models.dev 的 reasoning_options [{"type":"toggle"}](二元可控) [](有推理但无控制手段)
MiniMax 官方仓库 issue #121 "M2.7 不允许关闭思考",无官方回复

结论:M2.x 的推理是模型固有属性,不是参数没找对。 任何库层改动都无法让它关闭;唯一诚实的做法是如实报错。

2.5 输出长度不是有效判别量(2026-08-02 e2e 补测,各 15 轮)

初版判据用 completion_tokens 阈值区分推理开关,被自己的数据证伪:

档位 completion_tokens 观测范围 reasoning_tokens
关闭(reasoning_effort=none 4 46 15/15 轮为 None
开启(medium 13 186 15/15 轮 > 0

两档的输出长度分布重叠:关闭档偶尔到 46(模型没照做「只输出两个数字」,把解题过程写进了正文——那是正文不是推理);开启档最低到 13(medium 档想得少的轮次)。按长度阈值判,两个方向都会误判。

reasoning_tokens 在同一批 30 轮里干净分开。这条对下游同样成立:想判断某次调用是否发生了推理,只能看 reasoning_tokens,不能看输出长度。

另有一个不含魔数的确定性锚点:同一模型上关闭档的 prompt_tokens 严格小于开启档(实测 194 < 207),因为供应商在开启时向模板注入了推理指令。这是相对比较,供应商改模板也不会失效。

2.4 M3 的稳定性

同一请求打 10 次,(prompt_tokens, 是否上报 ctd) 全部为 (194, False),零跳变——enable_thinking=False 的修复可以建立在 M3 上。

3. qwen / deepseek:现有 profile 正确

模型 enable_thinking=false thinking:{disabled} reasoning_effort=none 现有 profile
qwen3.7-plus 关闭(compl 5 关闭 关闭 enable_thinking正确
deepseek-v4-pro 无效(仍推理 198 关闭(compl 3 关闭 thinking:{type}正确

两点附带事实:

  • reasoning_effort=none 在三家都有效,但这很可能是中转做了参数归一化。不可据此认为可以统一发一个参数——下游若直连供应商官方端点,该假设大概率不成立。翻译表必须一家一行。
  • qwen 的 strip_think_tags=True 已过时:实测 qwen 走 reasoning_content 字段,正文中无 <think> 标签。无害,但属于死代码。
  • 非流式没有 400DashScope 系"enable_thinking 仅支持流式"的限制经中转不存在。直连时是否仍存在未验证。

4. new-api 中转的三个行为(会污染观测)

这一节对任何经中转做实测的场景都适用,值得单独记住。

a)不校验参数值。 reasoning_effort="xyzzy" 返回 200 并当作"开推理"处理。意味着"靠上游报错兜底"的设计模式在此失效——Bedrock 式的"最小交集 + 裸逃生口"在这里等于零保护。

b)静默丢弃未知键。 默认路径是 struct round-tripConvertRequest 返回 struct 再 json.Marshal),未知键在第一次序列化就消失。new-api 有 per-channel 的 pass_through_body_enabled 开关可改变此行为。

(c)上游不返回 usage 时用本地 tokenizer 补算并整体替换。 补算出的 usage 只有三个标量,completion_tokens_details 为零值。这直接解释了实测中的双峰现象:

现象 解释
同一请求 10 次:prompt=74 者 6 次不上报 reasoning_tokensprompt=72 者 4 次上报,从不交叉 74 = 本地估算值,72 = 上游真值;补算路径吃掉了 ctd

这不是多渠道路由(MiniMax 侧为单渠道单密钥),也不是配置错误,而是上游偶发不返回 usage 时的兜底逻辑。中转日志中的 local_count_tokens 标志可现场确认。

对库的直接影响reasoning_tokens 缺失不能解释为"该源不上报这个字段",只能解释为"本次调用未上报"。下游若按前者建立统计口径会算错。

5. 业界如何建模"同一语义、形态因模型而异"

调研覆盖 LiteLLM、OpenRouter、models.dev、LangChain、Vercel AI SDK、AWS Bedrock Converse、Portkey、Helicone、LlamaIndex、new-api/one-api。

5.1 核心共识:形态按 provider,能力按 model

概念 变化频率 应归属层次
形态:参数长什么样(enable_thinking / thinking.type / reasoning_effort 协议方言,一个供应商数年不变 provider 级
能力:能否关闭、有几档、默认开不开 模型属性,同一供应商每代都变 model 级

注册单位的分布很能说明问题:LiteLLM2986 条目)、models.dev5949 条)、LangChain、OpenRouter(细到 endpoint)、Helicone 全部下沉到 model 级;仍停在 provider 级的只有 Portkey 与 LlamaIndex,而这两家恰是失败语义最差的两家(均静默丢弃)。二者相关不是偶然:注册单位不够细,就只能靠"表里没有 = 不发"来兜底,而这正是静默失效的成因。

5.2 失败语义的四种谱系

语义 代表 适用前提
默认报错 + 可配置降级开关 LiteLLMUnsupportedParamsError + drop_params 有 model 级能力表可依据
软降级 + 显式 warning 通道 Vercel AI SDK(丢弃参数并 push warnings[] 调用方愿意读 warning
静默忽略 + 可选路由过滤 OpenRouter(默认忽略;require_parameters:true 改为排除不支持的上游) 网关自己拥有路由权
硬失败(透传给上游报错) BedrockinferenceConfig 4 字段交集 + additionalModelRequestFields 裸透传) 上游会诚实报错

选型时先问"我的上游会不会诚实报错"。若不会(如本项目的中转),最后一种直接出局,静默类也不能选。

5.3 表会过期,这是公理

LiteLLM 有过真实事故(issue #27351gpt-5.1-mini 漏登记导致 temperature 被误拒)。它的应对是两种相反极性,值得直接借鉴:

  • opt-in 能力(用错会 400 或悄悄花钱):未登记 → 视作不支持 → 拒绝
  • opt-out 能力(多半支持,误拒代价大):未登记 → 放行 → 只有表里显式写 false 才拒

维护方式上,LiteLLM/models.dev 靠社区 PR + CI 校验,LangChain 靠"上游拉取 + 本地增补 + 代码生成"。对内部库而言唯一现实的答案是:谁实测出来谁登记,登记必须附实测证据与日期。

5.4 「布尔开关 → 多档旋钮」无语义共识

系统 effort → 预算的换算
LiteLLM 一组 2 的幂(1024/2048/4096/8192/16384),全部可用环境变量覆盖;gemini 各型号还另有分叉
OpenRouter max_tokens 的百分比(≈80%/50%/20%
Helicone 一律 max_tokens/2,完全不看档位
LangChain 明确不保证跨 provider 可比

唯一对齐的是"关"none / disabled / thinking:{type:"disabled"} / OpenRouter effort:"none" 语义一致。"开"那一端没有任何标准。

工程共识只有一条:这个映射必须是可覆盖的常量,不是可推导的公式。 业界所有人都在拍脑袋,区别只在拍完让不让调用方改。

5.5 Vercel AI SDK 的一处设计值得单记

它的推理档位枚举里有一个 'provider-default',与 'none'(明确关闭)严格区分。这与本库 enable_thinking 的三态(None 不干预 / True / False)是同一思想——"调用方不表态"必须是一个独立的值,不能与任何具体档位混同。本库这一点原本就做对了,应保持。

6. 附带发现(不属本次范围,建议另立 issue)

kimi-k3 拒绝 temperature=0:返回 400 invalid temperature: only 1 is supported(另有渠道回 only 0.6)。本库把 400 归入 RequestRejectedError——不重试、不换源。若下游统一下发 temperature=0,此类源会 100% 硬失败。这与本次两条 issue 同源:供应商能力差异未被建模

中转渠道可用性会波动:kimi 渠道在 429 后被中转下线,随后返回 404 Model not supported by any channel。任何依赖真实 API 的测试都必须容忍源不可用(跳过并给出明确原因),而不是失败。

7. 未能证实

  1. MiniMax 官方文档对 reasoning_effort 的一手定义:官方文档站三次抓取均失败。M2.x 关不掉有三处佐证,但官方原文未取得。另有二手来源称 MiniMax 原生开关是 thinking:{type:"adaptive"/"disabled"}——该说法已被本次实测证伪(M2.7/M2.5 上两种写法均无效),但"中转是否对 reasoning_effort 做了改写"仍未排除。直连官方端点复测可彻底澄清。
  2. qwen 直连 DashScope 时非流式 enable_thinking 是否仍报 400:仅验证了经中转的行为。
  3. new-api 走本地补算的确切触发条件:读到了补算分支与 local_count_tokens 标记,未逐条比对所有渠道类型。双峰现象与该解释高度吻合,但未在日志中直接验证。
  4. 能力表条目对非本次实测模型的正确性qwen / deepseek 只测了各一个型号,同系其他型号未验证。

8. 对后续开发的指导

  1. 判定参数是否生效,优先看 prompt_tokens 而非输出长度(§1)。
  2. 排除"无效值被静默丢弃"必须做反证实验:传一个乱码值,看它的行为是否与目标值不同(§2.2)。
  3. 经中转做的任何实测都要标注"经中转,直连未验证",并写进注释(§3、§7)。
  4. 新增供应商或模型前,先查 OpenRouter /api/v1/models 与 models.dev——它们的登记与本次实测 100% 吻合,可作为低成本预判,但不可作为运行时依赖。
  5. 能力表条目必须附实测证据与日期;表过期是必然事件,退化路径与漂移检测要一起设计(§5.3)。
  6. reasoning_tokens 缺失只能记 None,绝不可记 0(§4c)——"观测不到"与"没发生"是两件事。
  7. 判断"是否发生了推理"只能看 reasoning_tokens,不能看输出长度(§2.5)——两档的 completion_tokens 分布是重叠的,长度阈值两个方向都会误判。