mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
feat(responses): call sign_request after body is final, send signed bytes when signed
This commit is contained in:
parent
9121dd16e6
commit
2c5d026b0d
2 changed files with 107 additions and 24 deletions
|
|
@ -2316,6 +2316,31 @@ class BaseLLMHTTPHandler:
|
|||
# but never included in the outbound provider payload.
|
||||
request_context["litellm_params"] = dict(litellm_params)
|
||||
|
||||
is_stream_request = bool(stream)
|
||||
if is_stream_request and fake_stream is True:
|
||||
stream, data = self._prepare_fake_stream_request(
|
||||
stream=stream,
|
||||
data=data,
|
||||
fake_stream=fake_stream,
|
||||
)
|
||||
|
||||
# Sign after the body is final (post-transform/normalize/extra_body and post
|
||||
# fake-stream prep) so signed bytes match what we send. No-op for providers
|
||||
# that inherit the default sign_request.
|
||||
headers, signed_body = responses_api_provider_config.sign_request(
|
||||
headers=headers,
|
||||
optional_params=dict(litellm_params),
|
||||
request_data=data,
|
||||
api_base=api_base,
|
||||
api_key=litellm_params.api_key,
|
||||
model=model,
|
||||
stream=stream,
|
||||
fake_stream=fake_stream,
|
||||
)
|
||||
body_kwargs: Dict[str, Any] = (
|
||||
{"data": signed_body} if signed_body is not None else {"json": data}
|
||||
)
|
||||
|
||||
## LOGGING
|
||||
logging_obj.pre_call(
|
||||
input=input,
|
||||
|
|
@ -2328,22 +2353,14 @@ class BaseLLMHTTPHandler:
|
|||
)
|
||||
|
||||
try:
|
||||
if stream:
|
||||
# For streaming, use stream=True in the request
|
||||
if fake_stream is True:
|
||||
stream, data = self._prepare_fake_stream_request(
|
||||
stream=stream,
|
||||
data=data,
|
||||
fake_stream=fake_stream,
|
||||
)
|
||||
|
||||
if is_stream_request:
|
||||
response = sync_httpx_client.post(
|
||||
url=api_base,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=timeout
|
||||
or float(response_api_optional_request_params.get("timeout", 0)),
|
||||
stream=stream,
|
||||
**body_kwargs,
|
||||
)
|
||||
if fake_stream is True:
|
||||
return MockResponsesAPIStreamingIterator(
|
||||
|
|
@ -2368,13 +2385,12 @@ class BaseLLMHTTPHandler:
|
|||
call_type=CallTypes.responses.value,
|
||||
)
|
||||
else:
|
||||
# For non-streaming requests
|
||||
response = sync_httpx_client.post(
|
||||
url=api_base,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=timeout
|
||||
or float(response_api_optional_request_params.get("timeout", 0)),
|
||||
**body_kwargs,
|
||||
)
|
||||
except Exception as e:
|
||||
raise self._handle_error(
|
||||
|
|
@ -2462,6 +2478,28 @@ class BaseLLMHTTPHandler:
|
|||
# but never included in the outbound provider payload.
|
||||
request_context["litellm_params"] = dict(litellm_params)
|
||||
|
||||
is_stream_request = bool(stream)
|
||||
if is_stream_request and fake_stream is True:
|
||||
stream, data = self._prepare_fake_stream_request(
|
||||
stream=stream,
|
||||
data=data,
|
||||
fake_stream=fake_stream,
|
||||
)
|
||||
|
||||
headers, signed_body = responses_api_provider_config.sign_request(
|
||||
headers=headers,
|
||||
optional_params=dict(litellm_params),
|
||||
request_data=data,
|
||||
api_base=api_base,
|
||||
api_key=litellm_params.api_key,
|
||||
model=model,
|
||||
stream=stream,
|
||||
fake_stream=fake_stream,
|
||||
)
|
||||
body_kwargs: Dict[str, Any] = (
|
||||
{"data": signed_body} if signed_body is not None else {"json": data}
|
||||
)
|
||||
|
||||
## LOGGING
|
||||
logging_obj.pre_call(
|
||||
input=input,
|
||||
|
|
@ -2474,22 +2512,14 @@ class BaseLLMHTTPHandler:
|
|||
)
|
||||
|
||||
try:
|
||||
if stream:
|
||||
# For streaming, we need to use stream=True in the request
|
||||
if fake_stream is True:
|
||||
stream, data = self._prepare_fake_stream_request(
|
||||
stream=stream,
|
||||
data=data,
|
||||
fake_stream=fake_stream,
|
||||
)
|
||||
|
||||
if is_stream_request:
|
||||
response = await async_httpx_client.post(
|
||||
url=api_base,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=timeout
|
||||
or float(response_api_optional_request_params.get("timeout", 0)),
|
||||
stream=stream,
|
||||
**body_kwargs,
|
||||
)
|
||||
|
||||
if fake_stream is True:
|
||||
|
|
@ -2516,13 +2546,12 @@ class BaseLLMHTTPHandler:
|
|||
call_type=CallTypes.responses.value,
|
||||
)
|
||||
else:
|
||||
# For non-streaming, proceed as before
|
||||
response = await async_httpx_client.post(
|
||||
url=api_base,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=timeout
|
||||
or float(response_api_optional_request_params.get("timeout", 0)),
|
||||
**body_kwargs,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -762,3 +762,57 @@ def test_base_responses_config_sign_request_is_noop_by_default():
|
|||
)
|
||||
assert out_headers == {"Authorization": "Bearer sk-existing"}
|
||||
assert signed_body is None
|
||||
|
||||
|
||||
def _make_responses_handler_call(signed_body):
|
||||
"""Drive BaseLLMHTTPHandler.response_api_handler with a fully mocked provider
|
||||
config + sync client, returning the kwargs the client.post was called with.
|
||||
|
||||
signed_body=None simulates a no-op (non-signing) provider; bytes simulates a
|
||||
signing provider (e.g. Bedrock Mantle).
|
||||
"""
|
||||
from unittest.mock import MagicMock
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
provider_config = MagicMock()
|
||||
provider_config.validate_environment.return_value = {}
|
||||
provider_config.get_complete_url.return_value = (
|
||||
"https://bedrock-mantle.us-east-2.api.aws/openai/v1/responses"
|
||||
)
|
||||
provider_config.transform_responses_api_request.return_value = {"input": "hi"}
|
||||
provider_config.should_fake_stream.return_value = False
|
||||
provider_config.sign_request.return_value = ({"X-Signed": "1"}, signed_body)
|
||||
|
||||
mock_client = MagicMock(spec=HTTPHandler)
|
||||
mock_client.post.return_value = MagicMock()
|
||||
|
||||
handler = BaseLLMHTTPHandler()
|
||||
handler.response_api_handler(
|
||||
model="openai.gpt-5.5",
|
||||
input="hi",
|
||||
responses_api_provider_config=provider_config,
|
||||
response_api_optional_request_params={},
|
||||
custom_llm_provider="bedrock_mantle",
|
||||
litellm_params=GenericLiteLLMParams(aws_region_name="us-east-2"),
|
||||
logging_obj=MagicMock(),
|
||||
client=mock_client,
|
||||
_is_async=False,
|
||||
)
|
||||
return mock_client.post.call_args.kwargs
|
||||
|
||||
|
||||
def test_responses_handler_sends_json_when_not_signed():
|
||||
"""No-op provider (signed_body is None) -> handler posts json=data, no data= bytes."""
|
||||
kwargs = _make_responses_handler_call(signed_body=None)
|
||||
assert kwargs.get("json") == {"input": "hi"}
|
||||
assert "data" not in kwargs
|
||||
|
||||
|
||||
def test_responses_handler_sends_signed_bytes_when_signed():
|
||||
"""Signing provider -> handler posts the exact signed bytes via data=, not json=."""
|
||||
kwargs = _make_responses_handler_call(signed_body=b'{"input": "hi"}')
|
||||
assert kwargs.get("data") == b'{"input": "hi"}'
|
||||
assert "json" not in kwargs
|
||||
assert kwargs["headers"] == {"X-Signed": "1"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue