fix: read a tier the way every config path actually spells it
Both public assembly paths took the tier on trust: a bare "none" from JSON or a hand-built SourceConfig stayed a str, and `is Effort.NONE` then read it as a contradiction and crashed on `.value` while wording the error -- the caller got an AttributeError where a ValueError was promised, and on the request side that unclassified exception walked straight through the transport's ThinkingUnsupportedError catch and the retry classifier. Normalize at the two entrances instead, matching what the .env path has always done, and let EFFORT_FALLBACK be spelled with the same freedom as its neighbour.
This commit is contained in:
@@ -275,6 +275,41 @@ class TestReasoningEffortPriority:
|
||||
assert "reasoning_effort" not in body
|
||||
|
||||
|
||||
class TestRequestTierNormalization:
|
||||
"""`chat(reasoning_effort=...)` 是公共入口,裸字符串必须在此归一(issue #20)。
|
||||
|
||||
两条装配路(工厂 / 构造函数全量注入,CLAUDE.md §4.5)与 `.env` 路的口径必须
|
||||
一致——后者早已是"解析即归一"。不归一的后果不是"少个类型注解"那么轻: 档位
|
||||
一路要被 `is Effort.NONE` 身份比较,裸字符串会在 transport 的错误路径上抛
|
||||
`AttributeError`,而它不属错误四分类,会穿透 `except ThinkingUnsupportedError`
|
||||
与 RetryMW 的分类捕获,以未分类异常冒出 `chat()`(2026-09-05 独立验证实测)。
|
||||
"""
|
||||
|
||||
def _capturing_client(self, captured, **overrides):
|
||||
def handler(request):
|
||||
captured.append(json.loads(request.content))
|
||||
return _sse()
|
||||
|
||||
return _client(handler=handler, **overrides)
|
||||
|
||||
async def test_bare_string_tier_reaches_the_wire(self):
|
||||
captured = []
|
||||
source = _source(provider="zhipu", model="glm-5.3")
|
||||
async with self._capturing_client(captured, sources=[source]) as client:
|
||||
await client.chat([{"role": "user", "content": "hi"}], reasoning_effort="max")
|
||||
assert captured[0]["reasoning_effort"] == "max"
|
||||
|
||||
async def test_illegal_tier_is_a_value_error_listing_the_vocabulary(self):
|
||||
"""非法档位是调用方编程错误: 当场 `ValueError`,不进洋葱、不成为未分类异常。"""
|
||||
source = _source(provider="zhipu", model="glm-5.3")
|
||||
async with _client(sources=[source]) as client:
|
||||
with pytest.raises(ValueError) as exc:
|
||||
await client.chat([{"role": "user", "content": "hi"}], reasoning_effort="lowest")
|
||||
message = str(exc.value)
|
||||
assert "chat(reasoning_effort=...)" in message
|
||||
assert all(tier.value in message for tier in Effort)
|
||||
|
||||
|
||||
class _MemoryRecorder:
|
||||
"""收下遥测行原样存起来;断言"哪些行被写了"必须能看到零行的情形。"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user