From 6234434de69a800cbef0335145e34eb3af782cae Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:40:05 -0700 Subject: [PATCH] fix(websearch): route a hook's model on a JSON-configured provider and keep the request provider on an echoed model --- litellm/litellm_core_utils/core_helpers.py | 9 +++++++-- litellm/llms/custom_httpx/llm_http_handler.py | 7 ++++--- tests/unit/litellm_core_utils/test_core_helpers.py | 10 ++++++++++ tests/unit/llms/custom_httpx/test_llm_http_handler.py | 6 ++++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index ae647e0cfc1..49ec398fa1d 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -42,14 +42,19 @@ 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 + from litellm.llms.openai_like.json_loader import JSONProviderRegistry provider_names: Final = {getattr(provider, "value", provider) for provider in litellm.provider_list} - return candidate in provider_names or is_registered_custom_provider(candidate) + return ( + candidate in provider_names + or JSONProviderRegistry.exists(candidate) + 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: + if not patch_model or patch_model == model: return qualify_provider_stripped_model(model, custom_llm_provider) leading_segment: Final = patch_model.split("/", 1)[0] if leading_segment != patch_model and names_a_provider(leading_segment): diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 7855ff1cffe..ea0e2a8a16b 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -48,7 +48,10 @@ from litellm.litellm_core_utils.audio_utils.subtitle_utils import ( SUBTITLE_RESPONSE_FORMATS, synthesize_subtitle_document, ) -from litellm.litellm_core_utils.core_helpers import set_provider_response_headers_in_hidden_params +from litellm.litellm_core_utils.core_helpers import ( + qualify_agentic_followup_model, + set_provider_response_headers_in_hidden_params, +) from litellm.litellm_core_utils.get_litellm_params import AWS_CREDENTIAL_KWARGS_KEYS from litellm.litellm_core_utils.llm_request_utils import serialize_multipart_form_fields from litellm.litellm_core_utils.realtime_errors import ( @@ -5847,8 +5850,6 @@ class BaseLLMHTTPHandler: if patch.messages is None: raise ValueError("Agentic loop plan missing patched messages") - from litellm.litellm_core_utils.core_helpers import qualify_agentic_followup_model - full_model_name: Final = qualify_agentic_followup_model(patch.model, model, custom_llm_provider) optional_params_for_followup: Final = dict(optional_params) diff --git a/tests/unit/litellm_core_utils/test_core_helpers.py b/tests/unit/litellm_core_utils/test_core_helpers.py index 2ddf15f5edc..55ce234c7a3 100644 --- a/tests/unit/litellm_core_utils/test_core_helpers.py +++ b/tests/unit/litellm_core_utils/test_core_helpers.py @@ -662,6 +662,16 @@ def test_a_patched_sub_path_model_still_takes_the_request_provider(): ) +def test_a_patched_model_on_a_json_configured_provider_reaches_that_provider(): + assert qualify_agentic_followup_model("aihubmix/gpt-4o", "gpt-4o", "openai") == "aihubmix/gpt-4o" + + +def test_a_patch_echoing_the_request_model_keeps_the_request_provider(): + assert qualify_agentic_followup_model("openai/gpt-4o", "openai/gpt-4o", "openrouter") == ( + "openrouter/openai/gpt-4o" + ) + + def test_spend_metadata_merge_survives_the_logging_thread_inserting_into_the_request_metadata() -> None: metadata: Final[dict[str, object]] = {} diff --git a/tests/unit/llms/custom_httpx/test_llm_http_handler.py b/tests/unit/llms/custom_httpx/test_llm_http_handler.py index 2b301c663c5..e662caee6a0 100644 --- a/tests/unit/llms/custom_httpx/test_llm_http_handler.py +++ b/tests/unit/llms/custom_httpx/test_llm_http_handler.py @@ -5368,3 +5368,9 @@ class TestAgenticFollowUpKeepsTheProviderPrefix: assert response.model == "claude-sonnet-4-5" assert response._hidden_params["custom_llm_provider"] == "anthropic" + + @pytest.mark.asyncio + async def test_a_hook_echoing_an_org_style_model_stays_on_the_request_provider(self): + response = await self._run_followup("openai/gpt-4o", "openrouter", patched_model="openai/gpt-4o") + + assert response._hidden_params["custom_llm_provider"] == "openrouter"