Merge pull request #11455 from colesmcintosh/429-fireworks-mapping

Fix Fireworks AI rate limit exception mapping - detect "rate limit" text in error messages
This commit is contained in:
Cole McIntosh 2025-06-06 15:06:15 -06:00 • committed by GitHub
commit 1ceb9f9621
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 128 additions and 2 deletions

View file

@ -24,6 +24,28 @@ from ..exceptions import (
)
class ExceptionCheckers:
"""
Helper class for checking various error conditions in exception strings.
"""
@staticmethod
def is_error_str_rate_limit(error_str: str) -> bool:
"""
Check if an error string indicates a rate limit error.
Args:
error_str: The error string to check
Returns:
True if the error indicates a rate limit, False otherwise
"""
if not isinstance(error_str, str):
return False
return "429" in error_str or "rate limit" in error_str.lower()
def get_error_message(error_obj) -> Optional[str]:
"""
OpenAI Returns Error message that is nested, this extract the message
@ -274,7 +296,7 @@ def exception_type( # type: ignore # noqa: PLR0915
+ "Exception"
)
if "429" in error_str:
if ExceptionCheckers.is_error_str_rate_limit(error_str):
exception_mapping_worked = True
raise RateLimitError(
message=f"RateLimitError: {exception_provider} - {message}",
@ -451,6 +473,15 @@ def exception_type( # type: ignore # noqa: PLR0915
response=getattr(original_exception, "response", None),
litellm_debug_info=extra_information,
)
elif original_exception.status_code == 500:
exception_mapping_worked = True
raise InternalServerError(
message=f"InternalServerError: {exception_provider} - {message}",
model=model,
llm_provider=custom_llm_provider,
response=getattr(original_exception, "response", None),
litellm_debug_info=extra_information,
)
elif original_exception.status_code == 503:
exception_mapping_worked = True
raise ServiceUnavailableError(

View file

@ -773,7 +773,7 @@ def test_litellm_predibase_exception():
@pytest.mark.parametrize(
"provider", ["predibase", "vertex_ai_beta", "anthropic", "databricks", "watsonx"]
"provider", ["predibase", "vertex_ai_beta", "anthropic", "databricks", "watsonx", "fireworks_ai"]
)
def test_exception_mapping(provider):
"""
@ -819,6 +819,98 @@ def test_exception_mapping(provider):
pass
def test_fireworks_ai_exception_mapping():
"""
Comprehensive test for Fireworks AI exception mapping, including:
1. Standard 429 rate limit errors
2. Text-based rate limit detection (the main issue fixed)
3. Generic 400 errors that should NOT be rate limits
4. ExceptionCheckers utility function
Related to: https://github.com/BerriAI/litellm/pull/11455
Based on Fireworks AI documentation: https://docs.fireworks.ai/tools-sdks/python-client/api-reference
"""
import litellm
from litellm.llms.fireworks_ai.common_utils import FireworksAIException
from litellm.litellm_core_utils.exception_mapping_utils import ExceptionCheckers
# Test scenarios covering all important cases
test_scenarios = [
{
"name": "Standard 429 rate limit with proper status code",
"status_code": 429,
"message": "Rate limit exceeded. Please try again in 60 seconds.",
"expected_exception": litellm.RateLimitError,
},
{
"name": "Status 400 with rate limit text (the main issue fixed)",
"status_code": 400,
"message": '{"error":{"object":"error","type":"invalid_request_error","message":"rate limit exceeded, please try again later"}}',
"expected_exception": litellm.RateLimitError,
},
{
"name": "Status 400 with generic invalid request (should NOT be rate limit)",
"status_code": 400,
"message": '{"error":{"type":"invalid_request_error","message":"Invalid parameter value"}}',
"expected_exception": litellm.BadRequestError,
},
]
# Test each scenario
for scenario in test_scenarios:
mock_exception = FireworksAIException(
status_code=scenario["status_code"],
message=scenario["message"],
headers={}
)
try:
response = litellm.completion(
model="fireworks_ai/llama-v3p1-70b-instruct",
messages=[{"role": "user", "content": "Hello"}],
mock_response=mock_exception,
)
pytest.fail(f"Expected {scenario['expected_exception'].__name__} to be raised")
except scenario["expected_exception"] as e:
if scenario["expected_exception"] == litellm.RateLimitError:
assert "rate limit" in str(e).lower() or "429" in str(e)
except Exception as e:
pytest.fail(f"Expected {scenario['expected_exception'].__name__} but got {type(e).__name__}: {e}")
# Test ExceptionCheckers.is_error_str_rate_limit() method directly
# Test cases that should return True (rate limit detected)
rate_limit_strings = [
"429 rate limit exceeded",
"Rate limit exceeded, please try again later",
"RATE LIMIT ERROR",
"Error 429: rate limit",
'{"error":{"type":"invalid_request_error","message":"rate limit exceeded, please try again later"}}',
"HTTP 429 Too Many Requests",
]
for error_str in rate_limit_strings:
assert ExceptionCheckers.is_error_str_rate_limit(error_str), f"Should detect rate limit in: {error_str}"
# Test cases that should return False (not rate limit)
non_rate_limit_strings = [
"400 Bad Request",
"Authentication failed",
"Invalid model specified",
"Context window exceeded",
"Internal server error",
"",
"Some other error message",
]
for error_str in non_rate_limit_strings:
assert not ExceptionCheckers.is_error_str_rate_limit(error_str), f"Should NOT detect rate limit in: {error_str}"
# Test edge cases
assert not ExceptionCheckers.is_error_str_rate_limit(None) # type: ignore
assert not ExceptionCheckers.is_error_str_rate_limit(42) # type: ignore
def test_anthropic_tool_calling_exception():
"""
Related - https://github.com/BerriAI/litellm/issues/4348
@ -1285,3 +1377,6 @@ async def test_exception_bubbling_up(sync_mode, stream_mode, model):
assert exc_info.value.code == "invalid_value"
assert exc_info.value.param is not None
assert exc_info.value.type == "invalid_request_error"