fix(llm): OLLAMA_HOST fallback in detect_backend + default port (follow-up to #2019)

The PR resolved OLLAMA_HOST for the client base_url but not in detect_backend(),
so the headline #1940 case (OLLAMA_HOST set, no --backend) still errored with
"no LLM API key found". detect_backend() now uses _resolve_ollama_base_url (empty
default stays falsy, so ollama remains opt-in and never shadows a paid key). Also
default the port to 11434 when OLLAMA_HOST omits it (a bare host would otherwise
resolve to port 80), and handle bare-port / :port forms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
safishamsi
2026-07-20 11:41:43 +01:00
co-authored by Claude Opus 4.8
parent fab28ab7bc
commit 868f75de38
+36 -7
View File
@@ -58,18 +58,43 @@ _TOKENIZER = _get_tokenizer()
def _resolve_ollama_base_url(default: str) -> str:
"""Resolve the Ollama base URL. Honors an explicit OLLAMA_BASE_URL first
(verbatim), else falls back to Ollama's own OLLAMA_HOST (#1940), else the
default. OLLAMA_HOST may be a bare host, host:port, ``:port`` or bare port —
normalized the way the ollama client does: add ``http://`` when the scheme is
missing, default the port to 11434 when absent, and append the OpenAI-compat
``/v1`` suffix."""
ollama_base_url = os.environ.get("OLLAMA_BASE_URL")
if ollama_base_url is not None:
return ollama_base_url
ollama_host = os.environ.get("OLLAMA_HOST")
if ollama_host is None:
return default
if not ollama_host.startswith(("http://", "https://")):
ollama_host = f"http://{ollama_host}"
ollama_host = ollama_host.rstrip("/")
if not ollama_host.endswith("/v1"):
ollama_host = f"{ollama_host}/v1"
return ollama_host
host = ollama_host.strip()
if not host:
return default
# Bare port ("11434") or ":port" (":11434") -> localhost on that port.
if host.isdigit():
host = f"localhost:{host}"
elif host.startswith(":") and host[1:].isdigit():
host = f"localhost{host}"
if not host.startswith(("http://", "https://")):
host = f"http://{host}"
# Default the port to Ollama's 11434 when the host omits it (bare hostname
# would otherwise resolve to port 80 and silently fail to connect).
from urllib.parse import urlsplit, urlunsplit
try:
parts = urlsplit(host)
if parts.hostname and parts.port is None:
hostname = f"[{parts.hostname}]" if ":" in parts.hostname else parts.hostname
userinfo = parts.netloc.rsplit("@", 1)[0] + "@" if "@" in parts.netloc else ""
host = urlunsplit(parts._replace(netloc=f"{userinfo}{hostname}:11434"))
except (ValueError, TypeError):
pass
host = host.rstrip("/")
if not host.endswith("/v1"):
host = f"{host}/v1"
return host
BACKENDS: dict[str, dict] = {
@@ -2641,7 +2666,11 @@ def detect_backend() -> str | None:
return "azure"
if os.environ.get("AWS_PROFILE") or os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION"):
return "bedrock"
ollama_url = os.environ.get("OLLAMA_BASE_URL")
# Honor Ollama's own OLLAMA_HOST here too, not just OLLAMA_BASE_URL (#1940) —
# otherwise a user who set the standard Ollama var but no --backend still
# gets "no LLM API key found". Empty default -> falsy when neither is set,
# so ollama stays opt-in and never shadows a paid key (checked first above).
ollama_url = _resolve_ollama_base_url("")
if ollama_url:
_validate_ollama_base_url(ollama_url)
return "ollama"