From f7c33f7c814cce002c4cd5f1045ec3bc0832dcb8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:04:14 +0000 Subject: [PATCH 1/2] fix(ollama): resolve explicit api_base before litellm.api_base global and forward it to the bridge model-info lookup Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/main.py | 8 ++--- tests/test_litellm/test_main.py | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/litellm/main.py b/litellm/main.py index c341db08155..5e849468b2f 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1022,7 +1022,7 @@ def responses_api_bridge_check( try: model_info = cast( dict, - _get_model_info_helper(model=model, custom_llm_provider=custom_llm_provider), + _get_model_info_helper(model=model, custom_llm_provider=custom_llm_provider, api_base=api_base), ) if model_info.get("mode") is None and model.startswith("responses/"): model = model.replace("responses/", "") @@ -4281,7 +4281,7 @@ def _complete_ollama(ctx: _CompletionDispatchContext) -> _CompletionDispatchResu stream: Final = ctx.stream timeout: Final = ctx.timeout - api_base = litellm.api_base or api_base or get_secret("OLLAMA_API_BASE") or "http://localhost:11434" + api_base = api_base or litellm.api_base or get_secret("OLLAMA_API_BASE") or "http://localhost:11434" if api_key is not None and "Authorization" not in headers: headers["Authorization"] = f"Bearer {api_key}" @@ -4321,7 +4321,7 @@ def _complete_ollama_chat(ctx: _CompletionDispatchContext) -> _CompletionDispatc stream: Final = ctx.stream timeout: Final = ctx.timeout - api_base = litellm.api_base or api_base or get_secret("OLLAMA_API_BASE") or "http://localhost:11434" + api_base = api_base or litellm.api_base or get_secret("OLLAMA_API_BASE") or "http://localhost:11434" api_key = api_key or litellm.ollama_key or os.environ.get("OLLAMA_API_KEY") or litellm.api_key if api_key is not None and "Authorization" not in headers: @@ -6689,7 +6689,7 @@ def embedding( api_key=api_key, ) elif custom_llm_provider == "ollama": - api_base = litellm.api_base or api_base or get_secret_str("OLLAMA_API_BASE") or "http://localhost:11434" + api_base = api_base or litellm.api_base or get_secret_str("OLLAMA_API_BASE") or "http://localhost:11434" if isinstance(input, str): input = [input] diff --git a/tests/test_litellm/test_main.py b/tests/test_litellm/test_main.py index 8cf878d05d9..228aed37af9 100644 --- a/tests/test_litellm/test_main.py +++ b/tests/test_litellm/test_main.py @@ -801,6 +801,58 @@ def test_responses_api_bridge_check_gpt_5_5_tools_plus_reasoning_routes_to_respo assert model_info.get("mode") == "responses" +def test_responses_api_bridge_check_forwards_api_base_to_model_info_helper(): + """Regression test for https://github.com/BerriAI/litellm/issues/37041 -- the bridge + check's model-info lookup must hit the request's api_base, not fall back to the + provider default (localhost:11434 for ollama).""" + from litellm.main import responses_api_bridge_check + + with patch("litellm.main._get_model_info_helper") as mock_get_model_info: + mock_get_model_info.return_value = {"mode": "chat"} + responses_api_bridge_check( + model="llama3", + custom_llm_provider="ollama", + api_base="http://my-host:30000", + ) + + assert mock_get_model_info.call_args.kwargs["api_base"] == "http://my-host:30000" + + +@pytest.mark.parametrize("model", ["ollama/llama3", "ollama_chat/llama3"]) +def test_ollama_completion_explicit_api_base_overrides_global(model, monkeypatch): + """Regression test for https://github.com/BerriAI/litellm/issues/26170 -- the explicit + api_base kwarg must win over the litellm.api_base global, matching the openai provider.""" + monkeypatch.setattr(litellm, "api_base", "https://api.deepseek.com") + + with patch("litellm.main._get_model_info_helper") as mock_get_model_info, patch.object( + litellm_main.base_llm_http_handler, "completion" + ) as mock_completion: + mock_get_model_info.return_value = {"mode": "chat"} + mock_completion.return_value = litellm.ModelResponse() + litellm.completion( + model=model, + messages=[{"role": "user", "content": "hi"}], + api_base="http://my-host:30000", + ) + + assert mock_completion.call_args.kwargs["api_base"] == "http://my-host:30000" + + +def test_ollama_embedding_explicit_api_base_overrides_global(monkeypatch): + """Regression test for https://github.com/BerriAI/litellm/issues/26170 (embedding path).""" + monkeypatch.setattr(litellm, "api_base", "https://api.deepseek.com") + + with patch("litellm.main.ollama.ollama_embeddings") as mock_embeddings: + mock_embeddings.return_value = litellm.EmbeddingResponse() + litellm.embedding( + model="ollama/qwen3-embedding:0.6b", + input="hello", + api_base="http://my-host:30000", + ) + + assert mock_embeddings.call_args.kwargs["api_base"] == "http://my-host:30000" + + def test_responses_api_bridge_check_azure_gpt_5_4_tools_plus_reasoning_routes_to_responses(): """Azure gpt-5.4 with both tools and reasoning_effort should route to Responses API.""" from litellm.main import responses_api_bridge_check From 31ae093cbfc79529a8467e79b6980491ac98e7b7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:14:19 +0000 Subject: [PATCH 2/2] test(ollama): assert api_base resolution at the HTTP boundary with respx Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/test_litellm/test_main.py | 104 ++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/tests/test_litellm/test_main.py b/tests/test_litellm/test_main.py index 228aed37af9..f3db124a73c 100644 --- a/tests/test_litellm/test_main.py +++ b/tests/test_litellm/test_main.py @@ -801,56 +801,94 @@ def test_responses_api_bridge_check_gpt_5_5_tools_plus_reasoning_routes_to_respo assert model_info.get("mode") == "responses" -def test_responses_api_bridge_check_forwards_api_base_to_model_info_helper(): +_OLLAMA_SHOW_RESPONSE: Final = { + "model_info": {"llama.context_length": 8192}, + "capabilities": ["completion"], +} + +_OLLAMA_GENERATE_RESPONSE: Final = { + "model": "llama3-custom", + "response": "hello from ollama", + "done": True, + "done_reason": "stop", + "prompt_eval_count": 5, + "eval_count": 4, +} + +_OLLAMA_CHAT_RESPONSE: Final = { + "model": "llama3-custom", + "message": {"role": "assistant", "content": "hello from ollama"}, + "done": True, + "done_reason": "stop", + "prompt_eval_count": 5, + "eval_count": 4, +} + + +def test_responses_api_bridge_check_forwards_api_base_to_model_info_lookup(respx_mock: respx.MockRouter): """Regression test for https://github.com/BerriAI/litellm/issues/37041 -- the bridge check's model-info lookup must hit the request's api_base, not fall back to the provider default (localhost:11434 for ollama).""" from litellm.main import responses_api_bridge_check - with patch("litellm.main._get_model_info_helper") as mock_get_model_info: - mock_get_model_info.return_value = {"mode": "chat"} - responses_api_bridge_check( - model="llama3", - custom_llm_provider="ollama", - api_base="http://my-host:30000", - ) + show_route = respx_mock.post("http://my-host:30000/api/show").respond(json=_OLLAMA_SHOW_RESPONSE) - assert mock_get_model_info.call_args.kwargs["api_base"] == "http://my-host:30000" + litellm.get_model_info.cache_clear() + model_info, model = responses_api_bridge_check( + model="llama3-custom", + custom_llm_provider="ollama", + api_base="http://my-host:30000", + ) + + assert show_route.called + assert model == "llama3-custom" + assert model_info["max_tokens"] == 8192 -@pytest.mark.parametrize("model", ["ollama/llama3", "ollama_chat/llama3"]) -def test_ollama_completion_explicit_api_base_overrides_global(model, monkeypatch): +@pytest.mark.parametrize( + "model, completion_path, completion_response", + [ + ("ollama/llama3-custom", "/api/generate", _OLLAMA_GENERATE_RESPONSE), + ("ollama_chat/llama3-custom", "/api/chat", _OLLAMA_CHAT_RESPONSE), + ], +) +def test_ollama_completion_explicit_api_base_overrides_global( + model, completion_path, completion_response, monkeypatch, respx_mock: respx.MockRouter +): """Regression test for https://github.com/BerriAI/litellm/issues/26170 -- the explicit api_base kwarg must win over the litellm.api_base global, matching the openai provider.""" - monkeypatch.setattr(litellm, "api_base", "https://api.deepseek.com") + monkeypatch.setattr(litellm, "api_base", "https://unrelated-global.example.com") + respx_mock.post("http://my-host:30000/api/show").respond(json=_OLLAMA_SHOW_RESPONSE) + completion_route = respx_mock.post(f"http://my-host:30000{completion_path}").respond(json=completion_response) - with patch("litellm.main._get_model_info_helper") as mock_get_model_info, patch.object( - litellm_main.base_llm_http_handler, "completion" - ) as mock_completion: - mock_get_model_info.return_value = {"mode": "chat"} - mock_completion.return_value = litellm.ModelResponse() - litellm.completion( - model=model, - messages=[{"role": "user", "content": "hi"}], - api_base="http://my-host:30000", - ) + litellm.get_model_info.cache_clear() + response = litellm.completion( + model=model, + messages=[{"role": "user", "content": "hi"}], + api_base="http://my-host:30000", + ) - assert mock_completion.call_args.kwargs["api_base"] == "http://my-host:30000" + assert completion_route.called + assert response.choices[0].message.content == "hello from ollama" -def test_ollama_embedding_explicit_api_base_overrides_global(monkeypatch): +def test_ollama_embedding_explicit_api_base_overrides_global(monkeypatch, respx_mock: respx.MockRouter): """Regression test for https://github.com/BerriAI/litellm/issues/26170 (embedding path).""" - monkeypatch.setattr(litellm, "api_base", "https://api.deepseek.com") + monkeypatch.setattr(litellm, "api_base", "https://unrelated-global.example.com") + respx_mock.post("http://my-host:30000/api/show").respond(json=_OLLAMA_SHOW_RESPONSE) + embed_route = respx_mock.post("http://my-host:30000/api/embed").respond( + json={"model": "qwen3-embedding:0.6b", "embeddings": [[0.1, 0.2, 0.3]], "prompt_eval_count": 2} + ) - with patch("litellm.main.ollama.ollama_embeddings") as mock_embeddings: - mock_embeddings.return_value = litellm.EmbeddingResponse() - litellm.embedding( - model="ollama/qwen3-embedding:0.6b", - input="hello", - api_base="http://my-host:30000", - ) + litellm.get_model_info.cache_clear() + response = litellm.embedding( + model="ollama/qwen3-embedding:0.6b", + input="hello", + api_base="http://my-host:30000", + ) - assert mock_embeddings.call_args.kwargs["api_base"] == "http://my-host:30000" + assert embed_route.called + assert response.data[0]["embedding"] == [0.1, 0.2, 0.3] def test_responses_api_bridge_check_azure_gpt_5_4_tools_plus_reasoning_routes_to_responses():