From c7358ac97da346d5929e951b48cf5a80878b5d13 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 30 Aug 2026 00:55:48 +1000 Subject: [PATCH] fix: recover empty validation compilations --- dashboard/manifest.json | 2 +- dashboard/plugin_api.py | 12 +++++------ plugin.yaml | 2 +- tests/test_validation_harness.py | 36 ++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/dashboard/manifest.json b/dashboard/manifest.json index c1c792c..2ce7611 100644 --- a/dashboard/manifest.json +++ b/dashboard/manifest.json @@ -3,7 +3,7 @@ "label": "Ollama Models", "description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.", "icon": "Cpu", - "version": "1.7.9", + "version": "1.7.10", "tab": {"path": "/ollama-manager", "position": "after:models"}, "entry": "dist/index.js", "css": "dist/style.css", diff --git a/dashboard/plugin_api.py b/dashboard/plugin_api.py index 37feeaa..4ee86ce 100644 --- a/dashboard/plugin_api.py +++ b/dashboard/plugin_api.py @@ -2150,13 +2150,13 @@ def _run_validation_harness( metrics.append(_persist_metric(conversation_id, final_id, primary, final_state, status="completed")) final_message = final_result.get("message") if isinstance(final_result.get("message"), dict) else {} final_content = str(final_message.get("content") or "").strip() - if not final_content: - raise HTTPException(502, "Primary model returned an empty compiled answer") - if _looks_like_validation_report(final_content): + compiler_needs_retry = not final_content or _looks_like_validation_report(final_content) + if compiler_needs_retry: retry_id = uuid.uuid4().hex retry_started = time.time() + retry_reason = "empty output" if not final_content else "review text" _persist_chat_stage(request_id, "compiler-retry", primary, "running", retry_started) - _persist_chat_event(request_id, conversation_id, "compiler-retry-started", level="warning", stage="Primary returned review text; requesting final answer", model=primary) + _persist_chat_event(request_id, conversation_id, "compiler-retry-started", level="warning", stage=f"Primary returned {retry_reason}; requesting final answer", model=primary) _chat_state(retry_id, state="preparing", stage="Preparing final-answer retry", model=primary, parent_id=request_id, conversation_id=conversation_id) retry_payload = _chat_payload( body, @@ -2178,8 +2178,8 @@ def _run_validation_harness( _persist_chat_event(request_id, conversation_id, "compiler-retry-completed", stage="Final answer retry completed", model=primary, payload={"output_chars": len(final_content)}) else: final_content = draft - _persist_chat_stage(request_id, "compiler-retry", primary, "fallback", retry_started, finished_at=retry_finished, output_chars=len(final_content), error="Primary returned review text twice; preserved the primary draft") - _persist_chat_event(request_id, conversation_id, "compiler-retry-fallback", level="warning", stage="Preserved primary draft after invalid finalization", model=primary, payload={"output_chars": len(final_content)}) + _persist_chat_stage(request_id, "compiler-retry", primary, "fallback", retry_started, finished_at=retry_finished, output_chars=len(final_content), error=f"Primary returned {retry_reason} twice; preserved the primary draft") + _persist_chat_event(request_id, conversation_id, "compiler-retry-fallback", level="warning", stage="Preserved primary draft after invalid finalization", model=primary, payload={"output_chars": len(final_content), "reason": retry_reason}) compiler_finished = time.time() _persist_chat_stage(request_id, "compiler", primary, "completed", compiler_started, finished_at=compiler_finished, output_chars=len(final_content)) _persist_chat_event(request_id, conversation_id, "compiler-completed", stage="Primary final answer compiled", model=primary, payload={"output_chars": len(final_content)}) diff --git a/plugin.yaml b/plugin.yaml index c01503a..cf3b5af 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,5 +1,5 @@ name: ollama-manager -version: 1.7.9 +version: 1.7.10 description: Native dashboard manager and chat interface for local Ollama models, attachments, URLs, shared persistent conversations, performance metrics, and live runtime telemetry. auto_install_dependencies: true python_dependencies: diff --git a/tests/test_validation_harness.py b/tests/test_validation_harness.py index 4b40931..f52f123 100644 --- a/tests/test_validation_harness.py +++ b/tests/test_validation_harness.py @@ -178,6 +178,42 @@ class ValidationHarnessTests(unittest.TestCase): self.assertEqual(len(metrics), 4) self.assertTrue(any("IMPORTANT FINALIZATION RETRY" in prompt for _, prompt in calls)) + def test_empty_compiler_output_is_retried_and_never_becomes_a_502(self): + body = api.ChatRequest(primary_model="primary", validator_models=["validator"], harness=True, message="Write the requested result") + calls = [] + + def fake_stream(payload, request_id, cancel_event=None, parent_id=None): + model = payload["model"] + prompt = payload["messages"][-1]["content"] + calls.append((model, prompt)) + if model == "validator": + content = "Make the result more specific." + elif "IMPORTANT FINALIZATION RETRY" in prompt: + content = "refined final answer after empty compiler output" + elif "VALIDATION REPORTS:" in prompt: + content = "" + else: + content = "primary draft" + return {"message": {"role": "assistant", "content": content}, "done": True} + + def fake_metric(conversation_id, request_id, model, state, status=None, error=""): + return {"request_id": request_id, "model": model, "status": status} + + with patch.object(api, "_require_installed_model", side_effect=lambda name: name), patch.object( + api, "_stream_chat_request", side_effect=fake_stream + ), patch.object(api, "_persist_metric", side_effect=fake_metric), patch.object( + api, "_persist_chat_stage" + ), patch.object(api, "_persist_chat_event"), patch.object(api, "_chat_state"): + final, metrics, reports = api._run_validation_harness( + body, "root-request", "conversation", "primary", ["validator"], threading.Event(), [] + ) + + self.assertEqual(final, "refined final answer after empty compiler output") + self.assertEqual(len(reports), 1) + self.assertEqual(len(metrics), 4) + self.assertTrue(any("Primary returned empty output" in prompt for _, prompt in calls) or any("IMPORTANT FINALIZATION RETRY" in prompt for _, prompt in calls)) + + def test_status_omits_full_catalog_by_default(self): with patch.object(api, "_local_tags", return_value=[]), patch.object(api, "_local_ps", return_value=[]), patch.object( api, "_ensure_catalog", return_value={"models": [], "families": {}, "source": "test"}