From d47e72f66c086ca10e7a9a83dbf4c66fa05d6635 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 22 Sep 2026 10:50:49 -0700 Subject: [PATCH] fix(utils): stop a nested additional_drop_params entry from crashing openai-compatible calls (#42492) add_provider_specific_params_to_optional_params built the extra_body dropped-key set as frozenset(additional_drop_params), so one non-string entry raised TypeError: unhashable type: 'list' and every openai-compatible call carrying one failed with a 500 before it reached the transport. The set now takes only the string entries, the element type every other signature in this chain already declares as list[str]. A list-form entry still drops nothing: is_nested_path() tests a string, so delete_nested_value() has never applied one on any provider. This removes the crash only, so a working string path such as "tools[*].function.x" sitting beside a malformed list entry is applied instead of taking the request down. --- litellm/utils.py | 4 +++- tests/test_litellm/test_utils.py | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/litellm/utils.py b/litellm/utils.py index 8ce83f475a4..b9d56b25653 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -4979,7 +4979,9 @@ def add_provider_specific_params_to_optional_params( **extra_body, } - dropped_keys: Final = EXTRA_BODY_ROUTING_KEYS | frozenset(additional_drop_params or ()) + dropped_keys: Final = EXTRA_BODY_ROUTING_KEYS | frozenset( + param for param in (additional_drop_params or ()) if isinstance(param, str) + ) processed_extra_body: Final = {k: v for k, v in initial_extra_body.items() if k not in dropped_keys} _ensure_extra_body_is_safe: Final = getattr(sys.modules[__name__], "_ensure_extra_body_is_safe") diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index 62aae7aed34..ead805eed42 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -3042,6 +3042,45 @@ class TestExtraBodyCannotOverrideModel: assert result["extra_body"] == {"top_k": 5}, result + def test_nested_drop_paths_do_not_break_extra_body_filtering(self) -> None: + from litellm.utils import add_provider_specific_params_to_optional_params + + result = add_provider_specific_params_to_optional_params( + optional_params={}, + passed_params={ + "model": "hosted_vllm/my-vllm-model", + "extra_body": {"model": "hosted_vllm/other", "top_k": 5, "kept": True}, + }, + custom_llm_provider="hosted_vllm", + openai_params=["model", "temperature"], + additional_drop_params=[["tools", "function", "strict"], "top_k"], + ) + + assert result == {"extra_body": {"kept": True}}, result + + def test_a_list_entry_does_not_break_a_supported_nested_drop_path(self) -> None: + def tools() -> list[dict]: + return [ + { + "type": "function", + "function": {"name": "f", "custom_marker": "LEAK", "parameters": {"type": "object"}}, + } + ] + + untouched = litellm.get_optional_params( + model="my-vllm-model", custom_llm_provider="hosted_vllm", tools=tools() + ) + assert untouched["tools"][0]["function"]["custom_marker"] == "LEAK", untouched + + result = litellm.get_optional_params( + model="my-vllm-model", + custom_llm_provider="hosted_vllm", + tools=tools(), + additional_drop_params=["tools[*].function.custom_marker", ["tools", "function", "custom_marker"]], + ) + + assert "custom_marker" not in result["tools"][0]["function"], result + class TestDropParamsWithPromptCacheKey: """