Files
agentic-codemod-pipeline/llm/scripts/backend.py
T
2026-08-29 13:17:59 +02:00

142 lines
4.5 KiB
Python
Executable File

#!/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())