diff --git a/litellm/utils.py b/litellm/utils.py index 8fe3fd84fbd..991fc642d10 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -3086,8 +3086,67 @@ def token_counter( def supports_httpx_timeout(custom_llm_provider: str) -> bool: """ Helper function to know if a provider implementation supports httpx timeout + + A provider supports ``httpx.Timeout`` when its completion is dispatched to + LiteLLM's own httpx clients (``AsyncHTTPHandler``/``HTTPHandler`` accept + ``float | httpx.Timeout``): the shared ``BaseLLMHTTPHandler.completion`` + path in ``main.py``, or the ``CodestralTextCompletion`` handler which uses + ``AsyncHTTPHandler`` directly. Providers routed through third-party SDKs + that expect a plain float (for example ``vertex_ai``, ``anthropic``, + ``azure_ai``, ``gemini``) are intentionally not listed, and neither are + OpenAI-SDK-based OpenAI-compatible providers (handled conservatively for + now, matching the pre-existing ``openai``/``azure`` coverage). """ - supported_providers: Final = ["openai", "azure", "bedrock"] + supported_providers: Final = [ + # SDK clients that accept httpx.Timeout natively + "openai", + "azure", + "bedrock", + # Providers dispatched to BaseLLMHTTPHandler.completion (shared httpx handler) + "a2a", + "anthropic_text", + "bedrock_mantle", + "bytez", + "cloudflare", + "cohere", + "cohere_chat", + "cometapi", + "compactifai", + "databricks", + "databricks_agent", + "datarobot", + "deepseek", + "edenai", + "fal_ai", + "fireworks_ai", + "gdc", + "gigachat", + "gradient_ai", + "groq", + "heroku", + "hosted_vllm", + "huggingface", + "langflow", + "langgraph", + "lemonade", + "minimax", + "mistral", + "nadir", + "oci", + "ollama", + "ollama_chat", + "openrouter", + "ovhcloud", + "ragflow", + "scaledown", + "snowflake", + "text-completion-codestral", + "together_ai", + "triton", + "vercel_ai_gateway", + "watsonx_text", + "xai", + ] if custom_llm_provider in supported_providers: return True diff --git a/tests/unit/test_completion_timeout_resolution.py b/tests/unit/test_completion_timeout_resolution.py index 37b5cceef5a..2f101336c7a 100644 --- a/tests/unit/test_completion_timeout_resolution.py +++ b/tests/unit/test_completion_timeout_resolution.py @@ -143,6 +143,75 @@ def test_httpx_timeout_coerced_for_provider_without_httpx_timeout_support(): assert not isinstance(out, httpx.Timeout) +SHARED_HTTPX_HANDLER_PROVIDERS: Final = ( + "a2a", + "anthropic_text", + "bedrock_mantle", + "bytez", + "cloudflare", + "cohere", + "cohere_chat", + "cometapi", + "compactifai", + "databricks", + "databricks_agent", + "datarobot", + "deepseek", + "edenai", + "fal_ai", + "fireworks_ai", + "gdc", + "gigachat", + "gradient_ai", + "groq", + "heroku", + "hosted_vllm", + "huggingface", + "langflow", + "langgraph", + "lemonade", + "minimax", + "mistral", + "nadir", + "oci", + "ollama", + "ollama_chat", + "openrouter", + "ovhcloud", + "ragflow", + "scaledown", + "snowflake", + "text-completion-codestral", + "together_ai", + "triton", + "vercel_ai_gateway", + "watsonx_text", + "xai", +) + + +@pytest.mark.parametrize("provider", SHARED_HTTPX_HANDLER_PROVIDERS) +def test_httpx_timeout_preserved_for_shared_httpx_handler_providers(provider: str) -> None: + """Providers dispatched to the shared httpx handler accept httpx.Timeout (issue #47822).""" + assert supports_httpx_timeout(provider) is True + t: Final = httpx.Timeout(40.0, connect=5.0) + resolved: Final = CompletionTimeout.resolve( + t, + {}, + provider, + global_timeout=None, + supports_httpx_timeout=supports_httpx_timeout, + stream_kind=StreamKind.NOT_STREAMED, + ) + assert resolved is t + + +@pytest.mark.parametrize("provider", ("azure_ai", "vertex_ai", "anthropic", "gemini", "watsonx")) +def test_httpx_timeout_still_coerced_for_sdk_providers(provider: str) -> None: + """Providers on third-party SDKs keep the float coercion.""" + assert supports_httpx_timeout(provider) is False + + def test_httpx_timeout_preserved_for_openai(): t = httpx.Timeout(40.0, connect=5.0) out = CompletionTimeout.resolve(