feat(backends): map LLM auth/rate-limit errors to backend-neutral errors

Closes the error-code gap between LangGraph and FastGPT backends: the
endpoint's 401/429 branches previously could never trigger under the
LangGraph backend because all provider failures collapsed into LLMAPIError
(500).

- agent/llm: LLMAuthError / LLMRateLimitError as LLMAPIError subclasses;
  OpenAILLMService raises them from openai AuthenticationError (401, no
  retry) and RateLimitError (429, after bounded retries exhausted)
- backends/langgraph: _complete_turn translates LLMAuthError ->
  ChatBackendAuthenticationError and LLMRateLimitError ->
  ChatBackendRateLimitError; stream reuses complete() so the mapping
  applies to both paths; other errors (timeout etc.) propagate unchanged
- endpoint untouched: existing 401/429 response branches now fire for
  both backends

Verified: 212 unit tests (7 new error-mapping tests)
This commit is contained in:
Eric Wang
2026-10-22 21:50:56 +08:00
parent 2bbbaf07af
commit ca73b1e8c5
3 changed files with 185 additions and 11 deletions
+24 -1
View File
@@ -27,6 +27,16 @@ class LLMAPIError(LLMError):
"""provider 返回错误或无效响应。"""
class LLMAuthError(LLMAPIError):
"""provider 拒绝凭证(401)。不可重试,需区分上报(对照
FastGPT 后端的 ChatBackendAuthenticationError 语义)。"""
class LLMRateLimitError(LLMAPIError):
"""provider 限流(429)。重试耗尽后抛出,需区分上报(对照
FastGPT 后端的 ChatBackendRateLimitError 语义)。"""
class LLMService(Protocol):
"""节点依赖的最小 LLM 接口。"""
@@ -128,7 +138,12 @@ class OpenAILLMService:
) -> str:
import asyncio
from openai import APIConnectionError, APITimeoutError, RateLimitError
from openai import (
APIConnectionError,
APITimeoutError,
AuthenticationError,
RateLimitError,
)
client = self._get_client()
messages = [
@@ -163,7 +178,15 @@ class OpenAILLMService:
retries += 1
await asyncio.sleep(min(2 ** retries, 10))
continue
if isinstance(exc, RateLimitError):
# 限流耗尽:区分上报,API 层映射为 429(两后端错误码等价)
raise LLMRateLimitError(
f"LLM provider 限流(重试 {self._max_retries} 次后仍失败)"
) from exc
raise LLMAPIError(f"LLM provider 不可用:{type(exc).__name__}") from exc
except AuthenticationError as exc:
# 401 鉴权失败:不可重试,区分上报,API 层映射为 401
raise LLMAuthError("LLM provider 认证失败") from exc
except Exception as exc:
# 其他错误(如 4xx 参数错)不可重试
raise LLMAPIError(f"LLM 调用失败:{type(exc).__name__}") from exc