fix(retry): recognize ZhiPu 1302 rate-limit error for retry

ZhiPu API returns code 1302 with Chinese text "速率限制" instead of
standard HTTP 429 + "rate limit", causing the retry engine to treat
it as non-transient and fail immediately.
This commit is contained in:
chengyongru
2026-04-21 21:23:20 +08:00
committed by Xubin Ren
parent 1b692debdc
commit 37ea8b8f5b
2 changed files with 52 additions and 0 deletions
+2
View File
@@ -108,6 +108,7 @@ class LLMProvider(ABC):
"connection",
"server error",
"temporarily unavailable",
"速率限制",
)
_RETRYABLE_STATUS_CODES = frozenset({408, 409, 429})
_TRANSIENT_ERROR_KINDS = frozenset({"timeout", "connection"})
@@ -154,6 +155,7 @@ class LLMProvider(ABC):
"temporarily unavailable",
"overloaded",
"concurrency limit",
"速率限制",
)
_SENTINEL = object()
+50
View File
@@ -546,6 +546,56 @@ async def test_chat_with_retry_normalizes_explicit_none_max_tokens() -> None:
assert provider.last_kwargs["temperature"] == 0.7
@pytest.mark.asyncio
async def test_chat_with_retry_retries_zhipu_1302_rate_limit(monkeypatch) -> None:
"""ZhiPu returns code 1302 with Chinese rate-limit text instead of HTTP 429."""
provider = ScriptedProvider([
LLMResponse(
content='Error: {\'code\': \'1302\', \'message\': \'您的账户已达到速率限制,请您控制请求频率\'}',
finish_reason="error",
),
LLMResponse(content="ok"),
])
delays: list[float] = []
async def _fake_sleep(delay: float) -> None:
delays.append(delay)
monkeypatch.setattr("nanobot.providers.base.asyncio.sleep", _fake_sleep)
response = await provider.chat_with_retry(messages=[{"role": "user", "content": "hello"}])
assert response.content == "ok"
assert provider.calls == 2
assert delays == [1]
@pytest.mark.asyncio
async def test_chat_with_retry_retries_zhipu_1302_with_429_status(monkeypatch) -> None:
"""ZhiPu 1302 error with HTTP 429 status should also retry."""
provider = ScriptedProvider([
LLMResponse(
content='Error: {\'code\': \'1302\', \'message\': \'您的账户已达到速率限制,请您控制请求频率\'}',
finish_reason="error",
error_status_code=429,
error_code="1302",
),
LLMResponse(content="ok"),
])
delays: list[float] = []
async def _fake_sleep(delay: float) -> None:
delays.append(delay)
monkeypatch.setattr("nanobot.providers.base.asyncio.sleep", _fake_sleep)
response = await provider.chat_with_retry(messages=[{"role": "user", "content": "hello"}])
assert response.content == "ok"
assert provider.calls == 2
assert delays == [1]
@pytest.mark.asyncio
async def test_chat_stream_with_retry_normalizes_explicit_none_max_tokens() -> None:
"""chat_stream_with_retry must apply the same None-guard as chat_with_retry."""