diff --git a/litellm/llms/hosted_vllm/responses/transformation.py b/litellm/llms/hosted_vllm/responses/transformation.py index 8489bc0da84..9e9254712e8 100644 --- a/litellm/llms/hosted_vllm/responses/transformation.py +++ b/litellm/llms/hosted_vllm/responses/transformation.py @@ -11,9 +11,32 @@ from typing import Final from litellm.litellm_core_utils.url_utils import join_api_base from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import ResponseInputParam from litellm.types.router import GenericLiteLLMParams from litellm.types.utils import LlmProviders +HOSTED_VLLM_RESPONSES_SUPPORTED_PARAMS: Final = ( + "chat_template_kwargs", + "top_k", + "repetition_penalty", + "presence_penalty", + "frequency_penalty", + "min_p", + "best_of", + "stop_token_ids", + "spaces_between_special_tokens", + "prompt_logprobs", + "skip_special_tokens", + "include_stop_str_in_output", + "guided_json", + "guided_regex", + "guided_choice", + "guided_grammar", + "guided_decoding_backend", + "guided_whitespace_pattern", + "priority", +) + class HostedVLLMResponsesAPIConfig(OpenAIResponsesAPIConfig): """ @@ -29,6 +52,41 @@ class HostedVLLMResponsesAPIConfig(OpenAIResponsesAPIConfig): def custom_llm_provider(self) -> LlmProviders: return LlmProviders.HOSTED_VLLM + def get_supported_openai_params(self, model: str) -> list[str]: + params = super().get_supported_openai_params(model) + params.extend(HOSTED_VLLM_RESPONSES_SUPPORTED_PARAMS) + return params + + def transform_responses_api_request( + self, + model: str, + input: str | ResponseInputParam, + response_api_optional_request_params: dict, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> dict: + request: Final[dict] = super().transform_responses_api_request( + model=model, + input=input, + response_api_optional_request_params=response_api_optional_request_params, + litellm_params=litellm_params, + headers=headers, + ) + + for param in HOSTED_VLLM_RESPONSES_SUPPORTED_PARAMS: + if param in response_api_optional_request_params and response_api_optional_request_params[param] is not None: + request[param] = response_api_optional_request_params[param] + elif litellm_params is not None: + val = ( + litellm_params.get(param) + if hasattr(litellm_params, "get") + else getattr(litellm_params, param, None) + ) + if val is not None: + request[param] = val + + return request + def validate_environment( self, headers: dict, diff --git a/litellm/responses/utils.py b/litellm/responses/utils.py index dc75e77d685..1492ee51963 100644 --- a/litellm/responses/utils.py +++ b/litellm/responses/utils.py @@ -338,6 +338,14 @@ class ResponsesAPIRequestUtils: custom_llm_provider: Final = params.pop("custom_llm_provider", None) special_params: Final[dict[str, object]] = params.pop("kwargs", {}) + allowed_openai_params: Final = ( + params.get("allowed_openai_params") + or special_params.get("allowed_openai_params") + or [] + ) + allowed_set: Final = {p for p in allowed_openai_params if isinstance(p, str)} + default_keys: Final = set(valid_keys) | allowed_set + additional_drop_params: Final[list[str] | None] = params.pop("additional_drop_params", None) non_default_params: Final = _apply_nested_drop_params( PreProcessNonDefaultParams.base_pre_process_non_default_params( @@ -345,7 +353,7 @@ class ResponsesAPIRequestUtils: special_params=special_params, custom_llm_provider=custom_llm_provider, additional_drop_params=additional_drop_params, - default_param_values={k: None for k in valid_keys}, + default_param_values={k: None for k in default_keys}, additional_endpoint_specific_params=["input"], ), additional_drop_params, diff --git a/tests/unit/llms/hosted_vllm/responses/test_hosted_vllm_responses.py b/tests/unit/llms/hosted_vllm/responses/test_hosted_vllm_responses.py index e32f876a6b9..2fec0e44b44 100644 --- a/tests/unit/llms/hosted_vllm/responses/test_hosted_vllm_responses.py +++ b/tests/unit/llms/hosted_vllm/responses/test_hosted_vllm_responses.py @@ -183,3 +183,74 @@ def test_hosted_vllm_validate_environment_custom_api_key(): ) assert headers.get("Authorization") == "Bearer my-custom-key" + + + +def test_hosted_vllm_responses_supported_params(): + """Test get_supported_openai_params() includes vLLM provider-specific params.""" + config = HostedVLLMResponsesAPIConfig() + supported = config.get_supported_openai_params(model="Qwen/Qwen3-8B") + + assert "chat_template_kwargs" in supported + assert "top_k" in supported + assert "repetition_penalty" in supported + assert "presence_penalty" in supported + assert "frequency_penalty" in supported + assert "min_p" in supported + + +def test_hosted_vllm_responses_forwards_provider_specific_params(): + """ + Regression test for Issue #48362: + hosted_vllm Responses API must not drop provider-specific params + (chat_template_kwargs, top_k, repetition_penalty, presence_penalty) + that chat completions forwards. + """ + mock_client = _make_mock_http_client(_make_mock_responses_api_response("Test output")) + + with patch( + "litellm.llms.custom_httpx.llm_http_handler.get_httpx_client", + return_value=mock_client, + ): + response = litellm.responses( + model="hosted_vllm/Qwen/Qwen3-8B", + input="hi", + api_base="https://test-vllm.example.com/v1", + api_key="test-key", + chat_template_kwargs={"enable_thinking": False}, + top_k=20, + repetition_penalty=1.0, + presence_penalty=1.5, + ) + + assert response is not None + assert mock_client.post.call_count == 1 + call_json = mock_client.post.call_args[1]["json"] + assert call_json["model"] == "Qwen/Qwen3-8B" + assert call_json["input"] == "hi" + assert call_json["chat_template_kwargs"] == {"enable_thinking": False} + assert call_json["top_k"] == 20 + assert call_json["repetition_penalty"] == 1.0 + assert call_json["presence_penalty"] == 1.5 + + +def test_hosted_vllm_responses_transform_request_from_litellm_params(): + """Test transform_responses_api_request forwards provider params from litellm_params.""" + config = HostedVLLMResponsesAPIConfig() + litellm_params = GenericLiteLLMParams( + chat_template_kwargs={"enable_thinking": False}, + top_k=50, + min_p=0.05, + ) + + request = config.transform_responses_api_request( + model="Qwen/Qwen3-8B", + input="test input", + response_api_optional_request_params={}, + litellm_params=litellm_params, + headers={}, + ) + + assert request["chat_template_kwargs"] == {"enable_thinking": False} + assert request["top_k"] == 50 + assert request["min_p"] == 0.05