From f4f704a5055bf9a4f0f821e83dedd354fb6d1e07 Mon Sep 17 00:00:00 2001 From: Chengxuan Wang Date: Thu, 11 Jun 2026 14:54:41 -0700 Subject: [PATCH] address review: copy fallbacks before get_fallback_model_group (it pops string-format entries) --- .../router_utils/fallback_event_handlers.py | 6 ++++- .../test_fallback_event_handlers.py | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/litellm/router_utils/fallback_event_handlers.py b/litellm/router_utils/fallback_event_handlers.py index 09377f40782..affaf629931 100644 --- a/litellm/router_utils/fallback_event_handlers.py +++ b/litellm/router_utils/fallback_event_handlers.py @@ -66,8 +66,12 @@ def build_mid_stream_continuation_messages( candidate_models: List[Optional[str]] = [model_group] if fallbacks is not None and model_group is not None: try: + # Shallow copy: get_fallback_model_group POPS a matching entry from + # flat string-format fallback lists — mutating the live list here + # would silently drop one fallback target before the actual + # fallback execution runs. fallback_model_group, _ = get_fallback_model_group( - fallbacks=fallbacks, model_group=model_group + fallbacks=list(fallbacks), model_group=model_group ) if fallback_model_group: candidate_models.extend(fallback_model_group) diff --git a/tests/test_litellm/router_utils/test_fallback_event_handlers.py b/tests/test_litellm/router_utils/test_fallback_event_handlers.py index d83e8c0b551..316429cfe0a 100644 --- a/tests/test_litellm/router_utils/test_fallback_event_handlers.py +++ b/tests/test_litellm/router_utils/test_fallback_event_handlers.py @@ -119,3 +119,26 @@ def test_unrelated_fallback_groups_do_not_affect_prefill(): fallbacks=[{"some-other-model": ["claude-sonnet-4-6"]}], ) _assert_legacy_prefill(result) + + +def test_fallbacks_list_is_not_mutated_by_capability_check(): + """get_fallback_model_group pops a matching entry from flat string-format + fallback lists — the capability check must operate on a copy, or the actual + fallback execution silently loses one destination per mid-stream retry.""" + string_format_fallbacks = ["gpt-4", "claude-sonnet-4-6"] + build_mid_stream_continuation_messages( + messages=MESSAGES, + generated_content=PARTIAL, + model_group="gpt-4", + fallbacks=string_format_fallbacks, + ) + assert string_format_fallbacks == ["gpt-4", "claude-sonnet-4-6"] + + dict_format_fallbacks = [{"gpt-4": ["claude-sonnet-4-6"]}] + build_mid_stream_continuation_messages( + messages=MESSAGES, + generated_content=PARTIAL, + model_group="gpt-4", + fallbacks=dict_format_fallbacks, + ) + assert dict_format_fallbacks == [{"gpt-4": ["claude-sonnet-4-6"]}]