diff --git a/recipes/ai.py b/recipes/ai.py index e0fbb15..c06f3c4 100644 --- a/recipes/ai.py +++ b/recipes/ai.py @@ -237,8 +237,9 @@ def map_api_error(error): if isinstance(error, openai.APITimeoutError): return AIError("Die KI hat zu lange gebraucht. Bitte versuche es gleich noch einmal.") if isinstance(error, openai.RateLimitError): - code = getattr(error, "code", None) or "" - if "quota" in str(code) or "quota" in str(error).lower(): + # Out of credit is also a 429; OpenAI words it as "quota", "credits" or "billing". + details = f"{getattr(error, 'code', None) or ''} {error}".lower() + if any(word in details for word in ("quota", "credit", "billing")): return AIError("Das OpenAI-Guthaben ist aufgebraucht. Bitte den Admin informieren.") return AIError("Die KI ist gerade ausgelastet (Rate-Limit). Bitte in einer Minute erneut versuchen.") if isinstance(error, (openai.AuthenticationError, openai.PermissionDeniedError)): diff --git a/tests/test_ai.py b/tests/test_ai.py index 85c0fdb..d452843 100644 --- a/tests/test_ai.py +++ b/tests/test_ai.py @@ -108,6 +108,9 @@ def api_error(cls, status, message="error", body=None): (openai.APITimeoutError(request=REQUEST), "zu lange"), (api_error(openai.RateLimitError, 429, "Rate limit reached"), "ausgelastet"), (api_error(openai.RateLimitError, 429, "You exceeded your current quota", {"code": "insufficient_quota"}), "Guthaben"), + # Real-world message seen from the API (2026-10) for an account without credit + (api_error(openai.RateLimitError, 429, "Error code: 429 - You have no credits remaining. Add credits to " + "continue using the API at https://platform.openai.com/settings/organization/billing/."), "Guthaben"), (api_error(openai.AuthenticationError, 401), "API-Key"), (api_error(openai.NotFoundError, 404), "OPENAI_MODEL"), (api_error(openai.BadRequestError, 400), "nicht verarbeiten"),