fix: judge the phase 2 shape by the tier that was asked for

A provider that registered a disable form but no enable form was told
its shape was unknown and pointed at register_provider -- work it had
already done -- for a request that only ever needed the disable form.
The old bool code took the slot by direction; the tiered rewrite lost
that. Take the relevant field again, and keep "shape unknown" for the
case where both halves are missing, so the "cannot disable" wording
still owns the half-missing case.
This commit is contained in:
2026-09-05 04:14:29 -04:00
parent 1a35d515d9
commit 81a901144e
3 changed files with 65 additions and 12 deletions
+29 -11
View File
@@ -14,7 +14,7 @@ from typing import Any
from loguru import logger
from polygateway.providers import ProviderProfile
from polygateway.providers import ProviderProfile, ThinkingWire
from polygateway.types import EFFORT_ORDER, Effort, ThinkingObservation
@@ -391,7 +391,7 @@ def resolve_thinking(
return ThinkingResolution({}, None)
wire = profile.thinking
# Phase 2: 形态未知 —— 给了档位却不知道怎么发,静默放行就是欺骗调用方
if wire.on_base is None:
if _wire_unknown_for(wire, effort):
raise ThinkingUnsupportedError(
f"provider {profile.name!r} 的推理形态未知(模型 {model!r},请求档位 "
f"{effort.value!r}): 本库不知道该 provider 如何表达推理。请用 "
@@ -399,7 +399,7 @@ def resolve_thinking(
)
# Phase 3: 能力未登记 —— 新模型上线不该被库挡住,但也不该假装成功
if capability is None:
payload = _inject(profile, wire.on_base, effort, model=model)
payload = _inject(profile, effort, model=model)
if warn_unregistered:
_warn_unregistered(model, profile, effort, payload)
return ThinkingResolution(payload, effort)
@@ -409,7 +409,24 @@ def resolve_thinking(
raise ThinkingUnsupportedError(_cannot_disable(model, capability))
# Phase 5: 档位打空 —— 报错或按 fallback 映射(auto 例外,见 docstring)
applied = _settle_tier(effort, capability, model=model, fallback=fallback)
return ThinkingResolution(_inject(profile, wire.on_base, applied, model=model), applied)
return ThinkingResolution(_inject(profile, applied, model=model), applied)
def _wire_unknown_for(wire: ThinkingWire, effort: Effort) -> bool:
"""Phase 2 的判据: **按请求档取相关字段**,不是一律看 `on_base`。
旧版 `slot = thinking_on if enable_thinking else thinking_off` 即如此。只看
`on_base` 会让"关闭形态已知、开启形态未知"的自定义 provider 在请求 `none` 时
被误拒,且指向它已经做过的 `register_provider`(设计 §2 处置表第 2 条)。
请求 `none` 时判据是**两者皆 None**,而不是单看 `off`: `ThinkingWire` 的三个
`None` 语义互不重叠——`off is None` 而 `on_base` 已知是"该 provider 关不掉"
(由 `_inject` 说清是缺了哪半边),只有两者皆 None 才是"整个形态未知",此时
指路 `register_provider` 才是对的方向。
"""
if effort is not Effort.NONE:
return wire.on_base is None
return wire.off is None and wire.on_base is None
def _settle_tier(
@@ -437,13 +454,12 @@ def _settle_tier(
return mapped
def _inject(
profile: ProviderProfile, on_base: Mapping[str, Any], effort: Effort, *, model: str
) -> Mapping[str, Any]:
def _inject(profile: ProviderProfile, effort: Effort, *, model: str) -> Mapping[str, Any]:
"""按 wire 把档位写成请求体片段;wire 表达不了这一档时报错。
`on_base` 由调用方传入而非在此重读,是为了让"Phase 2 已保证它不是 None"这条
前提在签名上看得见。
自己重读 `wire` 而不由调用方传 `on_base`: Phase 2 的判据按请求档取相关字段
(`none` 看 `off`,其余档看 `on_base`)之后,"on_base 一定不是 None"这条前提
只对非 `none` 档成立,写进签名反而是句假话。
三种 `None` 的语义在此**各自兑现**(ThinkingWire 的 docstring 定义了它们):
`off is None` = 该 provider 关不掉,`effort_key is None` = 它只有开关没有档位。
@@ -459,16 +475,18 @@ def _inject(
f"需要不推理请换用支持关闭的 provider 或模型"
)
return wire.off
# 非 none 档的开启形态由 Phase 2 保证已知(内部不变量,不承担生产校验)
assert wire.on_base is not None
if effort is Effort.AUTO:
# auto = 开启但不指定强度: 逐字节等于升级前的 `thinking_on`
return on_base
return wire.on_base
if wire.effort_key is None:
raise ThinkingUnsupportedError(
f"provider {profile.name!r} 只有推理开关、没有档位键(模型 {model!r}),"
f"表达不了 reasoning_effort={effort.value!r}: 请改用 auto/none 两档,"
f"或用 register_provider 给该 provider 注册 effort_key"
)
return {**on_base, wire.effort_key: effort.value}
return {**wire.on_base, wire.effort_key: effort.value}
def _cannot_disable(model: str, capability: ThinkingCapability) -> str: