Simplify pass-through raw body handling

Read the SigV4-signed bytes directly from request.state inside
pass_through_request instead of threading a custom_raw_body argument
through three functions. Helper methods are restored to their original
signatures, and the new branch lives in one place at each httpx call site.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Milan 2026-05-09 11:45:21 +03:00
parent da41bc40b3
commit 7d04eb221d
No known key found for this signature in database
2 changed files with 44 additions and 61 deletions

View file

@ -376,7 +376,6 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils):
headers: dict,
requested_query_params: Optional[dict] = None,
custom_body: Optional[dict] = None,
custom_raw_body: Optional[Union[str, bytes]] = None,
) -> httpx.Response:
"""
Make a non-streaming HTTP request
@ -390,14 +389,6 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils):
headers=headers,
params=requested_query_params,
)
elif custom_raw_body is not None:
response = await async_client.request(
method=request.method,
url=url,
headers=headers,
params=requested_query_params,
content=custom_raw_body,
)
else:
response = await async_client.request(
method=request.method,
@ -416,7 +407,6 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils):
headers: dict,
requested_query_params: Optional[dict] = None,
_parsed_body: Optional[dict] = None,
custom_raw_body: Optional[Union[str, bytes]] = None,
forward_multipart: bool = False,
) -> httpx.Response:
"""
@ -431,14 +421,6 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils):
headers=headers,
params=requested_query_params,
)
elif custom_raw_body is not None:
response = await async_client.request(
method=request.method,
url=url,
headers=headers,
params=requested_query_params,
content=custom_raw_body,
)
elif (
HttpPassThroughEndpointHelpers.is_multipart(request) is True
and forward_multipart
@ -678,7 +660,6 @@ async def pass_through_request( # noqa: PLR0915
custom_headers: dict,
user_api_key_dict: UserAPIKeyAuth,
custom_body: Optional[dict] = None,
custom_raw_body: Optional[Union[str, bytes]] = None,
forward_headers: Optional[bool] = False,
merge_query_params: Optional[bool] = False,
query_params: Optional[dict] = None,
@ -697,9 +678,6 @@ async def pass_through_request( # noqa: PLR0915
custom_headers: The custom headers
user_api_key_dict: The user API key dictionary
custom_body: The custom body
custom_raw_body: Exact request body bytes/str for upstream (e.g. SigV4-signed).
When set, this is sent as ``content=...`` instead of re-encoding ``custom_body``
as JSON, so signatures and Content-Length stay consistent.
forward_headers: Whether to forward headers
merge_query_params: Whether to merge query params
query_params: The query params
@ -758,15 +736,20 @@ async def pass_through_request( # noqa: PLR0915
str(url)
)
# SigV4-signed callers (e.g. Bedrock) attach the exact bytes that were
# signed via request.state; we must send those instead of re-encoding the
# parsed dict (hooks mutate it, breaking the signature / Content-Length).
state_raw_body: Optional[Union[str, bytes]] = getattr(
request.state, LITELLM_PASS_THROUGH_RAW_BODY_STATE_KEY, None
)
# Skip body parsing for multipart requests - make_multipart_http_request will handle it
# But if custom_body is provided (e.g., JSON parsed despite multipart content-type), use it
is_multipart = (
HttpPassThroughEndpointHelpers.is_multipart(request)
and custom_body is None
and custom_raw_body is None
HttpPassThroughEndpointHelpers.is_multipart(request) and not custom_body
)
if custom_body is not None:
if custom_body:
_parsed_body = custom_body
elif is_multipart:
# Don't parse multipart body here - it will be handled by make_multipart_http_request
@ -908,22 +891,20 @@ async def pass_through_request( # noqa: PLR0915
)
)
else:
if custom_raw_body is not None:
req = async_client.build_request(
"POST",
url,
content=custom_raw_body,
params=requested_query_params,
headers=headers,
)
else:
req = async_client.build_request(
"POST",
url,
json=_parsed_body,
params=requested_query_params,
headers=headers,
)
# SigV4-signed callers (Bedrock) supply the exact pre-signed bytes;
# otherwise httpx encodes the parsed JSON dict as before.
body_kwargs: Dict[str, Any] = (
{"content": state_raw_body}
if state_raw_body is not None
else {"json": _parsed_body}
)
req = async_client.build_request(
"POST",
url,
params=requested_query_params,
headers=headers,
**body_kwargs,
)
response = await async_client.send(req, stream=stream)
@ -951,18 +932,28 @@ async def pass_through_request( # noqa: PLR0915
status_code=response.status_code,
)
response = (
await HttpPassThroughEndpointHelpers.non_streaming_http_request_handler(
request=request,
async_client=async_client,
if state_raw_body is not None:
# SigV4-signed callers (Bedrock) require the exact pre-signed bytes
# to be forwarded so the signature/Content-Length stay valid.
response = await async_client.request(
method=request.method,
url=url,
headers=headers,
requested_query_params=requested_query_params,
_parsed_body=_parsed_body,
custom_raw_body=custom_raw_body,
forward_multipart=is_multipart,
params=requested_query_params,
content=state_raw_body,
)
else:
response = (
await HttpPassThroughEndpointHelpers.non_streaming_http_request_handler(
request=request,
async_client=async_client,
url=url,
headers=headers,
requested_query_params=requested_query_params,
_parsed_body=_parsed_body,
forward_multipart=is_multipart,
)
)
)
verbose_proxy_logger.debug("response.headers= %s", response.headers)
if _is_streaming_response(response) is True:
@ -1390,11 +1381,6 @@ def create_pass_through_route(
LITELLM_PASS_THROUGH_CUSTOM_BODY_STATE_KEY,
None,
)
state_raw_body: Optional[Union[str, bytes]] = getattr(
request.state,
LITELLM_PASS_THROUGH_RAW_BODY_STATE_KEY,
None,
)
final_custom_body: Optional[dict] = None
if isinstance(state_custom_body, dict):
final_custom_body = state_custom_body
@ -1415,7 +1401,6 @@ def create_pass_through_route(
),
stream=is_streaming_request or stream,
custom_body=final_custom_body,
custom_raw_body=state_raw_body,
cost_per_request=cast(Optional[float], param_cost_per_request),
custom_llm_provider=custom_llm_provider,
guardrails_config=cast(Optional[dict], param_guardrails),

View file

@ -18,7 +18,6 @@ sys.path.insert(
from litellm.proxy.pass_through_endpoints.pass_through_endpoints import (
HttpPassThroughEndpointHelpers,
LITELLM_PASS_THROUGH_CUSTOM_BODY_STATE_KEY,
LITELLM_PASS_THROUGH_RAW_BODY_STATE_KEY,
pass_through_request,
)
from litellm.proxy.pass_through_endpoints.success_handler import (
@ -2206,8 +2205,6 @@ async def test_create_pass_through_route_custom_body_url_target():
setattr(
mock_request.state, LITELLM_PASS_THROUGH_CUSTOM_BODY_STATE_KEY, bedrock_body
)
signed_body = json.dumps(bedrock_body)
setattr(mock_request.state, LITELLM_PASS_THROUGH_RAW_BODY_STATE_KEY, signed_body)
await endpoint_func(
request=mock_request,
@ -2221,7 +2218,8 @@ async def test_create_pass_through_route_custom_body_url_target():
# The critical assertion: custom_body takes precedence over
# the body parsed from the raw request
assert call_kwargs["custom_body"] == bedrock_body
assert call_kwargs["custom_raw_body"] == signed_body
# HeadersDict-like custom_headers (e.g. botocore SigV4) must be coerced
# to a plain dict so signed headers actually reach the upstream.
assert call_kwargs["custom_headers"] == {
"authorization": "AWS4-HMAC-SHA256 signed",
"content-type": "application/json",