mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-20 10:03:37 +00:00
fix(llms): normalize scheme and port in Ollama base URL (#7206)
* fix(llms): normalize scheme and port in Ollama base URL
OLLAMA_HOST follows Ollama's own convention and may be a bare host
("0.0.0.0") or a host:port pair ("127.0.0.1:11434") rather than a full
URL. _normalize_ollama_base_url only appended "/v1", so those values
produced invalid base URLs such as "0.0.0.0/v1", and every request
failed with the misleading error "Failed to connect to OpenAI API:
Connection error." - confusing, since no OpenAI model was requested.
Fill in the missing parts the way Ollama's own client does: prepend
http:// when no scheme is present, append the default port 11434 when
none is present and the scheme is http (https implies 443), then append
the /v1 suffix the OpenAI-compatible endpoint requires.
Six of nine realistic OLLAMA_HOST forms were affected, including
127.0.0.1:11434, which is Ollama's documented default.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(llms): strip only the parsed path when normalizing Ollama base URL
Stripping trailing slashes from the whole URL before parsing corrupted
inputs that carry a query or fragment. "http://ollama/?tenant=acme" kept
a "/" path and produced a doubled "//v1", and a query or fragment ending
in "/" silently lost that character.
Parse first, then rstrip only parts.path. Adds regression tests for a
root path alongside a query and for a query value ending in "/".
Reported by CodeRabbit on #7206.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
3d72c707d5
commit
c90337ba5a
@@ -15,6 +15,7 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass, field
|
||||
import os
|
||||
from typing import Any
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
|
||||
from pydantic import model_validator
|
||||
|
||||
@@ -91,23 +92,39 @@ OPENAI_COMPATIBLE_PROVIDERS: dict[str, ProviderConfig] = {
|
||||
),
|
||||
}
|
||||
|
||||
_OLLAMA_DEFAULT_PORT = 11434
|
||||
|
||||
|
||||
def _normalize_ollama_base_url(base_url: str) -> str:
|
||||
"""Normalize Ollama base URL to ensure it ends with /v1.
|
||||
"""Normalize an Ollama base URL into a full OpenAI-compatible endpoint.
|
||||
|
||||
Ollama uses OLLAMA_HOST which may not include the /v1 suffix,
|
||||
but the OpenAI-compatible endpoint requires it.
|
||||
``OLLAMA_HOST`` follows Ollama's own convention and may be a bare host
|
||||
(``0.0.0.0``), a ``host:port`` pair (``127.0.0.1:11434``), or a full URL.
|
||||
Whichever parts are missing are filled in: ``http://`` when no scheme is
|
||||
given, the default Ollama port when none is given and the scheme is
|
||||
``http`` (``https`` implies 443), and the ``/v1`` suffix that the
|
||||
OpenAI-compatible endpoint requires.
|
||||
|
||||
Args:
|
||||
base_url: The base URL, potentially without /v1 suffix.
|
||||
base_url: The base URL, potentially missing scheme, port or /v1.
|
||||
|
||||
Returns:
|
||||
The base URL with /v1 suffix if needed.
|
||||
A fully-qualified base URL ending in /v1.
|
||||
"""
|
||||
base_url = base_url.rstrip("/")
|
||||
if not base_url.endswith("/v1"):
|
||||
return f"{base_url}/v1"
|
||||
return base_url
|
||||
if "://" not in base_url:
|
||||
base_url = f"http://{base_url}"
|
||||
|
||||
parts = urlsplit(base_url)
|
||||
|
||||
netloc = parts.netloc
|
||||
if parts.scheme == "http" and parts.port is None:
|
||||
netloc = f"{netloc}:{_OLLAMA_DEFAULT_PORT}"
|
||||
|
||||
path = parts.path.rstrip("/")
|
||||
if not path.endswith("/v1"):
|
||||
path = f"{path}/v1"
|
||||
|
||||
return urlunsplit((parts.scheme, netloc, path, parts.query, parts.fragment))
|
||||
|
||||
|
||||
class OpenAICompatibleCompletion(OpenAICompletion):
|
||||
|
||||
@@ -114,7 +114,34 @@ class TestNormalizeOllamaBaseUrl:
|
||||
def test_handles_v1_with_trailing_slash(self):
|
||||
"""Test /v1/ is normalized."""
|
||||
assert _normalize_ollama_base_url("http://localhost:11434/v1/") == "http://localhost:11434/v1"
|
||||
|
||||
def test_bare_host_gets_scheme_and_port(self):
|
||||
"""Bare host from OLLAMA_HOST gets http:// and the default port."""
|
||||
assert _normalize_ollama_base_url("0.0.0.0") == "http://0.0.0.0:11434/v1"
|
||||
|
||||
def test_bare_localhost_gets_scheme_and_port(self):
|
||||
"""Bare localhost gets http:// and the default port."""
|
||||
assert _normalize_ollama_base_url("localhost") == "http://localhost:11434/v1"
|
||||
|
||||
def test_host_port_without_scheme_gets_scheme(self):
|
||||
"""host:port without a scheme gets http:// prepended."""
|
||||
assert _normalize_ollama_base_url("127.0.0.1:11434") == "http://127.0.0.1:11434/v1"
|
||||
|
||||
def test_lan_host_port_without_scheme(self):
|
||||
"""A LAN host:port without a scheme gets http:// prepended."""
|
||||
assert _normalize_ollama_base_url("192.168.1.5:11434") == "http://192.168.1.5:11434/v1"
|
||||
|
||||
def test_https_url_keeps_scheme_and_gets_no_default_port(self):
|
||||
"""An explicit https:// URL keeps its scheme and gets no default port."""
|
||||
assert _normalize_ollama_base_url("https://ollama.example.com") == "https://ollama.example.com/v1"
|
||||
|
||||
def test_root_path_with_query_does_not_double_slash(self):
|
||||
"""A root path alongside a query yields /v1, not //v1."""
|
||||
assert _normalize_ollama_base_url("http://ollama/?tenant=acme") == "http://ollama:11434/v1?tenant=acme"
|
||||
|
||||
def test_trailing_slash_in_query_is_preserved(self):
|
||||
"""Only the path is stripped, so a query ending in / keeps that character."""
|
||||
assert _normalize_ollama_base_url("http://ollama:11434/?x=a/") == "http://ollama:11434/v1?x=a/"
|
||||
|
||||
class TestOpenAICompatibleCompletion:
|
||||
"""Tests for OpenAICompatibleCompletion class."""
|
||||
|
||||
Reference in New Issue
Block a user