Add YOLO preflight runtime diagnostics
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-05 21:17:20 +02:00
parent ad4fa45584
commit 7a29e7867d
6 changed files with 87 additions and 2 deletions
+1 -1
View File
@@ -299,7 +299,7 @@ To validate only local model/manifest paths on a machine without optional AI dep
python scripts/yolo_preflight.py --model-path /absolute/path/to/local-model.pt --tile-manifest-path /absolute/path/to/manifest.json --assume-dependencies --json
```
The preflight checks configuration, dependency availability, local model file existence, tile manifest validity, tile count and referenced tile paths. It does not load a YOLO model, run inference or download weights.
The preflight checks configuration, dependency availability, local model file existence, tile manifest validity, tile count and referenced tile paths. JSON output also includes runtime diagnostics for the model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` versions and CUDA availability when dependency checks pass. It does not load a YOLO model, run inference or download weights.
### Run backend
@@ -1,5 +1,7 @@
from __future__ import annotations
import os
from importlib import metadata
from pathlib import Path
from typing import Any, Type
@@ -42,6 +44,10 @@ class YoloPreflightService:
"max_tiles": resolved_settings.yolo_max_tiles,
"will_download_models": False,
"will_run_inference": False,
"runtime": YoloPreflightService._runtime_details(
settings=resolved_settings,
assume_dependencies=assume_dependencies,
),
}
if not resolved_settings.yolo_enabled:
@@ -50,6 +56,8 @@ class YoloPreflightService:
dependencies_available = True if assume_dependencies else yolo_adapter_class.dependencies_available()
result["checks"]["dependencies_available"] = dependencies_available
if dependencies_available and not assume_dependencies:
result["runtime"]["cuda_available"] = YoloPreflightService._torch_cuda_available()
if not dependencies_available:
result["status"] = "dependency_unavailable"
result["message"] = "YOLO dependencies are not installed. Install backend optional extras with geointel-backend[ai]."
@@ -115,3 +123,35 @@ class YoloPreflightService:
else:
result["message"] = "Configured YOLO preflight passed. No model was loaded and no inference was run."
return result
@staticmethod
def _runtime_details(*, settings: Settings, assume_dependencies: bool) -> dict[str, Any]:
model_directory = None
if settings.yolo_model_path:
model_directory = str(Path(settings.yolo_model_path).expanduser().parent)
return {
"dependencies_assumed": assume_dependencies,
"model_directory": model_directory,
"yolo_config_dir": os.environ.get("YOLO_CONFIG_DIR"),
"torch_version": YoloPreflightService._package_version("torch"),
"ultralytics_version": YoloPreflightService._package_version("ultralytics"),
"cuda_available": None,
}
@staticmethod
def _package_version(package_name: str) -> str | None:
try:
return metadata.version(package_name)
except metadata.PackageNotFoundError:
return None
@staticmethod
def _torch_cuda_available() -> bool | None:
try:
import torch
except Exception:
return None
try:
return bool(torch.cuda.is_available())
except Exception:
return None
+28 -1
View File
@@ -54,7 +54,9 @@ def _manifest(tmp_path: Path, tile_count: int = 1) -> Path:
return manifest_path
def test_yolo_preflight_reports_disabled_without_loading_model(tmp_path: Path) -> None:
def test_yolo_preflight_reports_disabled_without_loading_model(tmp_path: Path, monkeypatch) -> None:
monkeypatch.setenv("YOLO_CONFIG_DIR", str(tmp_path / "ultralytics"))
result = YoloPreflightService.run(
settings=Settings(yolo_enabled=False, yolo_model_path=str(tmp_path / "missing.pt")),
tile_manifest_path=str(tmp_path / "missing-manifest.json"),
@@ -66,6 +68,12 @@ def test_yolo_preflight_reports_disabled_without_loading_model(tmp_path: Path) -
assert result["checks"]["dependencies_available"] is None
assert result["checks"]["model_file_exists"] is None
assert result["checks"]["manifest_valid"] is None
assert result["runtime"]["dependencies_assumed"] is False
assert result["runtime"]["model_directory"] == str(tmp_path)
assert result["runtime"]["yolo_config_dir"] == str(tmp_path / "ultralytics")
assert "torch_version" in result["runtime"]
assert "ultralytics_version" in result["runtime"]
assert "cuda_available" in result["runtime"]
def test_yolo_preflight_distinguishes_missing_dependencies_from_missing_model(tmp_path: Path) -> None:
@@ -102,6 +110,25 @@ def test_yolo_preflight_validates_model_and_manifest_without_importing_yolo(tmp_
assert result["tile_count"] == 2
assert result["will_download_models"] is False
assert result["will_run_inference"] is False
assert result["runtime"]["dependencies_assumed"] is False
def test_yolo_preflight_marks_assumed_dependencies_in_runtime_details(tmp_path: Path) -> None:
model_path = tmp_path / "model.pt"
model_path.write_bytes(b"weights")
manifest_path = _manifest(tmp_path, tile_count=1)
result = YoloPreflightService.run(
settings=Settings(yolo_enabled=True, yolo_model_path=str(model_path), yolo_max_tiles=4),
tile_manifest_path=str(manifest_path),
yolo_adapter_class=MissingDependencyAdapter,
assume_dependencies=True,
)
assert result["status"] == "ready"
assert result["checks"]["dependencies_available"] is True
assert result["runtime"]["dependencies_assumed"] is True
assert result["runtime"]["model_directory"] == str(tmp_path)
def test_yolo_preflight_can_explicitly_smoke_load_local_model(tmp_path: Path) -> None: