Studio: expand Connections model picker for local inference server (#5643)
* feat: add custom model v1/model loading * fix: require base URL for local model catalog loading --------- Co-authored-by: Roland Tannous <115670425+rolandtannous@users.noreply.github.com>
This commit is contained in:
parent
db3393fbc8
commit
abeabc71bb
4 changed files with 247 additions and 53 deletions
|
|
@ -433,6 +433,12 @@ class ExternalProviderClient:
|
|||
if response.status_code != 200:
|
||||
error_body = await response.aread()
|
||||
error_text = error_body.decode("utf-8", errors = "replace")
|
||||
error_text = _friendly_provider_error_text(
|
||||
self.provider_type,
|
||||
response.status_code,
|
||||
error_text,
|
||||
model = model,
|
||||
)
|
||||
logger.error(
|
||||
"External provider returned %d: %s",
|
||||
response.status_code,
|
||||
|
|
@ -2933,12 +2939,31 @@ class ExternalProviderClient:
|
|||
response.raise_for_status()
|
||||
data = response.json()
|
||||
# OpenAI format: {"data": [{"id": "...", ...}, ...]}
|
||||
models = data.get("data", [])
|
||||
# Some local servers (Ollama with no models) return data: null.
|
||||
models = data.get("data") or []
|
||||
if not models and self.provider_type == "ollama":
|
||||
models = await self._list_ollama_native_models()
|
||||
return models
|
||||
except httpx.HTTPError as exc:
|
||||
logger.error("Failed to list models from %s: %s", self.provider_type, exc)
|
||||
raise
|
||||
|
||||
async def _list_ollama_native_models(self) -> list[dict[str, Any]]:
|
||||
"""Fallback when Ollama's /v1/models returns an empty or null catalog."""
|
||||
root = self.base_url.removesuffix("/v1").rstrip("/")
|
||||
response = await _http_client.get(
|
||||
f"{root}/api/tags",
|
||||
headers = self._auth_headers(),
|
||||
timeout = self._timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
return [
|
||||
{"id": entry.get("name", "").strip(), "owned_by": "ollama"}
|
||||
for entry in (payload.get("models") or [])
|
||||
if isinstance(entry, dict) and entry.get("name", "").strip()
|
||||
]
|
||||
|
||||
async def verify_models_endpoint_lightweight(self) -> None:
|
||||
"""
|
||||
Confirm GET /models returns 200 without buffering the full response body.
|
||||
|
|
@ -3087,6 +3112,40 @@ class ExternalProviderClient:
|
|||
"""No-op — the underlying client is shared across requests."""
|
||||
|
||||
|
||||
def _provider_display_name(provider_type: str) -> str:
|
||||
from core.inference.providers import get_provider_info
|
||||
|
||||
info = get_provider_info(provider_type) or {}
|
||||
return str(info.get("display_name") or provider_type)
|
||||
|
||||
|
||||
def _friendly_provider_error_text(
|
||||
provider_type: str,
|
||||
status_code: int,
|
||||
raw_message: str,
|
||||
*,
|
||||
model: str | None = None,
|
||||
) -> str:
|
||||
"""Rewrite common provider errors into actionable Studio copy."""
|
||||
if status_code == 404 and model:
|
||||
lowered = raw_message.lower()
|
||||
if "not found" in lowered or "not_found" in lowered:
|
||||
if provider_type == "ollama":
|
||||
label = _provider_display_name(provider_type)
|
||||
return (
|
||||
f"Model '{model}' is not installed in {label}. "
|
||||
f"Run `ollama pull {model}` in a terminal, then retry."
|
||||
)
|
||||
if provider_type in ("vllm", "llama_cpp"):
|
||||
label = _provider_display_name(provider_type)
|
||||
return (
|
||||
f"Model '{model}' is not available on the {label} server. "
|
||||
"Check that the server is running and the model is loaded, "
|
||||
"then retry."
|
||||
)
|
||||
return raw_message
|
||||
|
||||
|
||||
def _error_sse_line(status_code: int, message: str, provider_type: str) -> str:
|
||||
"""Format an error as an SSE data line in OpenAI error format."""
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -240,6 +240,36 @@ PROVIDER_REGISTRY: dict[str, dict[str, Any]] = {
|
|||
# /api/providers/registry dropdown — see list_available_providers.
|
||||
"hidden": True,
|
||||
},
|
||||
"ollama": {
|
||||
"display_name": "Ollama",
|
||||
"base_url": "http://localhost:11434/v1",
|
||||
"default_models": [],
|
||||
"supports_streaming": True,
|
||||
"supports_vision": True,
|
||||
"supports_tool_calling": True,
|
||||
"auth_header": "Authorization",
|
||||
"auth_prefix": "Bearer ",
|
||||
"notes": (
|
||||
"Local Ollama server. OpenAI-compatible /v1/chat/completions; "
|
||||
"no API key. Surfaced via CUSTOM_PROVIDER_PRESETS in the frontend."
|
||||
),
|
||||
"hidden": True,
|
||||
},
|
||||
"llama_cpp": {
|
||||
"display_name": "llama.cpp",
|
||||
"base_url": "http://localhost:8080/v1",
|
||||
"default_models": [],
|
||||
"supports_streaming": True,
|
||||
"supports_vision": True,
|
||||
"supports_tool_calling": True,
|
||||
"auth_header": "Authorization",
|
||||
"auth_prefix": "Bearer ",
|
||||
"notes": (
|
||||
"Local llama.cpp server (llama-server). OpenAI-compatible "
|
||||
"/v1/chat/completions. Surfaced via CUSTOM_PROVIDER_PRESETS."
|
||||
),
|
||||
"hidden": True,
|
||||
},
|
||||
"openrouter": {
|
||||
"display_name": "OpenRouter",
|
||||
"base_url": "https://openrouter.ai/api/v1",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue