diff --git a/tests/test_litellm/responses/test_responses_utils.py b/tests/test_litellm/responses/test_responses_utils.py index 7feab9c6035..c6f32b6d758 100644 --- a/tests/test_litellm/responses/test_responses_utils.py +++ b/tests/test_litellm/responses/test_responses_utils.py @@ -384,3 +384,35 @@ def test_responses_extra_body_forwarded_to_completion_transformation_handler(): assert call_kwargs.kwargs.get("extra_body") == { "custom_key": "custom_value" } + + +def test_responses_maps_reasoning_effort_from_litellm_params_to_reasoning(): + """ + Test that when reasoning_effort is passed in kwargs (e.g. from proxy litellm_params) + and reasoning is None, it is mapped to reasoning before the request. + + Supports per-model reasoning_effort/summary config in proxy for clients like Open WebUI + that cannot set extra_body. + """ + with patch( + "litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config", + return_value=None, + ), patch( + "litellm.responses.main.litellm_completion_transformation_handler.response_api_handler", + ) as mock_handler: + mock_handler.return_value = MagicMock() + + litellm.responses( + model="openai/gpt-4o", + input="Hello", + reasoning_effort={"effort": "high", "summary": "detailed"}, + ) + + mock_handler.assert_called_once() + call_kwargs = mock_handler.call_args + responses_api_request = call_kwargs.kwargs.get("responses_api_request", {}) + assert "reasoning" in responses_api_request + assert responses_api_request["reasoning"] == { + "effort": "high", + "summary": "detailed", + } diff --git a/tests/test_litellm/test_router.py b/tests/test_litellm/test_router.py index 9dcb16b545e..c55b26ca39c 100644 --- a/tests/test_litellm/test_router.py +++ b/tests/test_litellm/test_router.py @@ -1877,19 +1877,20 @@ async def test_anthropic_messages_call_type_is_cached(): in PromptCachingDeploymentCheck.async_log_success_event. """ import asyncio + + from litellm.caching.dual_cache import DualCache from litellm.router_utils.pre_call_checks.prompt_caching_deployment_check import ( PromptCachingDeploymentCheck, ) from litellm.router_utils.prompt_caching_cache import PromptCachingCache - from litellm.caching.dual_cache import DualCache - from litellm.types.utils import CallTypes from litellm.types.utils import ( - StandardLoggingPayload, - StandardLoggingModelInformation, - StandardLoggingMetadata, + CallTypes, StandardLoggingHiddenParams, + StandardLoggingMetadata, + StandardLoggingModelInformation, + StandardLoggingPayload, ) - + # Create mock standard logging payload inline def create_standard_logging_payload() -> StandardLoggingPayload: return StandardLoggingPayload( @@ -2081,3 +2082,107 @@ def test_update_kwargs_with_deployment_no_tags(): # No tags key should be added if deployment has no tags assert "tags" not in kwargs["metadata"] + + +def test_update_kwargs_with_deployment_merges_tools(): + """ + Test that when both deployment litellm_params and request have tools, + they are merged (deployment tools first, then request tools). + + Supports proxy-configured tools (e.g. for o3 deep research) merged with + client-provided tools. + """ + router = litellm.Router( + model_list=[ + { + "model_name": "o3-deep-research", + "litellm_params": { + "model": "openai/o3-deep-research", + "api_key": "fake-key", + "tools": [{"type": "web_search"}], + "tool_choice": "auto", + }, + }, + ], + ) + + kwargs: dict = { + "metadata": {}, + "tools": [ + { + "type": "function", + "function": {"name": "get_weather", "description": "Get weather"}, + }, + ], + } + deployment = router.get_deployment_by_model_group_name( + model_group_name="o3-deep-research" + ) + router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + + # Tools should be merged: deployment first, then request + assert "tools" in kwargs + assert len(kwargs["tools"]) == 2 + assert kwargs["tools"][0] == {"type": "web_search"} + assert kwargs["tools"][1]["function"]["name"] == "get_weather" + # tool_choice from request (none) - deployment's should be used + assert kwargs["tool_choice"] == "auto" + + +def test_update_kwargs_with_deployment_merge_tools_deployment_only(): + """ + Test that when only deployment has tools, they are applied to kwargs. + """ + router = litellm.Router( + model_list=[ + { + "model_name": "o3-deep-research", + "litellm_params": { + "model": "openai/o3-deep-research", + "api_key": "fake-key", + "tools": [{"type": "web_search"}], + "tool_choice": "required", + }, + }, + ], + ) + + kwargs: dict = {"metadata": {}} + deployment = router.get_deployment_by_model_group_name( + model_group_name="o3-deep-research" + ) + router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + + assert kwargs["tools"] == [{"type": "web_search"}] + assert kwargs["tool_choice"] == "required" + + +def test_update_kwargs_with_deployment_merge_tools_request_overrides_tool_choice(): + """ + Test that when request has tool_choice, it overrides deployment's. + """ + router = litellm.Router( + model_list=[ + { + "model_name": "o3-deep-research", + "litellm_params": { + "model": "openai/o3-deep-research", + "api_key": "fake-key", + "tools": [{"type": "web_search"}], + "tool_choice": "auto", + }, + }, + ], + ) + + kwargs: dict = { + "metadata": {}, + "tool_choice": "none", + } + deployment = router.get_deployment_by_model_group_name( + model_group_name="o3-deep-research" + ) + router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + + # Request tool_choice should be preserved (merged tools still applied) + assert kwargs["tool_choice"] == "none"