feat: initial commit
This commit is contained in:
Executable
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Odczyt llm/models.yml: wykrycie backendu i renderowanie zmiennych środowiskowych.
|
||||
|
||||
Jedno miejsce decyduje, jaki backend jest właściwy dla platformy i jakie zmienne
|
||||
dostaje silnik - Taskfile tylko o to pyta, nie duplikuje wiedzy.
|
||||
|
||||
Użycie:
|
||||
backend.py detect # nazwa backendu dla tej maszyny
|
||||
backend.py env [--backend NAME] # linie `export ...` do eval
|
||||
backend.py get serve.model [--backend NAME]
|
||||
backend.py list
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
MODELS_FILE = Path(__file__).resolve().parent.parent / "models.yml"
|
||||
|
||||
|
||||
def load() -> dict[str, Any]:
|
||||
return yaml.safe_load(MODELS_FILE.read_text(encoding="utf-8")) or {}
|
||||
|
||||
|
||||
def _has_nvidia_gpu() -> bool:
|
||||
if not shutil.which("nvidia-smi"):
|
||||
return False
|
||||
try:
|
||||
return subprocess.run(["nvidia-smi", "-L"], capture_output=True, timeout=10).returncode == 0
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return False
|
||||
|
||||
|
||||
def _has_docker() -> bool:
|
||||
if not shutil.which("docker"):
|
||||
return False
|
||||
try:
|
||||
return subprocess.run(["docker", "info"], capture_output=True, timeout=20).returncode == 0
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return False
|
||||
|
||||
|
||||
def detect() -> str:
|
||||
"""Kolejność jest świadoma: najpierw akceleracja sprzętowa, potem cokolwiek, co działa."""
|
||||
system = platform.system().lower()
|
||||
|
||||
if system == "darwin" and platform.machine() == "arm64":
|
||||
return "vllm-metal"
|
||||
if system == "linux" and _has_nvidia_gpu() and _has_docker():
|
||||
return "vllm-gpu"
|
||||
if shutil.which("ollama") or _has_docker():
|
||||
return "ollama"
|
||||
# Brak GPU, Metala i Ollamy: realnego modelu i tak nie ma sensu tu uruchamiać.
|
||||
return "mock"
|
||||
|
||||
|
||||
def resolve(name: str | None) -> tuple[str, dict[str, Any]]:
|
||||
data = load()
|
||||
backends = data.get("backends") or {}
|
||||
if not name or name == "auto":
|
||||
name = detect()
|
||||
if name not in backends:
|
||||
raise SystemExit(f"Nieznany backend '{name}'. Dostępne: {', '.join(sorted(backends))}")
|
||||
return name, backends[name]
|
||||
|
||||
|
||||
def _dotted(config: dict[str, Any], path: str) -> Any:
|
||||
value: Any = config
|
||||
for part in path.split("."):
|
||||
if not isinstance(value, dict) or part not in value:
|
||||
raise SystemExit(f"Brak klucza '{path}' w konfiguracji backendu")
|
||||
value = value[part]
|
||||
return value
|
||||
|
||||
|
||||
def cmd_env(args: argparse.Namespace) -> None:
|
||||
name, config = resolve(args.backend)
|
||||
models = config.get("models") or {}
|
||||
lines = [
|
||||
f"export CODEMOD_LLM_BACKEND={name}",
|
||||
f"export CODEMOD_MODEL_PROVIDER={config.get('provider', 'openai_like')}",
|
||||
f"export CODEMOD_LLM_BASE_URL={config['base_url']}",
|
||||
f"export CODEMOD_LLM_API_KEY={config.get('api_key', 'not-required')}",
|
||||
]
|
||||
lines += [f"export CODEMOD_MODEL_{role.upper()}={model}" for role, model in sorted(models.items())]
|
||||
print("\n".join(lines))
|
||||
|
||||
|
||||
def cmd_detect(args: argparse.Namespace) -> None:
|
||||
requested = (args.requested or "auto").strip()
|
||||
print(detect() if requested in ("", "auto") else requested)
|
||||
|
||||
|
||||
def cmd_get(args: argparse.Namespace) -> None:
|
||||
_, config = resolve(args.backend)
|
||||
value = _dotted(config, args.key)
|
||||
print(" ".join(str(v) for v in value) if isinstance(value, list) else value)
|
||||
|
||||
|
||||
def cmd_list(_: argparse.Namespace) -> None:
|
||||
data = load()
|
||||
current = detect()
|
||||
for name, config in (data.get("backends") or {}).items():
|
||||
marker = "*" if name == current else " "
|
||||
platforms = ", ".join(config.get("platforms", []))
|
||||
print(f" {marker} {name:<12} {config['base_url']:<32} [{platforms}]")
|
||||
print("\n * = wykryty jako właściwy dla tej maszyny")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
det = sub.add_parser("detect")
|
||||
det.add_argument("--requested", default="auto", help="jawnie wskazany backend; 'auto' = wykrycie")
|
||||
det.set_defaults(func=cmd_detect)
|
||||
sub.add_parser("list").set_defaults(func=cmd_list)
|
||||
|
||||
env = sub.add_parser("env")
|
||||
env.add_argument("--backend")
|
||||
env.set_defaults(func=cmd_env)
|
||||
|
||||
get = sub.add_parser("get")
|
||||
get.add_argument("key")
|
||||
get.add_argument("--backend")
|
||||
get.set_defaults(func=cmd_get)
|
||||
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user