From 68b378913188fdaad12f59b9882bad16fa7c248d Mon Sep 17 00:00:00 2001 From: Priyansh Nandwana Date: Fri, 2 Oct 2026 11:16:31 +0530 Subject: [PATCH] fix(websearch): fall back to the request model when a patch carries no model An empty patched model dispatched a provider-only name instead of falling back, and a provider-stripped sub-path copied into the patch lost its prefix. A patched model now keeps its own provider only when its leading segment names one. --- litellm/litellm_core_utils/core_helpers.py | 20 ++++++++++++------- .../litellm_core_utils/test_core_helpers.py | 9 +++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index a758a6bb46a..55094197d7f 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -38,15 +38,21 @@ def qualify_provider_stripped_model(model: str, custom_llm_provider: str) -> str return f"{custom_llm_provider}/{model}" -def qualify_agentic_followup_model(patch_model: str | None, model: str, custom_llm_provider: str) -> str: - """Resolve the model an agentic follow-up re-dispatches. +def names_a_provider(candidate: str) -> bool: + """Whether a model name's leading segment is a provider rather than part of the model.""" + import litellm + from litellm.litellm_core_utils.get_llm_provider_logic import is_registered_custom_provider - A hook that qualified its own patched model owns that choice, including a cross-provider - one, so only a bare patched model and the request's provider-stripped model get a prefix. - """ - if patch_model is None: + provider_names: Final = {getattr(provider, "value", provider) for provider in litellm.provider_list} + return candidate in provider_names or is_registered_custom_provider(candidate) + + +def qualify_agentic_followup_model(patch_model: str | None, model: str, custom_llm_provider: str) -> str: + """Resolve the model an agentic follow-up re-dispatches, keeping a hook's own provider.""" + if not patch_model: return qualify_provider_stripped_model(model, custom_llm_provider) - if "/" in patch_model: + leading_segment: Final = patch_model.split("/", 1)[0] + if leading_segment != patch_model and names_a_provider(leading_segment): return patch_model return qualify_provider_stripped_model(patch_model, custom_llm_provider) diff --git a/tests/unit/litellm_core_utils/test_core_helpers.py b/tests/unit/litellm_core_utils/test_core_helpers.py index 9964e1126ab..faf916429a9 100644 --- a/tests/unit/litellm_core_utils/test_core_helpers.py +++ b/tests/unit/litellm_core_utils/test_core_helpers.py @@ -645,3 +645,12 @@ class TestQualifyAgenticFollowUpModel: def test_a_patched_model_already_holding_the_request_provider_is_left_alone(self): assert qualify_agentic_followup_model("openai/gpt-4o", "gpt-4o", "openai") == "openai/gpt-4o" + + @pytest.mark.parametrize("patched", ["", None]) + def test_a_patch_without_a_model_falls_back_to_the_request_model(self, patched): + assert qualify_agentic_followup_model(patched, "gpt-4o", "openai") == "openai/gpt-4o" + + def test_a_patched_sub_path_model_still_takes_the_request_provider(self): + assert qualify_agentic_followup_model("mantle/anthropic.claude-sonnet-5", "gpt-4o", "bedrock") == ( + "bedrock/mantle/anthropic.claude-sonnet-5" + )