diff --git a/litellm/main.py b/litellm/main.py index fb0f649345d..d47b625f6b3 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1182,6 +1182,21 @@ def completion( # type: ignore # noqa: PLR0915 tools=tools, sanitize_openai_function_tool_names=_sanitize_openai_fn_tool_names, ) + if ( + _sanitize_openai_fn_tool_names + and isinstance(tool_choice, dict) + and tool_choice.get("type") == "function" + ): + _fn = tool_choice.get("function") + if isinstance(_fn, dict) and isinstance(_fn.get("name"), str): + from litellm.utils import _sanitize_openai_function_tool_name as _san_name + + _sanitized_tc_name = _san_name(_fn["name"], -1) + if _sanitized_tc_name != _fn["name"]: + tool_choice = { + **tool_choice, + "function": {**_fn, "name": _sanitized_tc_name}, + } # validate tool_choice tool_choice = validate_chat_completion_tool_choice(tool_choice=tool_choice) # validate optional params diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index c917bbde1b7..bfbc216e2da 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -4112,3 +4112,32 @@ def test_convert_to_model_response_restores_openai_tool_call_names_when_mapped() ) assert out.choices[0].message.tool_calls is not None assert out.choices[0].message.tool_calls[0].function.name == "plugin.subtool" + + +def test_tool_choice_function_name_sanitized_with_tools(): + from litellm.litellm_core_utils.openai_tool_name_mapping import ( + begin_openai_tool_name_mapping_scope, + ) + from litellm.utils import _sanitize_openai_function_tool_name, validate_and_fix_openai_tools + + begin_openai_tool_name_mapping_scope() + validate_and_fix_openai_tools( + tools=[ + { + "type": "function", + "function": { + "name": "my.tool", + "parameters": {"type": "object", "properties": {}}, + }, + } + ], + sanitize_openai_function_tool_names=True, + ) + # Simulate what main.py does for tool_choice + tool_choice = {"type": "function", "function": {"name": "my.tool"}} + _fn = tool_choice["function"] + sanitized = _sanitize_openai_function_tool_name(_fn["name"], -1) + if sanitized != _fn["name"]: + tool_choice = {**tool_choice, "function": {**_fn, "name": sanitized}} + + assert tool_choice["function"]["name"] == "my_tool"