From 9ed502f375677be569040b802b3dc821369b8b2b Mon Sep 17 00:00:00 2001 From: tin-berri Date: Mon, 26 Oct 2026 21:46:43 -0400 Subject: [PATCH] fix(decisions): honor base_url alias for custom backends (#48505) Co-authored-by: Azmi Muwahid --- litellm/decisions/call.py | 8 +++- tests/unit/decisions/test_main.py | 62 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/litellm/decisions/call.py b/litellm/decisions/call.py index abbeccc10b3..3b73f65c917 100644 --- a/litellm/decisions/call.py +++ b/litellm/decisions/call.py @@ -456,10 +456,14 @@ def prepare_call( extra_headers: Mapping[str, str] | None, kwargs: Mapping[str, object], ) -> DecisionsCall: + base_url_alias: Final = kwargs.get("base_url") + call_api_base: Final = ( + base_url_alias if api_base is None and isinstance(base_url_alias, str) and base_url_alias else api_base + ) upstream_model, provider, dynamic_api_key, dynamic_api_base = litellm.get_llm_provider( model=model, custom_llm_provider=custom_llm_provider, - api_base=api_base, + api_base=call_api_base, api_key=api_key, ) provider_config: Final = _provider_config(upstream_model, provider) @@ -488,7 +492,7 @@ def prepare_call( try: resolved_api_base, static_api_key = provider_config.resolve_connection( - dynamic_api_base or api_base, dynamic_api_key or api_key + dynamic_api_base or call_api_base, dynamic_api_key or api_key ) except ValueError as error: raise litellm.BadRequestError(message=str(error), model=model, llm_provider=provider) from error diff --git a/tests/unit/decisions/test_main.py b/tests/unit/decisions/test_main.py index e2385a2e215..5b32008b279 100644 --- a/tests/unit/decisions/test_main.py +++ b/tests/unit/decisions/test_main.py @@ -400,6 +400,68 @@ async def test_systemone_uses_evaluation_pricing(respx_mock: respx.MockRouter, d ) +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("base_url", "expected_api_base"), + [ + ("https://onprem.example/v1", "https://onprem.example/v1"), + ("", "https://fallback.example/v1"), + (None, "https://fallback.example/v1"), + (123, "https://fallback.example/v1"), + ], +) +async def test_openai_decisions_honors_base_url_alias( + respx_mock: respx.MockRouter, + monkeypatch: pytest.MonkeyPatch, + base_url: str | int | None, + expected_api_base: str, +) -> None: + monkeypatch.setenv("OPENAI_BASE_URL", "https://fallback.example/v1") + route: Final = respx_mock.post(f"{expected_api_base}/decisions").respond(json=_OPENAI_RESPONSE) + response: Final = await litellm.adecisions( + model="openai/gpt-6-luna", + input="The package arrived broken.", + questions=[{"type": "predicate", "name": "is_defect", "instructions": "Is this a defect?"}], + api_key="backend-secret-key", + base_url=base_url, + ) + + assert route.called + assert route.calls[0].request.headers["authorization"] == "Bearer backend-secret-key" + assert response._hidden_params["model"] == "openai/gpt-6-luna" + + +@pytest.mark.asyncio +async def test_openai_systemone_honors_base_url_alias(respx_mock: respx.MockRouter) -> None: + route: Final = respx_mock.post("https://onprem.example/v1/decisions").respond(json=_OPENAI_RESPONSE) + response: Final = await litellm.asystemone( + model="openai/gpt-6-luna", + state="The package arrived broken.", + questions=_QUESTIONS, + api_key="backend-secret-key", + base_url="https://onprem.example/v1", + ) + + assert route.called + assert route.calls[0].request.headers["authorization"] == "Bearer backend-secret-key" + assert response._hidden_params["model"] == "openai/gpt-6-luna" + + +@pytest.mark.asyncio +async def test_openai_decisions_api_base_wins_over_base_url_alias(respx_mock: respx.MockRouter) -> None: + route: Final = respx_mock.post("https://api.openai.com/v1/decisions").respond(json=_OPENAI_RESPONSE) + await litellm.adecisions( + model="openai/gpt-6-luna", + input="The package arrived broken.", + questions=[{"type": "predicate", "name": "is_defect", "instructions": "Is this a defect?"}], + api_key="test-key", + api_base="https://api.openai.com/v1", + base_url="https://onprem.example/v1", + ) + + assert route.called + + @pytest.mark.asyncio async def test_systemone_falls_back_to_plain_pricing_without_decisions_row( respx_mock: respx.MockRouter,