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.
This commit is contained in:
Jeena 2026-10-06 22:10:07 +09:00
parent 2df588b957
commit 86268107fa
2 changed files with 19 additions and 2 deletions

View file

@ -23,3 +23,4 @@ UPTIME_KUMA_PASSWORD=your-password
# Ollama for AI-driven auto-update classification # Ollama for AI-driven auto-update classification
OLLAMA_BASE_URL=http://192.168.1.20:11434 OLLAMA_BASE_URL=http://192.168.1.20:11434
OLLAMA_MODEL=gemma4:26b OLLAMA_MODEL=gemma4:26b
OLLAMA_FALLBACK_MODEL=gemma4:12b

View file

@ -29,9 +29,11 @@ class Classifier:
self, self,
base_url: str = "", base_url: str = "",
model: str = "", model: str = "",
fallback_model: str = "",
) -> None: ) -> None:
self.base_url = base_url or os.getenv("OLLAMA_BASE_URL", "http://192.168.1.20:11434") 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.model = model or os.getenv("OLLAMA_MODEL", "gemma4:26b")
self.fallback_model = fallback_model or os.getenv("OLLAMA_FALLBACK_MODEL", "gemma4:12b")
def classify( def classify(
self, self,
@ -54,9 +56,23 @@ class Classifier:
context = context_file.read_text(encoding="utf-8").strip() if context_file.is_file() else None 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) 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: try:
payload = json.dumps({ payload = json.dumps({
"model": self.model, "model": model,
"messages": messages, "messages": messages,
"stream": False, "stream": False,
"options": {"temperature": 0}, "options": {"temperature": 0},
@ -79,7 +95,7 @@ class Classifier:
raise ValueError(f"Unexpected action value: {action!r}") raise ValueError(f"Unexpected action value: {action!r}")
return {"action": action, "reason": result.get("reason", "")} return {"action": action, "reason": result.get("reason", "")}
except Exception as exc: except Exception as exc:
return {"action": "manual", "reason": f"Classification error: {exc}"} raise RuntimeError(f"model {model}: {exc}") from exc
def _build_messages( def _build_messages(
self, self,