mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-03 11:18:50 +00:00
Added validate payload error (#12494)
* Added validate payload error * added logger * added test case
This commit is contained in:
@@ -5,6 +5,7 @@ import orjson
|
||||
from fastapi import Request, UploadFile, status
|
||||
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.proxy._types import ProxyException
|
||||
from litellm.types.router import Deployment
|
||||
|
||||
|
||||
@@ -44,8 +45,8 @@ async def _read_request_body(request: Optional[Request]) -> Dict:
|
||||
else:
|
||||
try:
|
||||
parsed_body = orjson.loads(body)
|
||||
except orjson.JSONDecodeError:
|
||||
# Fall back to the standard json module which is more forgiving
|
||||
except orjson.JSONDecodeError as e:
|
||||
# First try the standard json module which is more forgiving
|
||||
# First decode bytes to string if needed
|
||||
body_str = body.decode("utf-8") if isinstance(body, bytes) else body
|
||||
|
||||
@@ -61,15 +62,26 @@ async def _read_request_body(request: Optional[Request]) -> Dict:
|
||||
r"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]", "", body_str
|
||||
)
|
||||
|
||||
parsed_body = json.loads(body_str)
|
||||
try:
|
||||
parsed_body = json.loads(body_str)
|
||||
except json.JSONDecodeError:
|
||||
# If both orjson and json.loads fail, throw a proper error
|
||||
verbose_proxy_logger.error(f"Invalid JSON payload received: {str(e)}")
|
||||
raise ProxyException(
|
||||
message=f"Invalid JSON payload: {str(e)}",
|
||||
type="invalid_request_error",
|
||||
param="request_body",
|
||||
code=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Cache the parsed result
|
||||
_safe_set_request_parsed_body(request=request, parsed_body=parsed_body)
|
||||
return parsed_body
|
||||
|
||||
except (json.JSONDecodeError, orjson.JSONDecodeError):
|
||||
verbose_proxy_logger.exception("Invalid JSON payload received.")
|
||||
return {}
|
||||
except (json.JSONDecodeError, orjson.JSONDecodeError, ProxyException) as e:
|
||||
# Re-raise ProxyException as-is
|
||||
verbose_proxy_logger.error(f"Invalid JSON payload received: {str(e)}")
|
||||
raise
|
||||
except Exception as e:
|
||||
# Catch unexpected errors to avoid crashes
|
||||
verbose_proxy_logger.exception(
|
||||
|
||||
@@ -20,6 +20,7 @@ from litellm.proxy.common_utils.http_parsing_utils import (
|
||||
_safe_set_request_parsed_body,
|
||||
get_form_data,
|
||||
)
|
||||
from litellm.proxy._types import ProxyException
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -150,6 +151,92 @@ async def test_circular_reference_handling():
|
||||
) # This will pass, showing the cache pollution
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_json_parsing_error_handling():
|
||||
"""
|
||||
Test that JSON parsing errors are properly handled and raise ProxyException
|
||||
with appropriate error messages.
|
||||
"""
|
||||
# Test case 1: Trailing comma error
|
||||
mock_request = MagicMock()
|
||||
invalid_json_with_trailing_comma = b'''{
|
||||
"model": "gpt-4o",
|
||||
"tools": [
|
||||
{
|
||||
"type": "mcp",
|
||||
"server_label": "litellm",
|
||||
"headers": {
|
||||
"x-litellm-api-key": "Bearer sk-1234",
|
||||
}
|
||||
}
|
||||
],
|
||||
"input": "Run available tools"
|
||||
}'''
|
||||
|
||||
mock_request.body = AsyncMock(return_value=invalid_json_with_trailing_comma)
|
||||
mock_request.headers = {"content-type": "application/json"}
|
||||
mock_request.scope = {}
|
||||
|
||||
# Should raise ProxyException for trailing comma
|
||||
with pytest.raises(ProxyException) as exc_info:
|
||||
await _read_request_body(mock_request)
|
||||
|
||||
assert exc_info.value.code == "400"
|
||||
assert "Invalid JSON payload" in exc_info.value.message
|
||||
assert "trailing comma" in exc_info.value.message
|
||||
|
||||
# Test case 2: Unquoted property name error
|
||||
mock_request2 = MagicMock()
|
||||
invalid_json_unquoted_property = b'''{
|
||||
"model": "gpt-4o",
|
||||
"tools": [
|
||||
{
|
||||
type: "mcp",
|
||||
"server_label": "litellm"
|
||||
}
|
||||
],
|
||||
"input": "Run available tools"
|
||||
}'''
|
||||
|
||||
mock_request2.body = AsyncMock(return_value=invalid_json_unquoted_property)
|
||||
mock_request2.headers = {"content-type": "application/json"}
|
||||
mock_request2.scope = {}
|
||||
|
||||
# Should raise ProxyException for unquoted property
|
||||
with pytest.raises(ProxyException) as exc_info2:
|
||||
await _read_request_body(mock_request2)
|
||||
|
||||
assert exc_info2.value.code == "400"
|
||||
assert "Invalid JSON payload" in exc_info2.value.message
|
||||
|
||||
# Test case 3: Valid JSON should work normally
|
||||
mock_request3 = MagicMock()
|
||||
valid_json = b'''{
|
||||
"model": "gpt-4o",
|
||||
"tools": [
|
||||
{
|
||||
"type": "mcp",
|
||||
"server_label": "litellm",
|
||||
"headers": {
|
||||
"x-litellm-api-key": "Bearer sk-1234"
|
||||
}
|
||||
}
|
||||
],
|
||||
"input": "Run available tools"
|
||||
}'''
|
||||
|
||||
mock_request3.body = AsyncMock(return_value=valid_json)
|
||||
mock_request3.headers = {"content-type": "application/json"}
|
||||
mock_request3.scope = {}
|
||||
|
||||
# Should parse successfully
|
||||
result = await _read_request_body(mock_request3)
|
||||
assert result["model"] == "gpt-4o"
|
||||
assert result["input"] == "Run available tools"
|
||||
assert len(result["tools"]) == 1
|
||||
assert result["tools"][0]["type"] == "mcp"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_form_data():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user