mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(proxy): preserve non-object JSON for provider passthrough
This commit is contained in:
parent
2c8340ec29
commit
a467922889
3 changed files with 42 additions and 14 deletions
|
|
@ -1373,6 +1373,17 @@ async def _read_request_body_deferring_parse_failure(
|
|||
parsed_body: Final = await _read_request_body(request=request)
|
||||
except ProxyException as parse_exception:
|
||||
return {}, parse_exception # mutable-ok: request_data is a plain dict across the whole auth path
|
||||
if not isinstance(parsed_body, dict):
|
||||
# Provider pass-through endpoints may forward a JSON array or scalar.
|
||||
# Auth only needs a mapping for its own checks; leave the raw body intact.
|
||||
if request_dispatched_to_pass_through_endpoint(request) or request_dispatched_to_provider_pass_through(request):
|
||||
return {}, None # mutable-ok: request_data is a plain dict across the whole auth path
|
||||
return {}, ProxyException(
|
||||
message="JSON request body must be an object",
|
||||
type="invalid_request_error",
|
||||
param="request_body",
|
||||
code=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return populate_request_with_path_params(request_data=parsed_body, request=request), None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -208,14 +208,6 @@ async def _read_request_body(request: Request | None) -> dict:
|
|||
code=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if not isinstance(parsed_body, dict):
|
||||
raise ProxyException(
|
||||
message="JSON request body must be an object",
|
||||
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
|
||||
|
|
|
|||
|
|
@ -48,17 +48,42 @@ def _starlette_request(body: bytes, content_type: str) -> Request:
|
|||
|
||||
@pytest.mark.parametrize("body", [b"[]", b"123", b'"str"', b"true", b"null"])
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_request_body_rejects_non_object_json(body: bytes):
|
||||
async def test_read_request_body_preserves_non_object_json_for_passthrough(body: bytes):
|
||||
request = _starlette_request(body, "application/json")
|
||||
|
||||
with pytest.raises(ProxyException) as error:
|
||||
await _read_request_body(request)
|
||||
|
||||
assert error.value.code == "400"
|
||||
assert "JSON request body must be an object" in error.value.message
|
||||
assert await _read_request_body(request) == orjson.loads(body)
|
||||
assert _safe_get_request_parsed_body(request) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", [b"[]", b"123", b'"str"', b"true", b"null"])
|
||||
@pytest.mark.asyncio
|
||||
async def test_auth_defers_non_object_json_rejection(body: bytes):
|
||||
from litellm.proxy.auth.user_api_key_auth import _read_request_body_deferring_parse_failure
|
||||
|
||||
request = _starlette_request(body, "application/json")
|
||||
parsed_body, error = await _read_request_body_deferring_parse_failure(request)
|
||||
|
||||
assert parsed_body == {}
|
||||
assert isinstance(error, ProxyException)
|
||||
assert error.code == "400"
|
||||
assert "JSON request body must be an object" in error.message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auth_preserves_non_object_json_for_provider_passthrough():
|
||||
from litellm.proxy.auth.user_api_key_auth import _read_request_body_deferring_parse_failure
|
||||
|
||||
request = _starlette_request(b'[{"prompt":"hi"}]', "application/json")
|
||||
request.scope["path"] = "/vertex_ai/v1/projects/test:rawPredict"
|
||||
request.scope["path_params"] = {"endpoint": "v1/projects/test:rawPredict"}
|
||||
|
||||
parsed_body, error = await _read_request_body_deferring_parse_failure(request)
|
||||
|
||||
assert parsed_body == {}
|
||||
assert error is None
|
||||
assert await request.body() == b'[{"prompt":"hi"}]'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_raw_json_body_returns_the_bytes_the_parsed_body_came_from():
|
||||
body = b'{"model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "hi"}]}'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue