From 86268107fa45e4f343cb43ef5e035142044a00bc Mon Sep 17 00:00:00 2001 From: Jeena Date: Tue, 6 Oct 2026 22:10:07 +0900 Subject: [PATCH] classifier: Add fallback model for transient LLM failures The 26b model occasionally hangs or fails outright on the local Ollama box, which used to drop every such update into 'manual' for no good reason. Retry the same prompt with a smaller model before giving up, and surface both errors when even the fallback fails. OLLAMA_FALLBACK_MODEL is new config, see .env.sample. --- .env.sample | 1 + update_monitor/classifier.py | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.env.sample b/.env.sample index d4d11d0..73cf4a0 100644 --- a/.env.sample +++ b/.env.sample @@ -23,3 +23,4 @@ UPTIME_KUMA_PASSWORD=your-password # Ollama for AI-driven auto-update classification OLLAMA_BASE_URL=http://192.168.1.20:11434 OLLAMA_MODEL=gemma4:26b +OLLAMA_FALLBACK_MODEL=gemma4:12b diff --git a/update_monitor/classifier.py b/update_monitor/classifier.py index a27d797..9a072d1 100644 --- a/update_monitor/classifier.py +++ b/update_monitor/classifier.py @@ -29,9 +29,11 @@ class Classifier: self, base_url: str = "", model: str = "", + fallback_model: str = "", ) -> None: self.base_url = base_url or os.getenv("OLLAMA_BASE_URL", "http://192.168.1.20:11434") self.model = model or os.getenv("OLLAMA_MODEL", "gemma4:26b") + self.fallback_model = fallback_model or os.getenv("OLLAMA_FALLBACK_MODEL", "gemma4:12b") def classify( self, @@ -54,9 +56,23 @@ class Classifier: context = context_file.read_text(encoding="utf-8").strip() if context_file.is_file() else None messages = self._build_messages(service_dir.name, examples, release_notes, current, latest, context) + try: + return self._classify_with_model(self.model, messages, timeout) + except Exception as primary_exc: + if not self.fallback_model or self.fallback_model == self.model: + return {"action": "manual", "reason": f"Classification error: {primary_exc}"} + try: + return self._classify_with_model(self.fallback_model, messages, timeout) + except Exception as fallback_exc: + return { + "action": "manual", + "reason": f"Classification error: {primary_exc}; fallback ({self.fallback_model}) also failed: {fallback_exc}", + } + + def _classify_with_model(self, model: str, messages: List[Dict[str, str]], timeout: float) -> Dict[str, str]: try: payload = json.dumps({ - "model": self.model, + "model": model, "messages": messages, "stream": False, "options": {"temperature": 0}, @@ -79,7 +95,7 @@ class Classifier: raise ValueError(f"Unexpected action value: {action!r}") return {"action": action, "reason": result.get("reason", "")} except Exception as exc: - return {"action": "manual", "reason": f"Classification error: {exc}"} + raise RuntimeError(f"model {model}: {exc}") from exc def _build_messages( self,