mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix: map missing OpenAI credentials to 401 instead of 500
The OpenAI SDK raises openai.OpenAIError (e.g. "Missing credentials") at client construction, before any HTTP request. These exceptions carry no status_code, and the broad exception handlers in openai.py defaulted the status to 500. Since 500 is retryable under standard retry policies, a permanent, client-side configuration error (missing api_key) got retried until attempts ran out, and the useful "Missing credentials" message arrived last, surfaced as InternalServerError. Add a _get_openai_exception_status_code helper that returns 401 for a pre-request openai.OpenAIError (no status_code), so it maps to a non-retryable AuthenticationError. Exceptions that already carry a status_code (a real HTTP response) are unchanged, and non-OpenAI exceptions keep the 500 default. Applied to the completion and streaming handlers. Fixes #35860 Co-authored-by: TRAE CLI <noreply@bytedance.com>
This commit is contained in:
parent
ead62528e6
commit
a1aa1528df
2 changed files with 77 additions and 3 deletions
|
|
@ -318,6 +318,31 @@ class OpenAIChatCompletionResponseIterator(BaseModelResponseIterator):
|
|||
raise e
|
||||
|
||||
|
||||
def _get_openai_exception_status_code(e: Exception) -> int:
|
||||
"""
|
||||
Determine the status code to attach to an ``OpenAIError`` raised from a
|
||||
caught exception.
|
||||
|
||||
The OpenAI SDK raises ``openai.OpenAIError`` (e.g. "Missing credentials")
|
||||
at *client construction*, before any HTTP request is made. Such exceptions
|
||||
carry no ``status_code``. Defaulting these to 500 asserts that a server
|
||||
responded with a server error, so the failure is treated as transient and
|
||||
retried even though it is a permanent, client-side configuration problem.
|
||||
|
||||
For these pre-request client errors we return 401 so they surface as a
|
||||
non-retryable ``AuthenticationError``. Any exception that already carries a
|
||||
``status_code`` (i.e. a real HTTP response happened) keeps that code.
|
||||
"""
|
||||
status_code = getattr(e, "status_code", None)
|
||||
if status_code is not None:
|
||||
return status_code
|
||||
if isinstance(e, openai.OpenAIError):
|
||||
# openai.OpenAIError with no status_code == raised before any HTTP
|
||||
# exchange (e.g. missing api_key), which is an auth/config problem.
|
||||
return 401
|
||||
return 500
|
||||
|
||||
|
||||
class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
|
@ -797,7 +822,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
|
|||
except OpenAIError as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
status_code: Final = getattr(e, "status_code", 500)
|
||||
status_code: Final = _get_openai_exception_status_code(e)
|
||||
error_headers = getattr(e, "headers", None)
|
||||
error_text: Final = getattr(e, "text", str(e))
|
||||
error_response: Final = getattr(e, "response", None)
|
||||
|
|
@ -920,7 +945,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
|
|||
# e.message
|
||||
except Exception as e:
|
||||
exception_response = getattr(e, "response", None)
|
||||
status_code = getattr(e, "status_code", 500)
|
||||
status_code = _get_openai_exception_status_code(e)
|
||||
exception_body = getattr(e, "body", None)
|
||||
error_headers = getattr(e, "headers", None)
|
||||
if error_headers is None and exception_response:
|
||||
|
|
@ -1075,7 +1100,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
|
|||
raise e
|
||||
|
||||
error_headers = getattr(e, "headers", None)
|
||||
status_code = getattr(e, "status_code", 500)
|
||||
status_code = _get_openai_exception_status_code(e)
|
||||
error_response = getattr(e, "response", None)
|
||||
exception_body = getattr(e, "body", None)
|
||||
if error_headers is None and error_response:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
"""
|
||||
Tests for _get_openai_exception_status_code.
|
||||
|
||||
Regression test for https://github.com/BerriAI/litellm/issues/35860:
|
||||
a missing API key raises openai.OpenAIError at client construction (with no
|
||||
status_code). Defaulting that to 500 makes it a retryable InternalServerError,
|
||||
so a permanent credential/config error is retried instead of failing fast.
|
||||
It should map to 401 (AuthenticationError) instead.
|
||||
"""
|
||||
|
||||
import openai
|
||||
|
||||
from litellm.llms.openai.openai import _get_openai_exception_status_code
|
||||
|
||||
|
||||
class TestGetOpenAIExceptionStatusCode:
|
||||
def test_openai_error_without_status_code_maps_to_401(self):
|
||||
"""openai.OpenAIError with no status_code (pre-request, e.g. missing key) -> 401."""
|
||||
err = openai.OpenAIError("Missing credentials. Please pass an `api_key`.")
|
||||
assert not hasattr(err, "status_code") or getattr(err, "status_code") is None
|
||||
assert _get_openai_exception_status_code(err) == 401
|
||||
|
||||
def test_existing_status_code_is_preserved(self):
|
||||
"""An exception that already carries a status_code keeps it (real HTTP response)."""
|
||||
|
||||
class FakeHTTPError(Exception):
|
||||
status_code = 429
|
||||
|
||||
assert _get_openai_exception_status_code(FakeHTTPError("rate limited")) == 429
|
||||
|
||||
def test_status_code_zero_is_preserved(self):
|
||||
"""A falsy-but-present status_code (0) is still honored, not defaulted."""
|
||||
|
||||
class ZeroStatus(Exception):
|
||||
status_code = 0
|
||||
|
||||
assert _get_openai_exception_status_code(ZeroStatus("weird")) == 0
|
||||
|
||||
def test_generic_exception_without_status_code_defaults_to_500(self):
|
||||
"""A non-OpenAI exception with no status_code keeps the 500 default."""
|
||||
assert _get_openai_exception_status_code(ValueError("boom")) == 500
|
||||
|
||||
def test_openai_error_subclass_with_status_code_kept(self):
|
||||
"""A real openai HTTP error (has status_code) is unchanged."""
|
||||
|
||||
class FakeOpenAIHTTPError(openai.OpenAIError):
|
||||
status_code = 400
|
||||
|
||||
assert _get_openai_exception_status_code(FakeOpenAIHTTPError("bad request")) == 400
|
||||
Loading…
Add table
Reference in a new issue