Allow serving cached local llama.cpp models

Co-authored-by: Kevin <120500656+oooindefatigable@users.noreply.github.com>
This commit is contained in:
ooovenenoso
2026-06-01 10:10:08 -04:00
committed by GitHub
parent 9b1acf6612
commit 5e47e69e99
3 changed files with 35 additions and 5 deletions

View File

@@ -16,6 +16,11 @@ logger = logging.getLogger(__name__)
# HuggingFace repo IDs are <org>/<name>, both alphanumerics plus ._-
# Rejecting anything else up front closes off shell-interpolation vectors.
_REPO_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$")
# Cached models scanned from a custom/local model dir are keyed by their leaf
# folder name (no slash), e.g. `DeepSeek-R1-UD-IQ4_XS`. The serve command uses
# the real on-disk path separately; this identifier is only for UI/task
# bookkeeping, so serving should accept the same safe glyph set as repo IDs.
_LOCAL_MODEL_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
# Include pattern is a glob: allow typical safe glyphs only.
_INCLUDE_RE = re.compile(r"^[A-Za-z0-9._\-*?/\[\]]+$")
# Remote host: user@host (optionally with :port-free hostname parts).
@@ -40,6 +45,14 @@ def _validate_repo_id(v: str | None) -> str:
return v
def _validate_serve_model_id(v: str | None) -> str:
if not v:
raise HTTPException(400, "repo_id is required")
if _REPO_ID_RE.match(v) or _LOCAL_MODEL_ID_RE.match(v):
return v
raise HTTPException(400, "Invalid repo_id — must be <org>/<name> or a cached local model id using [A-Za-z0-9._-]")
def _validate_include(v: str | None) -> str | None:
if v is None or v == "":
return None

View File

@@ -33,7 +33,7 @@ logger = logging.getLogger(__name__)
from routes.cookbook_helpers import (
_SSH_PORT_RE, _REMOTE_HOST_RE, _SESSION_ID_RE,
_validate_repo_id, _validate_include, _validate_remote_host, _validate_token,
_validate_repo_id, _validate_serve_model_id, _validate_include, _validate_remote_host, _validate_token,
_validate_local_dir, _validate_ssh_port, _validate_gpus, _shell_path,
_ps_squote, _bash_squote, _validate_serve_cmd, _parse_serve_phase,
_safe_env_prefix, _local_tooling_path_export, _append_serve_preflight_exit_lines,
@@ -776,9 +776,11 @@ def setup_cookbook_routes() -> APIRouter:
"""Launch a model server in a tmux session (or PowerShell background process on Windows).
`repo_id` is dual-purpose: a HuggingFace repo (`<org>/<name>`) for
model-serve commands, OR a bare pip package name when the cmd is a
`python -m pip install …`. We only enforce the strict HF format on
the model paths.
model-serve commands, a cached local-model id (the folder name reported
by `/api/model/cached`) for models scanned from a custom model dir, OR a
bare pip package name when the cmd is a `python -m pip install …`. We
keep strict validation, but serving local cached models must not require
a fake org/name wrapper.
"""
require_admin(request)
# Defence-in-depth: reject values that could break out of shell contexts.
@@ -807,7 +809,7 @@ def setup_cookbook_routes() -> APIRouter:
):
raise HTTPException(400, "Invalid pip package name")
else:
_validate_repo_id(req.repo_id)
_validate_serve_model_id(req.repo_id)
TMUX_LOG_DIR.mkdir(parents=True, exist_ok=True)
session_id = f"serve-{uuid.uuid4().hex[:8]}"
remote = req.remote_host

View File

@@ -12,6 +12,8 @@ from routes.cookbook_helpers import (
_local_tooling_path_export,
_safe_env_prefix,
_validate_gpus,
_validate_repo_id,
_validate_serve_model_id,
_validate_ssh_port,
)
@@ -52,6 +54,19 @@ def test_validate_gpus_accepts_indexes_only():
_validate_gpus("0; rm -rf /")
def test_validate_repo_id_stays_strict_for_hf_downloads():
assert _validate_repo_id("Qwen/Qwen3-8B") == "Qwen/Qwen3-8B"
with pytest.raises(HTTPException):
_validate_repo_id("DeepSeek-R1-UD-IQ4_XS")
def test_validate_serve_model_id_accepts_cached_local_model_names():
assert _validate_serve_model_id("Qwen/Qwen3-8B") == "Qwen/Qwen3-8B"
assert _validate_serve_model_id("DeepSeek-R1-UD-IQ4_XS") == "DeepSeek-R1-UD-IQ4_XS"
with pytest.raises(HTTPException):
_validate_serve_model_id("../escape")
def test_local_tooling_path_export_prepends_interpreter_bin():
"""The cookbook runners must see the venv's bin (where `hf`/`python` live)
so tmux shells can find them without an activated venv."""