From f42804995b52c3570b30981ae153f922d73fb6c3 Mon Sep 17 00:00:00 2001 From: kigland Date: Tue, 9 Jun 2026 09:03:35 +0800 Subject: [PATCH] Skip name-only namespace tools --- .../vertex_and_google_ai_studio_gemini.py | 6 ++- .../transformation.py | 4 +- ...test_vertex_and_google_ai_studio_gemini.py | 13 +++--- .../test_litellm_completion_responses.py | 44 ++++++++++++++++--- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index c3b2b8c1b3a..1500e674b55 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -683,7 +683,11 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): elif tool.get("type") == "namespace" and ( not tool.get("description") or not tool.get("parameters") ): - raise ValueError("Namespace tools require description and parameters") + verbose_logger.warning( + "Skipping namespace tool without description or parameters: %s", + tool.get("name"), + ) + continue # Handle tools with 'type' field (OpenAI spec compliance) Ignore this field -> https://github.com/BerriAI/litellm/issues/14644#issuecomment-3342061838 elif "type" in tool: tool = {k: tool[k] for k in tool if k != "type"} diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index c2aa7e9490c..bce95c81b67 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -1392,9 +1392,7 @@ class LiteLLMCompletionResponsesConfig: not typed_tool.get("description") or not typed_tool.get("parameters") ): - raise ValueError( - "Namespace tools require description and parameters" - ) + continue # Ensure parameters has "type": "object" as required by providers like Anthropic parameters = dict(typed_tool.get("parameters", {}) or {}) if not parameters or "type" not in parameters: diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py index c99b52805fb..d72b6faea9c 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py @@ -2917,14 +2917,15 @@ def test_vertex_ai_function_declarations_with_other_tools_separate(): assert func_tool["function_declarations"][0]["name"] == "get_weather" -def test_vertex_ai_namespace_tool_without_schema_raises(): +def test_vertex_ai_namespace_tool_without_schema_skips(): v = VertexGeminiConfig() - with pytest.raises(ValueError, match="Namespace tools require description"): - v._map_function( - value=[{"type": "namespace", "name": "mcp__node_repl"}], - optional_params={}, - ) + tools = v._map_function( + value=[{"type": "namespace", "name": "mcp__node_repl"}], + optional_params={}, + ) + + assert tools == [] def test_vertex_ai_single_tool_type_still_works(): diff --git a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py index b2d02a4e677..68ba44f8e39 100644 --- a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py +++ b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py @@ -1228,17 +1228,49 @@ class TestToolTransformation: ) assert result_tool["function"]["parameters"] == namespace_tool["parameters"] - def test_transform_namespace_tools_without_schema_raises(self): - """Name-only namespace tools cannot be converted into usable functions.""" + def test_transform_namespace_tools_without_schema_skips(self): + """Name-only namespace tools are skipped instead of becoming hollow functions.""" namespace_tool = { "type": "namespace", "name": "mcp__node_repl", } - with pytest.raises(ValueError, match="Namespace tools require description"): - LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( - tools=[namespace_tool] - ) + ( + result_tools, + web_search_options, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=[namespace_tool] + ) + + assert result_tools == [] + assert web_search_options is None + + def test_transform_namespace_tools_without_schema_keeps_other_tools(self): + """Skipping name-only namespace tools should not abort the whole request.""" + function_tool = { + "type": "function", + "name": "get_weather", + "description": "Get weather", + "parameters": { + "type": "object", + "properties": {"location": {"type": "string"}}, + }, + } + namespace_tool = { + "type": "namespace", + "name": "mcp__node_repl", + } + + ( + result_tools, + web_search_options, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=[namespace_tool, function_tool] + ) + + assert web_search_options is None + assert len(result_tools) == 1 + assert result_tools[0]["function"]["name"] == "get_weather" def test_transform_code_execution_tools(self): """Test that code_execution tools are passed through as-is"""