feat: Add PeerTube & PieFed auto-update, improve classifier
Add auto-update scripts and review examples for PeerTube and PieFed, bringing total auto-updating services to three. The classifier now includes the current and target version in the prompt so it can correctly evaluate conditional upgrade notes like "follow v8.1.0 instructions if upgrading from <= v8.0.2". Without this context the model would conservatively flag patch releases as manual. All auto-update wrappers now use `bash -euo pipefail` when calling remote scripts so failures in upstream deploy/upgrade scripts propagate correctly instead of silently succeeding. test_classifier.py now accepts a service name argument and derives realistic version numbers from the release notes for testing.
This commit is contained in:
parent
f55f3d8e91
commit
67a9509eaa
13 changed files with 376 additions and 10 deletions
|
|
@ -1,8 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test the Ollama model's ability to classify Immich release notes.
|
||||
Test the Ollama model's ability to classify release notes.
|
||||
Runs leave-one-out: for each example, uses the rest as few-shot context
|
||||
and checks whether the model predicts the correct action.
|
||||
|
||||
Usage: python test_classifier.py [service_name]
|
||||
service_name defaults to "immich"
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
@ -11,9 +14,28 @@ import sys
|
|||
from pathlib import Path
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
import re as _re
|
||||
|
||||
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://192.168.1.20:11434")
|
||||
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "gemma4:26b-64k")
|
||||
EXAMPLES_DIR = Path(__file__).parent / "services" / "immich" / "review-examples"
|
||||
SERVICE_NAME = sys.argv[1] if len(sys.argv) > 1 else "immich"
|
||||
EXAMPLES_DIR = Path(__file__).parent / "services" / SERVICE_NAME / "review-examples"
|
||||
|
||||
|
||||
def guess_version_from_notes(notes: str) -> str:
|
||||
"""Extract a version number from release notes (e.g. '# v2.7.0' or '### v8.1.5')."""
|
||||
match = _re.search(r"v?(\d+\.\d+\.\d+)", notes)
|
||||
return match.group(1) if match else "1.0.0"
|
||||
|
||||
|
||||
def make_previous_version(version: str) -> str:
|
||||
"""Decrement patch to simulate upgrading from the previous patch release."""
|
||||
parts = version.split(".")
|
||||
if len(parts) == 3 and parts[2].isdigit():
|
||||
patch = int(parts[2])
|
||||
if patch > 0:
|
||||
return f"{parts[0]}.{parts[1]}.{patch - 1}"
|
||||
return version
|
||||
|
||||
|
||||
def load_examples(directory: Path) -> list[dict]:
|
||||
|
|
@ -43,16 +65,18 @@ def load_examples(directory: Path) -> list[dict]:
|
|||
return examples
|
||||
|
||||
|
||||
def build_messages(examples: list[dict], target_notes: str) -> list[dict]:
|
||||
def build_messages(examples: list[dict], target_notes: str, current: str = "1.0.0", latest: str = "2.0.0") -> list[dict]:
|
||||
system = (
|
||||
"You are an update safety classifier for self-hosted Immich instances.\n"
|
||||
"Given release notes for an Immich update, decide whether the update can be\n"
|
||||
f"You are an update safety classifier for self-hosted {SERVICE_NAME} instances.\n"
|
||||
f"Given release notes for a {SERVICE_NAME} update, decide whether the update can be\n"
|
||||
"applied automatically (action: auto) or requires the admin to intervene\n"
|
||||
"manually before or during the update (action: manual).\n\n"
|
||||
"Rules:\n"
|
||||
"- Classify only THIS release, not future or past releases mentioned in the notes.\n"
|
||||
"- Warnings or announcements about breaking changes in upcoming versions do NOT\n"
|
||||
" make the current release manual.\n"
|
||||
"- Conditional notes like 'follow X instructions if upgrading from <= Y' only apply\n"
|
||||
" if the current running version is <= Y. If we are already past Y, they do not apply.\n"
|
||||
"- Respond ONLY with a JSON object: {\"action\": \"auto\", \"reason\": \"...\"}\n"
|
||||
" or {\"action\": \"manual\", \"reason\": \"...\"}\n"
|
||||
"- Keep reason under 60 words."
|
||||
|
|
@ -67,7 +91,7 @@ def build_messages(examples: list[dict], target_notes: str) -> list[dict]:
|
|||
"content": json.dumps({"action": ex["action"], "reason": ex["reason"]}),
|
||||
})
|
||||
|
||||
messages.append({"role": "user", "content": f"Release notes:\n{target_notes}"})
|
||||
messages.append({"role": "user", "content": f"Upgrading from {current} to {latest}.\n\nRelease notes:\n{target_notes}"})
|
||||
return messages
|
||||
|
||||
|
||||
|
|
@ -117,7 +141,9 @@ def main():
|
|||
|
||||
for i, target in enumerate(examples):
|
||||
few_shot = [ex for j, ex in enumerate(examples) if j != i]
|
||||
messages = build_messages(few_shot, target["notes"])
|
||||
latest = guess_version_from_notes(target["notes"])
|
||||
current = make_previous_version(latest)
|
||||
messages = build_messages(few_shot, target["notes"], current, latest)
|
||||
|
||||
try:
|
||||
response = call_ollama(messages)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue