fix: distinguish external cookbook runtimes (#1188)
This commit is contained in:
@@ -118,6 +118,7 @@ def _running_in_container(dockerenv_path="/.dockerenv", cgroup_path="/proc/1/cgr
|
|||||||
|
|
||||||
|
|
||||||
DockerRowStatus = namedtuple("DockerRowStatus", ["applicable", "install_hint"])
|
DockerRowStatus = namedtuple("DockerRowStatus", ["applicable", "install_hint"])
|
||||||
|
PackageUpdateStatus = namedtuple("PackageUpdateStatus", ["available", "note"])
|
||||||
|
|
||||||
|
|
||||||
def _docker_row_status(*, on_remote, in_container, installed, default_hint):
|
def _docker_row_status(*, on_remote, in_container, installed, default_hint):
|
||||||
@@ -180,7 +181,10 @@ def _package_status_note(name: str, probe: dict) -> str:
|
|||||||
locations = module.get("locations") or []
|
locations = module.get("locations") or []
|
||||||
if name == "vllm":
|
if name == "vllm":
|
||||||
if binaries.get("vllm"):
|
if binaries.get("vllm"):
|
||||||
return f"vLLM CLI: {binaries['vllm']}"
|
parts = [f"vLLM CLI: {binaries['vllm']}"]
|
||||||
|
if dists.get("vllm"):
|
||||||
|
parts.append(f"python package: vllm {dists['vllm']}")
|
||||||
|
return "; ".join(parts)
|
||||||
if module.get("found") and not dists.get("vllm"):
|
if module.get("found") and not dists.get("vllm"):
|
||||||
loc = locations[0] if locations else module.get("origin") or "unknown path"
|
loc = locations[0] if locations else module.get("origin") or "unknown path"
|
||||||
return f"Python sees a vllm namespace at {loc}, but no vLLM CLI is on PATH."
|
return f"Python sees a vllm namespace at {loc}, but no vLLM CLI is on PATH."
|
||||||
@@ -201,6 +205,35 @@ def _package_status_note(name: str, probe: dict) -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _package_pip_update_status(pkg: dict, probe: dict | None = None) -> PackageUpdateStatus:
|
||||||
|
"""Return whether the Dependencies UI should offer a generic pip update.
|
||||||
|
|
||||||
|
"Installed" means Cookbook can use the dependency. It does not always mean
|
||||||
|
the dependency is a Python package that Cookbook should update with pip:
|
||||||
|
native llama-server can come from a package manager/source build, and a CLI
|
||||||
|
may be on PATH without matching Python package metadata.
|
||||||
|
"""
|
||||||
|
if pkg.get("kind") == "system" or not pkg.get("pip"):
|
||||||
|
return PackageUpdateStatus(False, "Update this system dependency outside Odysseus.")
|
||||||
|
|
||||||
|
name = pkg.get("name")
|
||||||
|
binaries = probe.get("binaries") if isinstance(probe, dict) and isinstance(probe.get("binaries"), dict) else {}
|
||||||
|
dists = probe.get("dists") if isinstance(probe, dict) and isinstance(probe.get("dists"), dict) else {}
|
||||||
|
|
||||||
|
if name == "llama_cpp" and binaries.get("llama-server"):
|
||||||
|
return PackageUpdateStatus(
|
||||||
|
False,
|
||||||
|
"Using native llama-server on PATH; update it with its package manager or source checkout.",
|
||||||
|
)
|
||||||
|
if name == "vllm" and binaries.get("vllm") and not dists.get("vllm"):
|
||||||
|
return PackageUpdateStatus(
|
||||||
|
False,
|
||||||
|
"Using a vLLM CLI on PATH without Python package metadata; update it outside Odysseus.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return PackageUpdateStatus(True, "Update uses pip in the selected Python environment.")
|
||||||
|
|
||||||
|
|
||||||
def _prepend_user_install_bins_to_path() -> None:
|
def _prepend_user_install_bins_to_path() -> None:
|
||||||
"""Make pip --user console scripts visible to dependency probes.
|
"""Make pip --user console scripts visible to dependency probes.
|
||||||
|
|
||||||
@@ -944,6 +977,7 @@ def setup_shell_routes() -> APIRouter:
|
|||||||
|
|
||||||
for pkg in packages:
|
for pkg in packages:
|
||||||
on_remote = bool(host and pkg.get("target") == "remote")
|
on_remote = bool(host and pkg.get("target") == "remote")
|
||||||
|
probe = None
|
||||||
if on_remote:
|
if on_remote:
|
||||||
pkg["installed"] = bool(remote_status.get(pkg["name"], False))
|
pkg["installed"] = bool(remote_status.get(pkg["name"], False))
|
||||||
probe = remote_details.get(pkg["name"])
|
probe = remote_details.get(pkg["name"])
|
||||||
@@ -957,19 +991,36 @@ def setup_shell_routes() -> APIRouter:
|
|||||||
elif pkg["name"] == "llama_cpp" and shutil.which("llama-server"):
|
elif pkg["name"] == "llama_cpp" and shutil.which("llama-server"):
|
||||||
pkg["installed"] = True
|
pkg["installed"] = True
|
||||||
pkg["status_note"] = f"native llama-server: {shutil.which('llama-server')}"
|
pkg["status_note"] = f"native llama-server: {shutil.which('llama-server')}"
|
||||||
|
probe = {"binaries": {"llama-server": shutil.which("llama-server")}, "dists": {}}
|
||||||
|
elif pkg["name"] == "vllm":
|
||||||
|
_vllm_cli = shutil.which("vllm")
|
||||||
|
pkg["installed"] = _vllm_cli is not None
|
||||||
|
if pkg["installed"]:
|
||||||
|
try:
|
||||||
|
_vllm_version = importlib_metadata.version(_pip_dist_name(pkg))
|
||||||
|
except importlib_metadata.PackageNotFoundError:
|
||||||
|
_vllm_version = None
|
||||||
|
probe = {
|
||||||
|
"binaries": {"vllm": _vllm_cli},
|
||||||
|
"dists": {"vllm": _vllm_version} if _vllm_version else {},
|
||||||
|
}
|
||||||
|
pkg["status_note"] = _package_status_note("vllm", probe)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
importlib.import_module(pkg["name"])
|
importlib.import_module(pkg["name"])
|
||||||
if pkg["name"] == "vllm":
|
importlib_metadata.version(_pip_dist_name(pkg))
|
||||||
pkg["installed"] = shutil.which("vllm") is not None
|
pkg["installed"] = True
|
||||||
else:
|
|
||||||
importlib_metadata.version(_pip_dist_name(pkg))
|
|
||||||
pkg["installed"] = True
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pkg["installed"] = False
|
pkg["installed"] = False
|
||||||
except importlib_metadata.PackageNotFoundError:
|
except importlib_metadata.PackageNotFoundError:
|
||||||
pkg["installed"] = False
|
pkg["installed"] = False
|
||||||
|
|
||||||
|
if pkg.get("installed"):
|
||||||
|
update_status = _package_pip_update_status(pkg, probe)
|
||||||
|
pkg["pip_update_available"] = update_status.available
|
||||||
|
if update_status.note:
|
||||||
|
pkg["update_note"] = update_status.note
|
||||||
|
|
||||||
if pkg["name"] == "docker":
|
if pkg["name"] == "docker":
|
||||||
status = _docker_row_status(
|
status = _docker_row_status(
|
||||||
on_remote=on_remote,
|
on_remote=on_remote,
|
||||||
|
|||||||
@@ -618,6 +618,10 @@ async function _fetchDependencies() {
|
|||||||
const _statusTag = (pkg, isLocal, isSystemDep, winBlocked) => {
|
const _statusTag = (pkg, isLocal, isSystemDep, winBlocked) => {
|
||||||
if (winBlocked) return `<span class="cookbook-dep-tag cookbook-dep-na">N/A</span>`;
|
if (winBlocked) return `<span class="cookbook-dep-tag cookbook-dep-na">N/A</span>`;
|
||||||
if (pkg.installed && isSystemDep) return `<span class="cookbook-dep-tag cookbook-dep-installed" title="Found on selected server">Installed</span>`;
|
if (pkg.installed && isSystemDep) return `<span class="cookbook-dep-tag cookbook-dep-installed" title="Found on selected server">Installed</span>`;
|
||||||
|
if (pkg.installed && pkg.pip_update_available === false) {
|
||||||
|
const tip = esc(pkg.update_note || pkg.status_note || 'Found externally; update outside Odysseus.');
|
||||||
|
return `<span class="cookbook-dep-tag cookbook-dep-installed" title="${tip}">Installed</span>`;
|
||||||
|
}
|
||||||
if (pkg.installed) return `<button class="cookbook-dep-tag cookbook-dep-installed cookbook-dep-installed-btn" title="Installed — click for actions"><span class="cookbook-dep-installed-label">Installed</span><span class="cookbook-dep-caret">▾</span></button>`;
|
if (pkg.installed) return `<button class="cookbook-dep-tag cookbook-dep-installed cookbook-dep-installed-btn" title="Installed — click for actions"><span class="cookbook-dep-installed-label">Installed</span><span class="cookbook-dep-caret">▾</span></button>`;
|
||||||
if (isSystemDep) {
|
if (isSystemDep) {
|
||||||
const depTip = esc(pkg.install_hint || 'Install this OS package on the selected server.');
|
const depTip = esc(pkg.install_hint || 'Install this OS package on the selected server.');
|
||||||
@@ -632,11 +636,13 @@ async function _fetchDependencies() {
|
|||||||
const isSystemDep = pkg.kind === 'system';
|
const isSystemDep = pkg.kind === 'system';
|
||||||
const winBlocked = !isLocal && _isWindows() && _winUnsupported.has(pkg.name);
|
const winBlocked = !isLocal && _isWindows() && _winUnsupported.has(pkg.name);
|
||||||
const note = pkg.status_note ? `<div class="memory-item-meta" style="font-size:10px;opacity:0.65;margin-top:3px;">${esc(pkg.status_note)}</div>` : '';
|
const note = pkg.status_note ? `<div class="memory-item-meta" style="font-size:10px;opacity:0.65;margin-top:3px;">${esc(pkg.status_note)}</div>` : '';
|
||||||
|
const updateNote = pkg.installed && pkg.pip_update_available === false && pkg.update_note ? `<div class="memory-item-meta" style="font-size:10px;opacity:0.55;margin-top:3px;">${esc(pkg.update_note)}</div>` : '';
|
||||||
return `<div class="cookbook-dep-row${winBlocked ? ' cookbook-dep-blocked' : ''}" data-pkg-name="${esc(pkg.name)}" data-dep-pip="${esc(pkg.pip || '')}" data-dep-target="${isLocal ? 'local' : 'remote'}" data-dep-kind="${esc(pkg.kind || 'python')}">`
|
return `<div class="cookbook-dep-row${winBlocked ? ' cookbook-dep-blocked' : ''}" data-pkg-name="${esc(pkg.name)}" data-dep-pip="${esc(pkg.pip || '')}" data-dep-target="${isLocal ? 'local' : 'remote'}" data-dep-kind="${esc(pkg.kind || 'python')}">`
|
||||||
+ `<div class="cookbook-dep-info">`
|
+ `<div class="cookbook-dep-info">`
|
||||||
+ `<div class="memory-item-title">${esc(pkg.name)}</div>`
|
+ `<div class="memory-item-title">${esc(pkg.name)}</div>`
|
||||||
+ `<div class="memory-item-meta" style="font-size:10px;opacity:0.5;margin-top:2px;">${esc(pkg.desc)}</div>`
|
+ `<div class="memory-item-meta" style="font-size:10px;opacity:0.5;margin-top:2px;">${esc(pkg.desc)}</div>`
|
||||||
+ note
|
+ note
|
||||||
|
+ updateNote
|
||||||
+ `</div>`
|
+ `</div>`
|
||||||
+ `<span class="cookbook-dep-tag cookbook-dep-cat">${esc(pkg.category)}</span>`
|
+ `<span class="cookbook-dep-tag cookbook-dep-cat">${esc(pkg.category)}</span>`
|
||||||
+ _statusTag(pkg, isLocal, isSystemDep, winBlocked)
|
+ _statusTag(pkg, isLocal, isSystemDep, winBlocked)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from routes.shell_routes import (
|
|||||||
_running_in_container,
|
_running_in_container,
|
||||||
_docker_row_status,
|
_docker_row_status,
|
||||||
_package_installed_from_probe,
|
_package_installed_from_probe,
|
||||||
|
_package_pip_update_status,
|
||||||
_package_probe_script,
|
_package_probe_script,
|
||||||
_package_status_note,
|
_package_status_note,
|
||||||
_prepend_user_install_bins_to_path,
|
_prepend_user_install_bins_to_path,
|
||||||
@@ -224,6 +225,21 @@ class TestPackageProbeStatus:
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert _package_installed_from_probe("vllm", probe) is True
|
assert _package_installed_from_probe("vllm", probe) is True
|
||||||
|
assert "python package: vllm 0.8.5" in _package_status_note("vllm", probe)
|
||||||
|
assert _package_pip_update_status({"name": "vllm", "pip": "vllm"}, probe).available is True
|
||||||
|
|
||||||
|
def test_vllm_cli_without_dist_is_external_for_update(self):
|
||||||
|
probe = {
|
||||||
|
"modules": {"vllm": {"found": False, "real_module": False}},
|
||||||
|
"dists": {},
|
||||||
|
"binaries": {"vllm": "/opt/vllm/bin/vllm"},
|
||||||
|
}
|
||||||
|
|
||||||
|
status = _package_pip_update_status({"name": "vllm", "pip": "vllm"}, probe)
|
||||||
|
|
||||||
|
assert _package_installed_from_probe("vllm", probe) is True
|
||||||
|
assert status.available is False
|
||||||
|
assert "outside Odysseus" in status.note
|
||||||
|
|
||||||
def test_llama_cpp_is_installed_when_native_llama_server_exists(self):
|
def test_llama_cpp_is_installed_when_native_llama_server_exists(self):
|
||||||
probe = {
|
probe = {
|
||||||
@@ -234,6 +250,9 @@ class TestPackageProbeStatus:
|
|||||||
|
|
||||||
assert _package_installed_from_probe("llama_cpp", probe) is True
|
assert _package_installed_from_probe("llama_cpp", probe) is True
|
||||||
assert "native llama-server" in _package_status_note("llama_cpp", probe)
|
assert "native llama-server" in _package_status_note("llama_cpp", probe)
|
||||||
|
status = _package_pip_update_status({"name": "llama_cpp", "pip": "llama-cpp-python[server]"}, probe)
|
||||||
|
assert status.available is False
|
||||||
|
assert "package manager or source checkout" in status.note
|
||||||
|
|
||||||
def test_diffusers_requires_torch_too(self):
|
def test_diffusers_requires_torch_too(self):
|
||||||
missing_torch = {
|
missing_torch = {
|
||||||
|
|||||||
Reference in New Issue
Block a user