fix: validate WebUI launcher can import agent

This commit is contained in:
milo
2026-04-30 16:06:16 +08:00
committed by Hermes Bot
parent b8a346f421
commit 634f90a807
3 changed files with 89 additions and 5 deletions

View File

@@ -124,15 +124,50 @@ def discover_launcher_python(agent_dir: Path | None) -> str:
return shutil.which("python3") or shutil.which("python") or sys.executable
def ensure_python_has_webui_deps(python_exe: str) -> str:
def _python_can_run_webui_and_agent(python_exe: str, agent_dir: Path | None = None) -> bool:
script = "import yaml\nfrom run_agent import AIAgent\n"
env = os.environ.copy()
if agent_dir:
env["PYTHONPATH"] = (
str(agent_dir)
if not env.get("PYTHONPATH")
else f"{agent_dir}{os.pathsep}{env['PYTHONPATH']}"
)
check = subprocess.run(
[python_exe, "-c", "import yaml"],
[python_exe, "-c", script],
capture_output=True,
text=True,
env=env,
)
if check.returncode == 0:
return check.returncode == 0
def ensure_python_has_webui_deps(python_exe: str, agent_dir: Path | None = None) -> str:
"""Return a Python executable that can run both WebUI and Hermes Agent.
The WebUI can be launched directly with its local .venv. That venv has the
WebUI dependencies (for example PyYAML), but may not have Hermes Agent on its
import path. In that case the server starts healthy, then chat fails later
with "AIAgent not available". Prefer the agent venv when it is usable, and
validate the final interpreter before starting the server.
"""
if _python_can_run_webui_and_agent(python_exe, agent_dir):
return python_exe
agent_candidates: list[Path] = []
if agent_dir:
for rel in (
"venv/bin/python",
"venv/Scripts/python.exe",
".venv/bin/python",
".venv/Scripts/python.exe",
):
agent_candidates.append(agent_dir / rel)
for candidate in agent_candidates:
if str(candidate) != python_exe and candidate.exists():
if _python_can_run_webui_and_agent(str(candidate), agent_dir):
return str(candidate)
venv_dir = REPO_ROOT / ".venv"
venv_python = venv_dir / (
"Scripts/python.exe" if platform.system() == "Windows" else "bin/python"
@@ -158,7 +193,13 @@ def ensure_python_has_webui_deps(python_exe: str) -> str:
],
check=True,
)
return str(venv_python)
if _python_can_run_webui_and_agent(str(venv_python), agent_dir):
return str(venv_python)
raise RuntimeError(
"Python environment cannot import both WebUI dependencies and Hermes Agent. "
"Set HERMES_WEBUI_PYTHON to the Hermes Agent venv Python or install the "
"WebUI requirements into that environment."
)
def hermes_command_exists() -> bool:
@@ -298,7 +339,7 @@ def main() -> int:
install_hermes_agent()
agent_dir = discover_agent_dir()
python_exe = ensure_python_has_webui_deps(discover_launcher_python(agent_dir))
python_exe = ensure_python_has_webui_deps(discover_launcher_python(agent_dir), agent_dir)
state_dir = Path(
os.getenv("HERMES_WEBUI_STATE_DIR", str(Path.home() / ".hermes" / "webui"))
).expanduser()

View File

@@ -305,6 +305,7 @@ def test_server():
# causing onboarding writes (config.yaml, .env) to land in the production
# ~/.hermes/profiles/webui/ and overwrite real API keys.
"HERMES_BASE_HOME": str(TEST_STATE_DIR),
"HERMES_WEBUI_PASSWORD": "",
})
# Pass agent dir if discovered so server.py doesn't have to re-discover

View File

@@ -0,0 +1,42 @@
import pathlib
import bootstrap
def test_ensure_python_prefers_agent_venv_when_launcher_cannot_import_agent(monkeypatch, tmp_path):
"""Avoid starting WebUI with a local venv that later cannot import AIAgent."""
local_python = tmp_path / "webui" / ".venv" / "bin" / "python"
agent_python = tmp_path / "agent" / "venv" / "bin" / "python"
agent_python.parent.mkdir(parents=True)
agent_python.write_text("", encoding="utf-8")
probes = []
def fake_can_run(python_exe: str, agent_dir: pathlib.Path | None = None) -> bool:
probes.append(pathlib.Path(python_exe))
return pathlib.Path(python_exe) == agent_python
monkeypatch.setattr(bootstrap, "_python_can_run_webui_and_agent", fake_can_run)
selected = bootstrap.ensure_python_has_webui_deps(str(local_python), tmp_path / "agent")
assert selected == str(agent_python)
assert probes == [local_python, agent_python]
def test_ensure_python_fails_loudly_when_no_interpreter_can_import_agent(monkeypatch, tmp_path):
"""Do not report health OK when chat would fail with missing AIAgent."""
local_python = tmp_path / "webui" / ".venv" / "bin" / "python"
agent_python = tmp_path / "agent" / "venv" / "bin" / "python"
agent_python.parent.mkdir(parents=True)
agent_python.write_text("", encoding="utf-8")
monkeypatch.setattr(bootstrap, "_python_can_run_webui_and_agent", lambda *a, **k: False)
monkeypatch.setattr(bootstrap.subprocess, "run", lambda *a, **k: None)
try:
bootstrap.ensure_python_has_webui_deps(str(local_python), tmp_path / "agent")
except RuntimeError as exc:
assert "cannot import both WebUI dependencies and Hermes Agent" in str(exc)
else:
raise AssertionError("expected RuntimeError")