From baf77a7f0664303e15aa5ccab5aa137c3b178285 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:07:32 -0800 Subject: [PATCH] fix(router): leave out the no-fallback note when a create stops after an order fallback A create that fails at a later order level after an earlier level fell back now ends without the "No fallback was attempted" note, since a fallback did run. The proxy's notice that the provider may already hold the file or batch still comes through --- litellm/router_rust/core.py | 7 ++++++- tests/unit/test_router/test_router_retries.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/litellm/router_rust/core.py b/litellm/router_rust/core.py index 900a9d197fd..f7c0d504684 100644 --- a/litellm/router_rust/core.py +++ b/litellm/router_rust/core.py @@ -4421,6 +4421,7 @@ class _Driver: # The primary level reached the end of `async_function_with_fallbacks_common_utils`, the # only place the outcome message is appended: not its early raises, nor its order path. self._explains_failure: bool = False + self._order_chain_ran: bool = False def _blocked(self, response: object, endpoint_name: str) -> str | None: """How the provider blocked a served response, the first check of @@ -4621,6 +4622,7 @@ class _Driver: self._level_errors[at] = last_hop if at == self._base_level: self._explains_failure = False + self._order_chain_ran = True def _logged_off_the_loop(self) -> bool: """Whether litellm logged the attempt's failure on a thread with no running loop. An async @@ -5445,7 +5447,10 @@ class _Driver: ) if ( self._base_level == 0 - and (self._explains_failure or self._is_create_that_may_already_exist(primary)) + and ( + self._explains_failure + or (not self._order_chain_ran and self._is_create_that_may_already_exist(primary)) + ) and self._core._host.dynamic().expose_router_debug_in_errors # pyright: ignore[reportPrivateUsage] # see above and hasattr(primary, "message") ): diff --git a/tests/unit/test_router/test_router_retries.py b/tests/unit/test_router/test_router_retries.py index 324eade8efd..46fd5149fad 100644 --- a/tests/unit/test_router/test_router_retries.py +++ b/tests/unit/test_router/test_router_retries.py @@ -923,10 +923,11 @@ async def test_a_create_that_times_out_on_a_fallback_is_not_sent_to_the_next_fal ) ) - with pytest.raises(openai.APITimeoutError): + with pytest.raises(openai.APITimeoutError) as raised: await _create(_batch_router(api_bases, num_retries=0), create) assert [route.call_count for route in routes] == [1, 1, 0] + assert "No fallback was attempted" not in raised.value.message @pytest.mark.parametrize("unsent_timeout", [httpx.ConnectTimeout, httpx.PoolTimeout])