diff --git a/litellm/llms/deepseek/chat/transformation.py b/litellm/llms/deepseek/chat/transformation.py index 63147642692..a316a3b9260 100644 --- a/litellm/llms/deepseek/chat/transformation.py +++ b/litellm/llms/deepseek/chat/transformation.py @@ -157,6 +157,11 @@ class DeepSeekChatConfig(OpenAIGPTConfig): #30722). Drop the unsupported entries so the function tools still go through, and drop the now-dangling tool_choice/parallel_tool_calls when nothing callable survives. + + Only non-`function` tools are ever dropped, so a `tool_choice` that names + a specific function still points at a surviving tool and is left intact; + `tool_choice`/`parallel_tool_calls` are cleared only when no function + tool remains. """ tools = optional_params.get("tools") if not isinstance(tools, list) or not tools: diff --git a/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py b/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py index 8d2d1470d5a..ec51e5d303d 100644 --- a/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py +++ b/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py @@ -81,3 +81,23 @@ def test_transform_request_strips_unsupported_tools_from_body(): assert [tool["type"] for tool in body["tools"]] == ["function"] assert body["tools"][0]["function"]["name"] == "shell" + + +async def test_async_transform_request_strips_unsupported_tools_from_body(): + config = DeepSeekChatConfig() + body = await config.async_transform_request( + model="deepseek-chat", + messages=[{"role": "user", "content": "hi"}], + optional_params={ + "tools": [ + _function_tool("shell"), + {"type": "namespace", "name": "container.exec"}, + ], + "tool_choice": "auto", + }, + litellm_params={}, + headers={}, + ) + + assert [tool["type"] for tool in body["tools"]] == ["function"] + assert body["tools"][0]["function"]["name"] == "shell"