feat: give zhipu, moonshot, anthropic and google a wire of their own

Eight segments now, and each one holds a ThinkingWire instead of two
fixed fragments: off, on_base, and the key a tier gets written to. The
two fragments could not say "on, at this depth", which is what every
current generation model wants.

Two deliberate behaviour changes fall out of it. The openai segment stops
reporting its shape as unknown — reasoning_effort is OpenAI's own field,
not a vendor dialect, so a compatible endpoint behind the gateway takes
it. And minimax's on-tier stops carrying a hardcoded medium: that was the
library picking a price for the caller, and medium is not even a tier GLM,
kimi or deepseek serve.

The issue #5 guards stay; their sample moves from "the openai segment" to
an explicitly registered unknown one, which is what they always meant to test.
This commit is contained in:
2026-09-05 00:42:58 -04:00
parent 2a50ddcf12
commit 7fabc792b2
7 changed files with 247 additions and 90 deletions
+1
View File
@@ -78,6 +78,7 @@ __all__ = [
"ThinkingCapability",
"ThinkingObservation",
"ThinkingUnsupportedError",
"ThinkingWire",
"TransientError",
"__version__",
"gather_bounded",
+114 -42
View File
@@ -14,75 +14,147 @@ from types import MappingProxyType
from typing import Any
@dataclass(frozen=True)
class ThinkingWire:
"""一个 provider 表达"开/关/多深"的请求体形态(设计 §3.3)。
三个字段各自的 `None` **语义互不重叠**,混淆任意两个都会退回 issue #5 修掉的
那种静默失效:
============== ==========================================================
``on_base=None`` **形态未知**: 本库不知道该 provider 如何表达"",配了开关
即装配期报错并指路 `register_provider`/`extra_body`
``off=None`` 已知开启形态,但**没有关闭形态**(该 provider 关不掉)
``effort_key`` ``None`` = 该 provider 只有开关、没有档位(qwen 系靠
``=None`` ``thinking_budget`` 调深度,不是档位)
============== ==========================================================
`on_base={}` 与 `on_base=None` 同样不可混: 前者是"已知无需注入任何参数即处于
开启档"(经网关的 OpenAI 兼容路径正是如此——档位由 `effort_key` 单独附加),
后者是"不知道怎么表达"
**为什么不是 cherry-studio 那套 wire DSL**: 它要支持 openai-chat /
openai-responses / anthropic-messages / google-generate-content 四种端点协议,
故需要 closed operation 集合与 `budgetWire` 代际变体。本库只有一个 OpenAI 兼容
transport,跨协议转换由 new-api 在服务端完成(它自己就有一层 canonical intent),
一个协议一层形态即够(P1 YAGNI)。
"""
off: Mapping[str, Any] | None
on_base: Mapping[str, Any] | None
effort_key: str | None
@dataclass(frozen=True)
class ProviderProfile:
"""单个 provider 的能力与差异声明。
thinking_on/thinking_off 分别是 `SourceConfig.enable_thinking` 为
True/False 时并入请求体的参数片段(`enable_thinking` 为 None 时二者都不
注入,用模型默认);strip_think_tags 声明响应 content 需剥离 ``<think>``
标签(qwen 系);supports_native_schema 供 D14 阶梯选择原生 response_format。
`thinking` 声明推理参数的**形态**(按 provider 变,数年不变一次);
`strip_think_tags` 声明响应 content 需剥离 ``<think>`` 标签(qwen 系);
`supports_native_schema` 供 D14 阶梯选择原生 response_format。
两档各有三种取值,**语义互不重叠**(issue #5):
========== ==========================================================
``{...}`` 已知的注入片段
``{}`` 已知**无需注入**任何参数即处于该档
``None`` **未知**: 本库不知道该 provider 如何表达这一档
========== ==========================================================
`None` 与 `{}` 必须分开: 二者曾同为空字典,导致 `enable_thinking=False`
对 minimax/openai 源静默失效——调用方以为关掉了推理,实际什么都没发生。
现在 `None` 会在装配期显式报错并指路 `register_provider` / `extra_body`。
注: 本类只声明**形态**(参数长什么样,按 provider 变);某个具体模型能否
关闭推理属**能力**(按 model 变),见 `ThinkingCapability`。
注: 本类只声明**形态**(参数长什么样,按 provider 变);某个具体模型支持哪些
档位属**能力**(按 model 变),见 `thinking.ThinkingCapability`。二者合一在
provider 级表达不了代际差异——glm-5.2 能关而 glm-5.3 不能,形态却完全相同。
"""
name: str
thinking_on: Mapping[str, Any] | None
thinking_off: Mapping[str, Any] | None
thinking: ThinkingWire
strip_think_tags: bool
supports_native_schema: bool = False
# 经 new-api 中转的口径。四家参考实现(cherry-studio / OpenRouter / LiteLLM /
# new-api 自身)一致的结论: OpenAI 兼容端点上,档位一律走标准的 `reasoning_effort`,
# 跨协议转换(→ Claude 的 thinking、Gemini 的 thinkingConfig)由网关服务端完成。
DEFAULT_PROFILES: Mapping[str, ProviderProfile] = MappingProxyType(
{
# 注入片段出处: VT llm.py:130-144(开启形态)与 CHS invokers.py:230-238(关闭形态)
"qwen": ProviderProfile(
name="qwen",
thinking_on={"enable_thinking": True},
thinking_off={"enable_thinking": False},
thinking=ThinkingWire(
off={"enable_thinking": False},
on_base={"enable_thinking": True},
# 百炼的深度控制是 `thinking_budget`(token 预算)而非档位;
# 预算型控制本库当前不支持(设计 §11 明确不做)
effort_key=None,
),
strip_think_tags=True,
),
# 官方 thinking_mode 文档: thinking:{type} 是开关,reasoning_effort 是深度,
# V4 一代两者并用(deepseek-v4-* 的档位见能力表)
"deepseek": ProviderProfile(
name="deepseek",
thinking_on={"thinking": {"type": "enabled"}},
thinking_off={"thinking": {"type": "disabled"}},
thinking=ThinkingWire(
off={"thinking": {"type": "disabled"}},
on_base={"thinking": {"type": "enabled"}},
effort_key="reasoning_effort",
),
strip_think_tags=False,
),
# OpenAI 兼容基线段名: 实践中被复用为**任意**兼容厂商的兜底(下游把
# kimi-k3 挂在 provider=openai 下),故不能下发任何厂商方言参数——发给
# 不认识它的厂商会 400。两档标 None(未知): 配了 enable_thinking 即在
# 装配期报错并指路,真 OpenAI 推理模型的用户走 register_provider
"openai": ProviderProfile(
name="openai",
thinking_on=None,
thinking_off=None,
# issue #20。智谱官方迁移建议原文: 原先用 {"type":"disabled"} 的应改为
# {"type":"enabled"} + reasoning_effort="low"——GLM-5.3 起 thinking.type
# 不再接受 disabled,故"关"这一档由能力表按型号裁定(5.2 能关,5.3 不能)
"zhipu": ProviderProfile(
name="zhipu",
thinking=ThinkingWire(
off={"thinking": {"type": "disabled"}},
on_base={"thinking": {"type": "enabled"}},
effort_key="reasoning_effort",
),
strip_think_tags=False,
),
# 注入形态出处: 2026-08-02 经自建 new-api 中转实测(findings §2),
# 2026-08-25 复测结论不变(findings 2026-08-25 §5);**直连官方端点未验证**。
# 实测 enable_thinking / thinking 两种写法均被静默丢弃(prompt_tokens
# 恒定等于基线 194),reasoning_effort 才是真开关——本片段的选型据此成立。
# "开"取 medium: qwen 的 enable_thinking:true 与 deepseek 的
# thinking:{enabled} 都不指定预算、由模型自定,medium 是五档里语义最接近
# "厂商正常强度"的一档;取 high 等于替下游做"加钱换质量"的业务判断。
# 要精确控制档位经 `SourceConfig.extra_body`(优先级高于本片段)
# kimi-k3 的档位是 low/high/max;thinking.type 为月之暗面的开关形态
"moonshot": ProviderProfile(
name="moonshot",
thinking=ThinkingWire(
off={"thinking": {"type": "disabled"}},
on_base={"thinking": {"type": "enabled"}},
effort_key="reasoning_effort",
),
strip_think_tags=False,
),
# 2026-08-02 经 new-api 中转实测(findings §2),2026-08-25 复测结论不变。
# enable_thinking / thinking 两种写法均被静默丢弃(prompt_tokens 恒等于基线
# 194),reasoning_effort 才是真开关——本段形态据此成立。
# `on_base={}`: "开"本身不需要任何参数,档位(若模型支持)由 effort_key 附加。
# 旧版此处硬编码 {"reasoning_effort": "medium"},那是替下游做的档位判断,
# 且 medium 在 GLM/kimi/deepseek 的档位表里根本不存在——正是本次要消灭的
"minimax": ProviderProfile(
name="minimax",
thinking_on={"reasoning_effort": "medium"},
thinking_off={"reasoning_effort": "none"},
thinking=ThinkingWire(
off={"reasoning_effort": "none"}, on_base={}, effort_key="reasoning_effort"
),
strip_think_tags=False,
),
# OpenAI 兼容基线段名: 实践中被复用为**任意**兼容厂商的兜底。两档此前标
# None(未知),因为当时无法区分"厂商方言"与"标准字段";`reasoning_effort`
# 是 OpenAI **官方**字段而非方言,发给经网关的兼容端点不会打到不认识它的
# 厂商,故 2026-09-04 起给出标准形态。真正形态未知的 provider 仍走
# register_provider 注册,而不是挂在本段下
"openai": ProviderProfile(
name="openai",
thinking=ThinkingWire(
off={"reasoning_effort": "none"}, on_base={}, effort_key="reasoning_effort"
),
strip_think_tags=False,
),
# Claude 5 系原生是 thinking.type=adaptive + output_config.effort,Gemini 3 系
# 原生是 thinkingConfig.thinkingLevel;两者的代际方言(Claude ≤4.5 的
# budget_tokens、Gemini 2.x 的 thinkingBudget)由 new-api 的 canonical intent
# 层吸收,本库只发 OpenAI 形态(设计 §3.4)
"anthropic": ProviderProfile(
name="anthropic",
thinking=ThinkingWire(
off={"reasoning_effort": "none"}, on_base={}, effort_key="reasoning_effort"
),
strip_think_tags=False,
),
"google": ProviderProfile(
name="google",
thinking=ThinkingWire(
off={"reasoning_effort": "none"}, on_base={}, effort_key="reasoning_effort"
),
strip_think_tags=False,
),
}
+3 -2
View File
@@ -315,8 +315,9 @@ def resolve_thinking(
# Phase 1: 调用方不表态 —— 与 False 严格区分,用模型默认档
if enable_thinking is None:
return {}
slot = profile.thinking_on if enable_thinking else profile.thinking_off
direction = "thinking_on" if enable_thinking else "thinking_off"
wire = profile.thinking
slot = wire.on_base if enable_thinking else wire.off
direction = "on_base" if enable_thinking else "off"
# Phase 2: 形态未知 —— 提供了开关却不知道怎么发,静默放行就是欺骗调用方
if slot is None:
raise ThinkingUnsupportedError(