feat: let one call ask for a different tier than its source defaults to

The three-layer priority (call > source > enable_thinking sugar > silence)
now lives in one pure function, thinking.effective_effort(). The assembly
guard and the request hot path used to each carry their own inline copy of
the sugar conversion; two copies of the same judgement drift into the worst
shape there is - passes at assembly, raises at runtime.

The guard now also honours effort_fallback, so a source that opted into
nearest is no longer sentenced at assembly for a tier it could have mapped.
This commit is contained in:
2026-09-05 02:15:25 -04:00
parent 603a835f60
commit 1f13eb18ab
5 changed files with 177 additions and 7 deletions
+33
View File
@@ -308,6 +308,39 @@ class ThinkingResolution:
applied_effort: Effort | None
def effective_effort(
*,
request_effort: Effort | None,
source_effort: Effort | None,
enable_thinking: bool | None,
) -> Effort | None:
"""求本次生效的档位: 请求级 > 源级 > `enable_thinking` 语法糖 > 不表态(设计 §4.2)。
**收口成一个纯函数**是本函数存在的全部理由: 装配守卫(`client._guard_thinking`)
与请求热路径(`openai_compat._build_payload`)必须给出**同一个**判定,两处各写
一份就地转换迟早会分叉,而分叉的形态是"装配期放行、运行期报错"——最难查的那种。
**一律用 `is None` 判有没有表态,不靠真值性**: `Effort.NONE`(要求不推理)与
`enable_thinking=False` 都是**表态**而非缺省,`x or y` 式的回落会把后者当成没配
从而跳到下一层——那正是本次要消灭的静默失效。
语法糖排在最末且 `True → AUTO`(开启但不指定强度,不依赖能力表),不是旧版那个
硬编码的 `medium`: 那是库替下游做的档位判断,而 `medium` 在 GLM/kimi/deepseek 的
档位表里根本不存在(设计 §4.2 声明过的有意变更)。
同源同时配 `enable_thinking` 与 `reasoning_effort` 且语义矛盾,已由
`SourceConfig.__post_init__` 在构造期报错,故这里不再判——两个字段说同一件事时,
矛盾是配置错误,不是优先级问题。
"""
if request_effort is not None:
return request_effort
if source_effort is not None:
return source_effort
if enable_thinking is None:
return None
return Effort.AUTO if enable_thinking else Effort.NONE
def resolve_thinking(
profile: ProviderProfile,
capability: ThinkingCapability | None,