fix(responses): pass extra_body through responses-to-completion bridge (#20982)

extra_body was silently dropped when calling litellm.responses() for
models using the completion bridge. The parameter is accepted as an
explicit kwarg in responses(), so it's not included in **kwargs, and the
handler/transformation functions didn't accept or forward it.

Changes:
- Pass extra_body from responses() to response_api_handler()
- Add extra_body parameter to response_api_handler() and forward it to
  the transformation function
- Add extra_body parameter to
  transform_responses_api_request_to_chat_completion_request() and
  include it in the litellm_completion_request dict
- Add 5 regression tests verifying extra_body flows correctly
This commit is contained in:
skylarkoo7 2026-02-12 11:22:49 +05:30
parent 9cee51abb9
commit 557a2defb5
4 changed files with 105 additions and 0 deletions

View file

@ -31,6 +31,7 @@ class LiteLLMCompletionTransformationHandler:
_is_async: bool = False,
stream: Optional[bool] = None,
extra_headers: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
**kwargs,
) -> Union[
ResponsesAPIResponse,
@ -47,6 +48,7 @@ class LiteLLMCompletionTransformationHandler:
custom_llm_provider=custom_llm_provider,
stream=stream,
extra_headers=extra_headers,
extra_body=extra_body,
**kwargs,
)
)

View file

@ -161,6 +161,7 @@ class LiteLLMCompletionResponsesConfig:
custom_llm_provider: Optional[str] = None,
stream: Optional[bool] = None,
extra_headers: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
**kwargs,
) -> dict:
"""
@ -215,6 +216,7 @@ class LiteLLMCompletionResponsesConfig:
# litellm specific params
"custom_llm_provider": custom_llm_provider,
"extra_headers": extra_headers,
"extra_body": extra_body,
}
# Responses API `Completed` events require usage, we pass `stream_options` to litellm.completion to include usage

View file

@ -681,6 +681,7 @@ def responses(
_is_async=_is_async,
stream=stream,
extra_headers=extra_headers,
extra_body=extra_body,
**kwargs,
)

View file

@ -0,0 +1,100 @@
"""
Regression tests for #20982 — ``extra_body`` is not passed through the
responses-to-completion bridge.
When calling ``litellm.responses()`` with ``extra_body`` for a model
that uses the completion bridge, the ``extra_body`` parameter was silently
dropped because:
1. It's an explicit parameter, so it's not in ``**kwargs``
2. The handler and transformation functions didn't accept/forward it
"""
import os
import sys
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the parent directory to the system path
from litellm.responses.litellm_completion_transformation.transformation import (
LiteLLMCompletionResponsesConfig,
)
class TestExtraBodyResponsesBridge:
"""Verify ``extra_body`` flows through the responses-to-completion
transformation."""
def test_extra_body_included_in_completion_request(self):
"""extra_body should appear in the transformed completion request."""
extra_body = {"provider": {"order": ["Together"], "allow_fallbacks": False}}
result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request(
model="openrouter/test-model",
input="Hello",
responses_api_request={},
custom_llm_provider="openrouter",
stream=False,
extra_headers=None,
extra_body=extra_body,
)
assert result.get("extra_body") == extra_body
def test_extra_body_none_by_default(self):
"""When not provided, extra_body should be None in the request."""
result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request(
model="openrouter/test-model",
input="Hello",
responses_api_request={},
custom_llm_provider="openrouter",
stream=False,
extra_headers=None,
)
assert result.get("extra_body") is None
def test_extra_body_preserved_with_other_params(self):
"""extra_body should coexist with other parameters like
extra_headers, temperature, etc."""
extra_body = {"custom_key": "custom_value"}
extra_headers = {"X-Custom": "header"}
result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request(
model="test-model",
input="Hello",
responses_api_request={"temperature": 0.7, "max_output_tokens": 100},
custom_llm_provider=None,
stream=True,
extra_headers=extra_headers,
extra_body=extra_body,
)
assert result["extra_body"] == extra_body
assert result["extra_headers"] == extra_headers
assert result["temperature"] == 0.7
assert result["max_tokens"] == 100
def test_extra_body_empty_dict(self):
"""An empty extra_body dict should still be passed through."""
result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request(
model="test-model",
input="Hello",
responses_api_request={},
extra_body={},
)
assert result.get("extra_body") == {}
def test_extra_body_nested_dict(self):
"""Deeply nested extra_body should be preserved as-is."""
extra_body = {
"provider": {
"order": ["Together", "Fireworks"],
"allow_fallbacks": False,
"quantizations": ["fp16"],
},
"transforms": ["middle-out"],
}
result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request(
model="openrouter/model",
input="Test",
responses_api_request={},
extra_body=extra_body,
)
assert result["extra_body"] == extra_body
assert result["extra_body"]["provider"]["order"] == ["Together", "Fireworks"]