diff --git a/litellm/router_utils/common_utils.py b/litellm/router_utils/common_utils.py index 49cca8ee99e..516a54a5337 100644 --- a/litellm/router_utils/common_utils.py +++ b/litellm/router_utils/common_utils.py @@ -176,6 +176,26 @@ def filter_team_based_models( ] +def _is_web_search_tool(tool: Mapping[str, object]) -> bool: + tool_type: Final = str(tool.get("type") or "") + tool_name: Final = str(tool.get("name") or "") + return ( + tool_type in ("web_search", "web_search_preview") + or tool_type.startswith("web_search_") + or tool_name == "web_search" + ) + + +def _is_web_fetch_tool(tool: Mapping[str, object]) -> bool: + tool_type: Final = str(tool.get("type") or "") + tool_name: Final = str(tool.get("name") or "") + return ( + tool_type in ("web_fetch", "web_fetch_preview") + or tool_type.startswith("web_fetch_") + or tool_name == "web_fetch" + ) + + def _deployment_supports_web_search(deployment: dict) -> bool: """ Check if a deployment supports web search. @@ -196,12 +216,27 @@ def _deployment_supports_web_search(deployment: dict) -> bool: return True +def _deployment_supports_web_fetch(deployment: Mapping[str, object]) -> bool: + """ + Check if a deployment supports web fetch. + + Priority: + 1. Check config-level override in model_info.supports_web_fetch + 2. Default to True (assume supported unless explicitly disabled) + """ + raw_info: Final = deployment.get("model_info") + if isinstance(raw_info, dict) and "supports_web_fetch" in raw_info: + return bool(raw_info["supports_web_fetch"]) + + return True + + def filter_web_search_deployments( healthy_deployments: list[dict] | dict, request_kwargs: dict | None = None, ) -> list[dict] | dict: """ - If the request is websearch, filter out deployments that don't support web search + If the request is websearch or webfetch, filter out deployments that don't support it """ if request_kwargs is None: return healthy_deployments @@ -210,21 +245,26 @@ def filter_web_search_deployments( if isinstance(healthy_deployments, dict): return healthy_deployments - is_web_search_request = False tools: Final = request_kwargs.get("tools") or [] - for tool in tools: - # These are the two websearch tools for OpenAI / Azure. - if tool.get("type") == "web_search" or tool.get("type") == "web_search_preview": - is_web_search_request = True - break + is_web_search_request: Final = any(isinstance(tool, dict) and _is_web_search_tool(tool) for tool in tools) + is_web_fetch_request: Final = any(isinstance(tool, dict) and _is_web_fetch_tool(tool) for tool in tools) - if not is_web_search_request: + if not is_web_search_request and not is_web_fetch_request: return healthy_deployments - # Filter out deployments that don't support web search - final_deployments: Final = [d for d in healthy_deployments if _deployment_supports_web_search(d)] + # Filter out deployments that don't support requested capabilities + final_deployments: Final = [ + d + for d in healthy_deployments + if (not is_web_search_request or _deployment_supports_web_search(d)) + and (not is_web_fetch_request or _deployment_supports_web_fetch(d)) + ] if len(healthy_deployments) > 0 and len(final_deployments) == 0: - verbose_logger.warning("No deployments support web search for request") + if is_web_search_request: + verbose_logger.warning("No deployments support web search for request") + if is_web_fetch_request: + verbose_logger.warning("No deployments support web fetch for request") + return final_deployments diff --git a/tests/test_litellm/router_utils/test_router_utils_common_utils.py b/tests/test_litellm/router_utils/test_router_utils_common_utils.py index ac18b4889dd..9b1dbeee1b8 100644 --- a/tests/test_litellm/router_utils/test_router_utils_common_utils.py +++ b/tests/test_litellm/router_utils/test_router_utils_common_utils.py @@ -353,6 +353,25 @@ class TestFilterWebSearchDeployments: # Should return the dict unchanged, not filter it assert result == deployment + def test_anthropic_versioned_web_search_filters_unsupported(self, sample_deployments): + """Anthropic versioned web_search tool types (e.g. web_search_20250305) should filter unsupported deployments""" + request_kwargs = {"tools": [{"type": "web_search_20250305", "name": "web_search"}]} + result = filter_web_search_deployments(sample_deployments, request_kwargs) + assert len(result) == 2 + result_ids = [d["model_info"]["id"] for d in result] + assert "deployment-3" not in result_ids + + def test_anthropic_web_fetch_filters_unsupported(self): + """Anthropic web_fetch tool types (e.g. web_fetch_20250910) should filter out supports_web_fetch=False deployments""" + deployments = [ + {"model_info": {"id": "d1", "supports_web_fetch": True}}, + {"model_info": {"id": "d2", "supports_web_fetch": False}}, + ] + request_kwargs = {"tools": [{"type": "web_fetch_20250910", "name": "web_fetch"}]} + result = filter_web_search_deployments(deployments, request_kwargs) + assert len(result) == 1 + assert result[0]["model_info"]["id"] == "d1" + def test_invalidate_model_group_info_cache(): """Test that _invalidate_model_group_info_cache clears the LRU cache."""