Spaces:
Paused
Paused
| """Testy hybrid routingu — AI klasifikace + fallbacky.""" | |
| import pytest | |
| def hybrid(app_module): | |
| """App v hybrid módu s 'ready' enginem (bez reálného subprocessu).""" | |
| import engine as engine_module | |
| app_module.SETTINGS.update({"agent_mode": "hybrid", "router_mode": "ai"}) | |
| app_module.ENGINE.state = engine_module.EngineState.READY | |
| return app_module | |
| def test_explicit_tags_override_ai(hybrid, monkeypatch): | |
| # tagy mají absolutní přednost — AI router se vůbec nesmí volat | |
| def _boom(messages): | |
| raise AssertionError("router se nemá volat") | |
| monkeypatch.setattr(hybrid, "_router_llm_call", _boom) | |
| assert hybrid._route_task("[kimi] narhni schéma") == "api" | |
| assert hybrid._route_task("@api udělej X") == "api" | |
| assert hybrid._route_task("[local] oprav překlep") == "local" | |
| assert hybrid._route_task("[qwen] oprav překlep") == "local" | |
| def test_context_limit_overrides_ai(hybrid, monkeypatch): | |
| monkeypatch.setattr(hybrid, "_router_llm_call", | |
| lambda m: (_ for _ in ()).throw(AssertionError)) | |
| hybrid.SETTINGS.update({"local_context_limit": 100}) | |
| assert hybrid._route_task("x" * 200) == "api" | |
| def test_ai_router_decides(hybrid, monkeypatch): | |
| calls = [] | |
| def fake_llm(messages): | |
| calls.append(messages) | |
| return "API" | |
| monkeypatch.setattr(hybrid, "_router_llm_call", fake_llm) | |
| assert hybrid._route_task("oprav mi tenhle test") == "api" | |
| # system prompt + user zpráva | |
| assert calls[0][0]["role"] == "system" | |
| assert "LOCAL or API" in calls[0][0]["content"] | |
| monkeypatch.setattr(hybrid, "_router_llm_call", lambda m: "LOCAL") | |
| assert hybrid._route_task("oprav mi tenhle test") == "local" | |
| def test_ai_router_parses_verbose_answers(hybrid, monkeypatch): | |
| monkeypatch.setattr(hybrid, "_router_llm_call", | |
| lambda m: "Odpověď: LOCAL — jde o rutinní úpravu.") | |
| assert hybrid._route_task("přejmenuj proměnnou") == "local" | |
| # obě slova v odpovědi => rozhoduje první výskyt | |
| monkeypatch.setattr(hybrid, "_router_llm_call", | |
| lambda m: "API (not LOCAL) because of architecture") | |
| assert hybrid._route_task("běžná otázka") == "api" | |
| def test_ai_router_garbage_falls_back_to_keywords(hybrid, monkeypatch): | |
| monkeypatch.setattr(hybrid, "_router_llm_call", lambda m: "žblepty 42") | |
| assert hybrid._route_task("proveď komplexní analýza modulu") == "api" # keyword | |
| assert hybrid._route_task("oprav překlep v readme") == "local" | |
| def test_ai_router_exception_falls_back(hybrid, monkeypatch): | |
| def _fail(messages): | |
| raise RuntimeError("engine timeout") | |
| monkeypatch.setattr(hybrid, "_router_llm_call", _fail) | |
| assert hybrid._route_task("navrhni architektura systému") == "api" # keyword | |
| assert hybrid._route_task("vypiš soubory") == "local" | |
| def test_engine_not_ready_falls_back(hybrid, monkeypatch): | |
| import engine as engine_module | |
| hybrid.ENGINE.state = engine_module.EngineState.STOPPED | |
| monkeypatch.setattr(hybrid, "_router_llm_call", | |
| lambda m: (_ for _ in ()).throw(AssertionError)) | |
| assert hybrid._route_task("root cause tohoto pádu") == "api" # keyword | |
| assert hybrid._route_task("malý fix") == "local" | |
| def test_keywords_mode_skips_ai(hybrid, monkeypatch): | |
| hybrid.SETTINGS.update({"router_mode": "keywords"}) | |
| monkeypatch.setattr(hybrid, "_router_llm_call", | |
| lambda m: (_ for _ in ()).throw(AssertionError)) | |
| assert hybrid._route_task("security audit") == "api" | |
| assert hybrid._route_task("napiš unit test") == "local" | |
| def test_router_mode_validation(settings_module): | |
| mgr = settings_module.SettingsManager() | |
| assert mgr.get().router_mode == "ai" # default | |
| applied, reload_needed, _ = mgr.update({"router_mode": "keywords"}) | |
| assert applied == {"router_mode": "keywords"} and reload_needed is False | |
| # case-insensitive normalizace | |
| applied, _, errors = mgr.update({"router_mode": "AI"}) | |
| assert applied == {"router_mode": "ai"} and errors == {} | |
| _, _, errors = mgr.update({"router_mode": "nesmysl"}) | |
| assert "router_mode" in errors | |
| def test_new_default_model_and_presets(app_module): | |
| from presets import PRESETS, infer_parsers | |
| assert app_module.SETTINGS.get().model == "Qwen/Qwen3-Coder-Next-FP8" | |
| assert "qwen3-coder-next-fp8" in PRESETS | |
| assert "minimax-m2.7" in PRESETS | |
| assert "minimax-m2" not in PRESETS | |
| # parser rodiny: Coder-Next dědí qwen3_coder, Devstral -> mistral | |
| assert infer_parsers("Qwen/Qwen3-Coder-Next-FP8") == ("qwen3_coder", "") | |
| assert infer_parsers("mistralai/Devstral-2-123B-Instruct-2512") == ("mistral", "") | |
| assert infer_parsers("MiniMaxAI/MiniMax-M2.7") == ("minimax_m2", "minimax_m2_append_think") | |