feat: let a source name its reasoning tier, and say so when it contradicts itself

This commit is contained in:
2026-09-05 01:56:24 -04:00
parent ed563b9ca0
commit 603a835f60
4 changed files with 178 additions and 0 deletions
+26
View File
@@ -22,6 +22,7 @@ from loguru import logger
from polygateway.types import (
BackpressurePolicy,
BreakerConfig,
Effort,
GlobalLimits,
RetryPolicy,
SourceConfig,
@@ -43,6 +44,10 @@ _SOURCE_FIELDS: dict[str, tuple[str, str]] = {
"TTFT_TIMEOUT_S": ("ttft_timeout_s", "float"),
"INTER_TOKEN_TIMEOUT_S": ("inter_token_timeout_s", "float"),
"ENABLE_THINKING": ("enable_thinking", "bool"),
# 档位两键(issue #20);值域校验分工: 档位在此(解析即校验,报错点得出 env 键名),
# fallback 交给 SourceConfig 构造期(那道同时覆盖构造函数注入与 dataclasses.replace)
"REASONING_EFFORT": ("reasoning_effort", "effort"),
"EFFORT_FALLBACK": ("effort_fallback", "str"),
"MISSING_DONE": ("missing_done", "str"),
"TRUST_ENV": ("trust_env", "bool"),
"EXTRA_BODY": ("extra_body", "json"),
@@ -93,6 +98,8 @@ def _cast(raw: str, kind: str, key: str) -> object:
if lowered in ("0", "false", "no", "off"):
return False
raise ValueError(f"非法布尔值: {raw!r}")
if kind == "effort":
return _to_effort(raw)
if kind == "json":
# JSONDecodeError 是 ValueError 子类,复用下方的统一包装
parsed = json.loads(raw)
@@ -104,6 +111,25 @@ def _cast(raw: str, kind: str, key: str) -> object:
raise ValueError(f"配置 {key} 解析失败: {exc}") from exc
def _to_effort(raw: str) -> Effort:
"""把 env 字符串解成 `Effort`;越界时报错并**列出全部八档**。
列全八档不是啰嗦: 档位词汇是封闭的,而下游会照着别处的习惯写
(`lowest`/`off`/`disabled` 都出现过),只说"非法值"等于让人去翻源码。
`strip().lower()` 与 `bool` 分支同一先例: `.env` 里的行尾空格与大写写法
是常态,而档位取值本身没有大小写语义(`Effort` 的值全小写)。
"""
try:
return Effort(raw.strip().lower())
except ValueError:
# 不 `from exc`: 枚举原生的 "'lowest' is not a valid Effort" 只是同一
# 件事的英文复述,链上去反而把可操作的那句挤到后面
raise ValueError(
f"非法推理档位 {raw!r};允许: {', '.join(e.value for e in Effort)}"
) from None
def _first(env: Mapping[str, str], *keys: str) -> tuple[str, str] | None:
for key in keys:
raw = env.get(key)
+49
View File
@@ -18,6 +18,9 @@ from loguru import logger
_MISSING_DONE_DOMAIN = frozenset({"retry", "salvage"})
_EFFORT_FALLBACK_DOMAIN = frozenset({"error", "nearest"})
"""`SourceConfig.effort_fallback` 的值域: 请求档打空时报错,还是映射到最近的档。"""
_PROTECTED_OVERLAY_KEYS: Mapping[str, str] = MappingProxyType(
{
"model": "会让遥测记录的 model 与实际请求分叉,成本按错单价换算",
@@ -395,6 +398,10 @@ class SourceConfig:
限额闸 0 表示不启用;`enable_thinking` 三态: None=不注入(模型默认)、
True=注入开启参数、False=注入关闭参数(统一 VT 与 CHS 相反的现状)。
2026-09-04 起 `enable_thinking` 降级为 `reasoning_effort` 的语法糖
(`True`→`AUTO`、`False`→`NONE`),保留不删是因为它已被三项目消费
(迁移兼容约束,ARCH §5.1)。两个字段说的是同一件事,故矛盾即报错。
"""
name: str
@@ -419,10 +426,25 @@ class SourceConfig:
(加任何 mapping 字段的固有代价,裸 dict 亦然),库内无以源作 key 的写法;
要可变副本用 `dict(source.extra_body)`,要改字段用 `dataclasses.replace`。"""
reasoning_effort: Effort | None = None
"""本源默认的推理档位;None = 不表态(与 `Effort.NONE`「要求不推理」不同)。
**追加在末尾**是硬要求: 三项目的测试按位置构造 fake,插在中间会静默错位
(本模块头部 docstring 的字段保序约定)。"""
effort_fallback: str = "error"
"""请求档打空时的处置: `error`(默认,报错)或 `nearest`(映射到最近的档)。
默认报错的理由是钱: 一次静默的 `medium → max` 在 GLM-5.3 上是数倍账单
(P5「严禁默认值掩盖错误」)。值域在此把关而非交给 `resolve_thinking`——
后者对未知值是 fail-closed(按 `error` 处理),不会替配置兜错,漏判的结果
就是 `EFFORT_FALLBAK` 这种拼写错误静默失效。"""
def __post_init__(self) -> None:
self._validate_identity()
self._validate_gates()
self._validate_watchdog()
self._validate_thinking()
self._freeze_extra_body()
def effective_est_tokens(self) -> int:
@@ -460,6 +482,33 @@ class SourceConfig:
):
raise ValueError("看门狗不变式要求 0 < inter_token < ttft < timeout_s")
def _validate_thinking(self) -> None:
"""推理两键的值域与互不矛盾(issue #20 设计 §4.2)。
矛盾**报错而非「后者赢」**: `enable_thinking` 与 `reasoning_effort` 表达的是
同一件事,静默取其一等于替下游猜它到底想要哪个,而猜错的代价是账单——
猜成开启就是白花钱,猜成关闭就是拿到一个没推理过的答案。
判据是「二者是否都在说关闭」: `enable_thinking is False` 与
`reasoning_effort is NONE` 必须同真同假。`True` + 某个开启档(如 `low`)
不算矛盾,那只是把同一件事说了两遍,且后者更精确。
"""
if self.effort_fallback not in _EFFORT_FALLBACK_DOMAIN:
raise ValueError(
f"SourceConfig.effort_fallback(EFFORT_FALLBACK)非法值 "
f"{self.effort_fallback!r};允许: {sorted(_EFFORT_FALLBACK_DOMAIN)}"
)
if self.enable_thinking is None or self.reasoning_effort is None:
return
if (self.enable_thinking is False) != (self.reasoning_effort is Effort.NONE):
raise ValueError(
f"{self.name!r} 的 enable_thinking={self.enable_thinking}"
f"reasoning_effort={self.reasoning_effort.value!r} 相互矛盾: "
f"enable_thinking 已是 reasoning_effort 的语法糖"
f"(True={Effort.AUTO.value}、False={Effort.NONE.value})。"
f"请只保留其中一个,或让两者语义一致"
)
def _freeze_extra_body(self) -> None:
"""校验后转只读视图: 装配完成的源不应再被就地改采样参数(设计决策 E)。"""
validated = validate_request_overlay(