@@ -0,0 +1,146 @@
|
||||
"""Фоновый запуск llama-server при старте WESP (если WESP_LLM_AUTOSTART и LLM включён)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_proc: Optional[subprocess.Popen] = None
|
||||
|
||||
|
||||
def _should_run_in_this_process(app: Any) -> bool:
|
||||
"""Не дублировать в родителе Flask reloader (как sync_client)."""
|
||||
if bool(app.config.get("DEBUG", False)):
|
||||
return os.environ.get("WERKZEUG_RUN_MAIN") == "true"
|
||||
return True
|
||||
|
||||
|
||||
def _parse_base_url(url: str) -> tuple[str, int]:
|
||||
u = urlparse((url or "").strip() or "http://127.0.0.1:8080")
|
||||
host = u.hostname or "127.0.0.1"
|
||||
port = int(u.port or 8080)
|
||||
return host, port
|
||||
|
||||
|
||||
def _resolve_llama_binary(base_dir: Path) -> Optional[Path]:
|
||||
env = os.getenv("LLAMA_SERVER", "").strip()
|
||||
if env:
|
||||
p = Path(env)
|
||||
if p.is_file():
|
||||
return p
|
||||
if sys.platform == "win32":
|
||||
for rel in ("tools/llama-server.exe", "tools/llama-server/llama-server.exe"):
|
||||
cand = base_dir / rel.replace("/", os.sep)
|
||||
if cand.is_file():
|
||||
return cand
|
||||
else:
|
||||
cand = base_dir / "tools" / "llama-server"
|
||||
if cand.is_file():
|
||||
return cand
|
||||
which = shutil.which("llama-server")
|
||||
if which:
|
||||
return Path(which)
|
||||
return None
|
||||
|
||||
|
||||
def _gguf_path(app: Any) -> Path:
|
||||
cfg = app.config
|
||||
root = Path(str(cfg.get("WESP_ASSISTANT_DIR") or ""))
|
||||
name = os.getenv("WESP_LLM_GGUF_FILE", "").strip() or str(
|
||||
cfg.get("WESP_LLM_MODEL") or ""
|
||||
).strip()
|
||||
if not name:
|
||||
name = "qwen2.5-1.5b-instruct-q5_k_m.gguf"
|
||||
return root / "models" / name
|
||||
|
||||
|
||||
def schedule_llm_server_autostart(app: Any) -> None:
|
||||
"""Запускает llama-server дочерним процессом; idempotent."""
|
||||
global _proc
|
||||
if _proc is not None:
|
||||
return
|
||||
if app.config.get("TESTING"):
|
||||
return
|
||||
if not app.config.get("WESP_LLM_AUTOSTART"):
|
||||
return
|
||||
if not app.config.get("WESP_ADMIN_LLM_ENABLED"):
|
||||
logger.warning(
|
||||
"WESP_LLM_AUTOSTART пропущен: LLM в админке выключен (галка или admin_llm_enabled в wesp_security_settings.json, либо WESP_ADMIN_LLM_ENABLED=0)."
|
||||
)
|
||||
return
|
||||
if not _should_run_in_this_process(app):
|
||||
return
|
||||
|
||||
base_dir = Path(str(app.config.get("BASE_DIR") or ".")).resolve()
|
||||
gguf = _gguf_path(app)
|
||||
if not gguf.is_file():
|
||||
logger.warning(
|
||||
"WESP_LLM_AUTOSTART пропущен: нет файла модели %s",
|
||||
gguf,
|
||||
)
|
||||
return
|
||||
|
||||
bin_path = _resolve_llama_binary(base_dir)
|
||||
if not bin_path:
|
||||
logger.warning(
|
||||
"WESP_LLM_AUTOSTART пропущен: не найден llama-server "
|
||||
"(переменная LLAMA_SERVER или %s/tools/).",
|
||||
base_dir,
|
||||
)
|
||||
return
|
||||
|
||||
host, port = _parse_base_url(str(app.config.get("WESP_LLM_BASE_URL") or ""))
|
||||
args = [
|
||||
str(bin_path),
|
||||
"-m",
|
||||
str(gguf),
|
||||
"--host",
|
||||
host,
|
||||
"--port",
|
||||
str(port),
|
||||
"-c",
|
||||
"8192",
|
||||
]
|
||||
cwd = str(bin_path.parent)
|
||||
popen_kw: dict = {"cwd": cwd}
|
||||
if sys.platform == "win32":
|
||||
popen_kw["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
|
||||
try:
|
||||
_proc = subprocess.Popen(args, **popen_kw)
|
||||
except OSError as exc:
|
||||
logger.warning("WESP_LLM_AUTOSTART: не удалось запустить llama-server: %s", exc)
|
||||
return
|
||||
|
||||
logger.info(
|
||||
"WESP_LLM_AUTOSTART: llama-server PID=%s, %s:%s, модель %s",
|
||||
_proc.pid,
|
||||
host,
|
||||
port,
|
||||
gguf.name,
|
||||
)
|
||||
|
||||
|
||||
def stop_llm_server_process() -> None:
|
||||
"""Завершить дочерний llama-server при выходе из процесса WESP."""
|
||||
global _proc
|
||||
if _proc is None:
|
||||
return
|
||||
p = _proc
|
||||
_proc = None
|
||||
try:
|
||||
p.terminate()
|
||||
p.wait(timeout=8)
|
||||
except Exception:
|
||||
try:
|
||||
p.kill()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user