| """Judge backend selection + transformers-fallback β no GPU, no downloads.""" |
| import importlib |
|
|
| import formscout.config as config |
| from formscout.serving import get_vlm_client |
| from formscout.serving.llama_cpp import LlamaCppClient |
|
|
|
|
| def _reload_config(monkeypatch, **env): |
| for k, v in env.items(): |
| if v is None: |
| monkeypatch.delenv(k, raising=False) |
| else: |
| monkeypatch.setenv(k, v) |
| importlib.reload(config) |
| return config |
|
|
|
|
| def test_resolve_backend_default_local(monkeypatch): |
| cfg = _reload_config(monkeypatch, FORMSCOUT_JUDGE_BACKEND=None, SPACE_ID=None) |
| assert cfg.resolve_judge_backend() == "llama_cpp" |
|
|
|
|
| def test_resolve_backend_auto_on_zero_gpu_space(monkeypatch): |
| cfg = _reload_config(monkeypatch, FORMSCOUT_JUDGE_BACKEND="auto", |
| SPACE_ID="me/space", SPACES_ZERO_GPU="true") |
| assert cfg.resolve_judge_backend() == "transformers" |
| importlib.reload(config) |
|
|
|
|
| def test_resolve_backend_auto_on_cpu_space_stays_llama(monkeypatch): |
| |
| cfg = _reload_config(monkeypatch, FORMSCOUT_JUDGE_BACKEND="auto", |
| SPACE_ID="me/space", SPACES_ZERO_GPU=None, ZERO_GPU=None) |
| assert cfg.resolve_judge_backend() == "llama_cpp" |
| importlib.reload(config) |
|
|
|
|
| def test_resolve_backend_explicit(monkeypatch): |
| cfg = _reload_config(monkeypatch, FORMSCOUT_JUDGE_BACKEND="llama_cpp", SPACE_ID="me/space") |
| assert cfg.resolve_judge_backend() == "llama_cpp" |
| importlib.reload(config) |
|
|
|
|
| def test_factory_returns_llama_cpp_locally(monkeypatch): |
| _reload_config(monkeypatch, FORMSCOUT_JUDGE_BACKEND="llama_cpp", SPACE_ID=None) |
| client = get_vlm_client() |
| assert isinstance(client, LlamaCppClient) |
| importlib.reload(config) |
|
|
|
|
| def test_transformers_client_available_is_cheap_bool(): |
| |
| from formscout.serving.transformers_vlm import TransformersVLMClient |
| c = TransformersVLMClient() |
| assert isinstance(c.available, bool) |
| c._failed = True |
| assert c.available is False |
|
|
|
|
| def test_judge_uses_rubric_on_fallback_sentinel(): |
| from formscout.agents.judge import JudgeAgent |
| from formscout.types import BiomechFeatures, ScoreResult, MovementResult, IngestResult |
| import numpy as np |
|
|
| agent = JudgeAgent() |
|
|
| class _FakeClient: |
| available = True |
|
|
| def complete(self, *a, **k): |
| return {"error": "no gpu", "fallback": True, "text": ""} |
|
|
| agent._client = _FakeClient() |
| features = BiomechFeatures(test_name="deep_squat", view="2d", side="na", |
| angles={}, alignments={}, symmetry_delta=None, |
| timing={}, confidence=0.9) |
| rubric = ScoreResult(score=2, rationale="rubric says 2", confidence=0.8) |
| movement = MovementResult(test_name="deep_squat", side="na", confidence=1.0) |
| ingest = IngestResult(frames=[np.zeros((10, 10, 3), np.uint8)], fps=30.0, |
| duration=0.03, n_people=1, width=10, height=10) |
|
|
| res = agent.run(features, rubric, movement, ingest) |
| assert res.score == 2 |
| assert "rubric-only" in res.rationale |
|
|