From 7a95037c75e98f1f81de65d30bcba2680406b69c Mon Sep 17 00:00:00 2001 From: Om Patil Date: Thu, 22 Oct 2026 01:46:11 +0000 Subject: [PATCH] fix(timeout): preserve httpx.Timeout connect/read for all providers supports_httpx_timeout was an allowlist of only openai/azure/bedrock. For every other provider (mistral, anthropic, ...), CompletionTimeout._coerce collapsed httpx.Timeout(10, connect=1) to the single float 10.0, so the connect timeout was ignored and stalled TCP connects waited the full read timeout. LiteLLM's own httpx handlers accept httpx.Timeout objects, so returning True for all providers is correct. Update the unit tests that previously asserted coercion for azure_ai. Fixes #47822 --- litellm/utils.py | 19 ++++++---- .../test_completion_timeout_resolution.py | 37 ++++++++++--------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/litellm/utils.py b/litellm/utils.py index 7de7ce6188d..17fdb1654fa 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2848,14 +2848,19 @@ def token_counter( def supports_httpx_timeout(custom_llm_provider: str) -> bool: """ - Helper function to know if a provider implementation supports httpx timeout + Helper function to know if a provider implementation supports httpx.Timeout. + + LiteLLM's own httpx handlers (and the OpenAI/Azure/Bedrock SDKs) accept + ``httpx.Timeout`` objects, preserving connect/read/write/pool components. + Returning False collapses the object to a single float (the read timeout), + which drops the connect timeout and is incorrect for every provider that + uses httpx under the hood. + + Historically this was an allowlist of three providers; that caused + connect timeouts to be ignored for mistral and many others. Prefer True + unless a specific provider is known to reject ``httpx.Timeout``. """ - supported_providers: Final = ["openai", "azure", "bedrock"] - - if custom_llm_provider in supported_providers: - return True - - return False + return True def supports_system_messages(model: str, custom_llm_provider: str | None) -> bool: diff --git a/tests/unit/test_completion_timeout_resolution.py b/tests/unit/test_completion_timeout_resolution.py index 37b5cceef5a..a9f73fec39f 100644 --- a/tests/unit/test_completion_timeout_resolution.py +++ b/tests/unit/test_completion_timeout_resolution.py @@ -129,18 +129,20 @@ def test_fallback_600_when_no_global_timeout(): ) -def test_httpx_timeout_coerced_for_provider_without_httpx_timeout_support(): +def test_httpx_timeout_preserved_for_non_openai_providers(): + """httpx.Timeout must keep connect/read components for all providers (not only openai/azure/bedrock).""" t = httpx.Timeout(50.0, connect=2.0) out = CompletionTimeout.resolve( t, {}, - "azure_ai", + "mistral", global_timeout=None, supports_httpx_timeout=supports_httpx_timeout, stream_kind=StreamKind.NOT_STREAMED, ) - assert out == 50.0 - assert not isinstance(out, httpx.Timeout) + assert out is t + assert isinstance(out, httpx.Timeout) + assert out.connect == 2.0 def test_httpx_timeout_preserved_for_openai(): @@ -174,21 +176,22 @@ def test_sdk_timeout_preserves_components_for_httpx_providers( assert resolved.as_dict() == timeout.as_dict() -@pytest.mark.parametrize("read_timeout, expected", ((23.0, 23.0), (None, 600.0))) -def test_sdk_timeout_coerces_read_timeout_for_providers_without_httpx_support( - read_timeout: float | None, expected: float +@pytest.mark.parametrize("read_timeout", (23.0, None)) +def test_sdk_timeout_preserves_components_for_all_providers( + read_timeout: float | None, ) -> None: - assert ( - CompletionTimeout.resolve( - SDKTimeout(connect=2.0, read=read_timeout, write=5.0, pool=7.0), - {}, - "azure_ai", - global_timeout=None, - supports_httpx_timeout=supports_httpx_timeout, - stream_kind=StreamKind.NOT_STREAMED, - ) - == expected + """SDK Timeout objects must keep their components for every provider.""" + timeout = SDKTimeout(connect=2.0, read=read_timeout, write=5.0, pool=7.0) + resolved = CompletionTimeout.resolve( + timeout, + {}, + "mistral", + global_timeout=None, + supports_httpx_timeout=supports_httpx_timeout, + stream_kind=StreamKind.NOT_STREAMED, ) + assert isinstance(resolved, httpx.Timeout) + assert resolved.as_dict() == timeout.as_dict() @pytest.mark.parametrize(