Anleitungen
Tutorial: Bilder, mit Prüfungen und einem Judge, der sie sieht
Evaluieren Sie eine Funktion, die Bilder erzeugt, hier einen Thumbnail-Generator für Produkte, der auch eine Bildunterschrift schreibt. Prüfen Sie die Thumbnails mit Messungen, die kein Modell brauchen, beurteilen Sie die Bildunterschriften mit einem Modell, das jedes Thumbnail sieht, messen Sie diesen Judge an einer Person, und liefern Sie dann eine echte Änderung aus. Jeder Schritt läuft auf diesem Rechner ohne Modell und ohne Netzwerk, und ein optionaler Schritt setzt ein echtes Vision-Modell ein.
Was Sie bauen
Ein Katalogdienst nimmt ein Produktfoto, speichert ein Thumbnail und schreibt eine einzeilige Bildunterschrift, die die Farbe des Produkts nennt. Drei Fragen entscheiden, ob eine Version ausgeliefert werden darf:
- Passt jedes Thumbnail in den Katalog: ein Web-Format, höchstens 256 mal 256 Pixel, höchstens 50 kB? Das ist eine Messung, die media_check an den Bytes des Thumbnails vornimmt.
- Ist jedes Thumbnail noch das Foto des Produkts und kein leeres Bild, kein Ausschnitt und kein anderes Produkt? image_matches_reference vergleicht es per Wahrnehmungs-Hash mit dem Foto.
- Nennt jede Bildunterschrift die Farbe, die eine Käuferin sieht? Dafür braucht es Augen, also wird einem Rubrik-Judge das Thumbnail gezeigt.
Die Fotos und Thumbnails bleiben im Projektordner. Das System zeichnet sie per Referenz auf (media/v1): wo jede Datei liegt, ihr Typ, ihre Größe und ihr sha256. Begriffe wie Fall, Lauf, Judge und Gate sind in Kernkonzepte definiert; alles über Medien steht in Bilder, Audio und Video.
Voraussetzungen
- Python 3.11 oder neuer und Oloproof mit seinem Extra media, das Pillow zum Vergleichen von Bildern hinzufügt:
python3 -m venv .venv
. .venv/bin/activate
pip install 'oloproof[media]'- Das Beispielprojekt, das mit dem Paket ausgeliefert wird. Kopieren Sie es in ein neues Verzeichnis und arbeiten Sie dort:
oloproof init --example media product-thumbnails
cd product-thumbnails- Port 8798 frei für den Ersatz-Judge (andernfalls ändern Sie ihn an beiden Stellen).
Jeder Schritt bis „Optional: ein echtes Vision-Modell“ ist offline und deterministisch: kein API-Schlüssel, kein Provider-Konto, keine Kosten.
Die Dateien
product-thumbnails/
app.py the thumbnailer under test (baseline)
app_v2.py the candidate change
judge_server.py a stand-in vision judge speaking the OpenAI API on 127.0.0.1
rubrics/caption_names_colour.md the judge's rubric
oloproof.yaml the suite
release.yaml rules for a run
compare.yaml a rule for a comparison
data/products.jsonl 20 cases
photos/ the 20 product photos the cases name
make_photos.py how the photos were drawn
labels/reviewer_verdicts.csv one person's verdicts on the baseline's captions
fill_labels.py copies those verdicts into a labelling sheetFühren Sie jeden Befehl in product-thumbnails/ aus.
Die Anwendung zeichnet ihre Bilder auf
# app.py (excerpt)
@system(name="thumbnailer", version=VERSION, records=("media/v1",))
def describe(case: dict[str, Any]) -> dict[str, str]:
photo_path = HERE / case["photo"]
photo = Image.open(photo_path).convert("RGB")
thumb = photo.copy()
thumb.thumbnail((320, 320))
out = HERE / "thumbs" / VERSION / photo_path.name
out.parent.mkdir(parents=True, exist_ok=True)
thumb.save(out)
given = media_file(photo_path, role="input")
made = media_file(out, width=thumb.width, height=thumb.height)
current_case().media([given, made])
centre = photo.getpixel((photo.width // 2, photo.height // 2))
return {"caption": f"A {colour_name(centre)} product."}media_file(path) beschreibt eine Datei: ihren file://-Ort, MIME-Typ, ihre Größe und ihren sha256, benannt nach der Datei. role="input" kennzeichnet das Foto, das das System bekommen hat; das Thumbnail ist eine Ausgabe, der Standard. current_case().media(...) zeichnet beide als ein media/v1-Artefakt auf, und records: [media/v1] in oloproof.yaml deklariert, dass das System es aufzeichnet. Nichts wird kopiert: Eine Prüfung oder ein Judge liest jede Datei später erneut und lehnt eine ab, deren Bytes nicht mehr zum Datensatz passen.
Diese Baseline hat zwei Fehler. Ihre Thumbnails sind 320 Pixel breit, und ihre Bildunterschrift nennt die Farbe des mittleren Pixels des Fotos, also den Hintergrund, wann immer das Produkt seitlich steht. Jede Version schreibt ihre Thumbnails unter thumbs/<version>/, sodass ein späterer Lauf nie die Dateien überschreibt, die ein früherer Lauf aufgezeichnet hat.
Der Datensatz
{"id": "p03", "input": {"photo": "photos/p03.png"}, "expected": {"photo": "photos/p03.png", "colour": "yellow", "shape": "triangle"}}input ist das, was das System erhält. expected.photo ist die Referenz, mit der das Thumbnail verglichen wird; colour und shape sind für eine Person da, die den Fall liest, und kein Evaluator liest sie.
Die Evaluatoren wählen
evaluators:
# The catalogue's limits, measured from the thumbnail's bytes: a web format, at most 256 by
# 256 pixels, at most 50 kB.
- type: media_check
criterion: thumbnail_fits
kind: image
formats: [png, jpeg, webp]
max_width: 256
max_height: 256
max_bytes: 50000
# The thumbnail is still the product's photo: a perceptual hash within 10 of 64 bits.
- type: image_matches_reference
criterion: same_photo
reference_field: photo
max_distance: 10
# The caption, judged by a model that sees the thumbnail.
- type: rubric_judge
criterion: caption_names_colour
provider: openai_compatible
model: stand-in-vision
base_url: http://127.0.0.1:8798/v1
rubric_file: rubrics/caption_names_colour.md
media: outputmedia_check misst jedes Ausgabebild: das Format aus seinen ersten Bytes, die Größe aus seinem Header, nie aus dem, was das System deklariert hat. image_matches_reference hasht das Thumbnail und das Foto, das expected.photo nennt, und besteht, wenn sich die Hashes in höchstens 10 ihrer 64 Bits unterscheiden; eine Verkleinerung oder eine Neukomprimierung verschiebt einen Hash um wenige Bits, ein anderes Bild um viele. Keiner von beiden ruft ein Modell auf, also entscheiden beide ab dem ersten Lauf.
media: output hängt die Ausgabebilder des Falls an die Anfrage des Judges an, jedes in seinem Prompt benannt. Ein Judge entscheidet nichts, bis die Labels von Menschen zeigen, dass er mit ihnen übereinstimmt, also wartet die dritte Regel darauf.
Der Ersatz-Judge
Ein Rubrik-Judge schickt seinen Prompt und die angehängten Bilder an ein Modell und liest {"pass": true|false, "rationale": "..."} zurück. Hier ist das Modell judge_server.py, das dieselbe API auf 127.0.0.1 spricht und kein Modell ist: Es dekodiert das angehängte Thumbnail, findet dessen häufigste Farbe außer dem Weiß und Grau des Hintergrunds und lässt eine Bildunterschrift bestehen, die sie nennt. Starten Sie es in einem zweiten Terminal und lassen Sie es laufen:
python judge_server.py --port 8798Die Policy
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
rules:
- id: thumbnails-fit
metric: thumbnail_fits
kind: observed_count
max_failures: 0
- id: same-photo
metric: same_photo
kind: observed_count
max_failures: 0
- id: caption-floor
metric: caption_names_colour
min: 0.80Jedes Thumbnail muss passen und muss sein Foto sein. Mindestens 80 % der Bildunterschriften müssen die richtige Farbe nennen, entschieden nur dann, wenn die untere Grenze des Intervalls 80 % übersteigt.
Ausführen
oloproof runGate: BLOCK (exit 1)
│ thumbnails-fit │ thumbnail_fits │ FAIL │ observed_failures_exceed_limit │
│ same-photo │ same_photo │ PASS │ observed_failures_within_limit │
│ caption-floor │ caption_names_colour │ INSUFFICIENT_EVIDENCE │ evaluator_not_validated │
│ thumbnail_fits │ 0.0% │ [0.0%, 16.9%] │ 0 / 20 observed · 0 missing · 0 excluded │
│ same_photo │ 100.0% │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ caption_names_colour │ 60.0% │ [36.0%, 80.9%] │ 12 / 20 observed · 0 missing · 0 excluded │Kein Thumbnail passt, jedes Thumbnail ist sein Foto, und 12 von 20 Bildunterschriften nennen die richtige Farbe. Die Regel des Judges ist unentschieden, weil noch niemand den Judge gemessen hat.
Die Fehlschläge untersuchen
oloproof inspect RUN_ID --failuresp03
output: {"caption": "A white product."}
thumbnail_fits: failed
measured: p03.png: width 320 px is above 256 px
caption_names_colour: failed
judge text, not verified: the product is yellow; the caption says 'a white product.'Der Grund einer Prüfung ist eine Messung, markiert als measured; der eines Judges sind seine eigenen Worte, die niemand geprüft hat. Die Fotos und Thumbnails erscheinen auch neben jedem Urteil auf der Seite des Falls in der Workbench, nach oloproof export RUN_ID, gelesen aus diesem Ordner; ein gehosteter Workspace lässt Ihren Browser sie von oloproof collect abrufen und hält sie nie selbst (Bilder, Audio und Video).
Den Judge an einer Person messen
Ziehen Sie eine Zufallsstichprobe der Fälle des Laufs in eine Tabelle, ohne die Urteile des Judges, lassen Sie eine Person jedes Thumbnail und jede Bildunterschrift ansehen, und importieren Sie, was sie gesagt hat. Hier vertritt fill_labels.py diese Person und kopiert ihre Urteile aus labels/reviewer_verdicts.csv:
oloproof labels export RUN_ID --criterion caption_names_colour --sample 20 --local --out sample.csv
python fill_labels.py sample.csv
oloproof labels import sample.csvMessen Sie den Judge dann an diesen Labels, und halten Sie fest, wer es getan hat:
oloproof evaluators list
oloproof evaluators validate EVALUATOR_ID --by alicecaption_names_colour: sha256:53605dd6... is now VALIDATED
agreement 100.0% [83.1%, 100.0%] · 20 of 20 labelled cases agreed · 0 labelled but not judged · kappa 1.000Ein Judge, der Bilder sieht, wird genauso gemessen wie einer, der Text liest. In einem gehosteten Workspace zeigt die Review-Warteschlange der prüfenden Person die Bilder jedes Falls, abgerufen von ihrem Browser bei Ihrem Collector (Judges).
oloproof gate RUN_ID --policy release.yamlthumbnails-fit: FAIL (observed_failures_exceed_limit)
same-photo: PASS (observed_failures_within_limit)
caption-floor: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
Gate: BLOCK (exit 1)Der Judge darf jetzt entscheiden, und 60 % reichen nicht.
Eine echte Änderung vornehmen
app_v2.py erzeugt Thumbnails mit 256 Pixeln und nennt die häufigste Farbe des Fotos außer dem Hintergrund, wo immer das Produkt steht. Kopieren Sie es über app.py, setzen Sie version: main-colour unter system in oloproof.yaml, lassen Sie den Judge laufen, und:
oloproof runGate: ALLOW (exit 0)
│ thumbnails-fit │ thumbnail_fits │ PASS │ observed_failures_within_limit │
│ same-photo │ same_photo │ PASS │ observed_failures_within_limit │
│ caption-floor │ caption_names_colour │ PASS │ lower_bound_meets_minimum │Der Judge ist dieselbe Evaluator-Version, die validiert wurde, also zählen seine Urteile.
Den Kandidaten mit der Baseline vergleichen
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlthumbnail_fits: +100.0 points [+52.9, +100.0] · 20 paired · 0 missing · 0 excluded
caption_names_colour: +40.0 points [+1.8, +72.7] · 20 paired · 0 missing · 0 excluded
captions-better caption_names_colour superiority PASS difference_above_zeroOptional: ein echtes Vision-Modell
Dieser Schritt verlässt den Offline-Pfad. Er braucht ein Modell, das Bilder liest: lokal Ollama mit einem Vision-Modell (ollama pull qwen2.5vl), ohne Schlüssel und ohne Kosten; in der Cloud provider: anthropic oder openai mit api_key_env, abgerechnet pro Bild ebenso wie pro Wort.
# live_judge.yaml
type: rubric_judge
criterion: caption_names_colour
provider: openai_compatible
model: qwen2.5vl
base_url: http://localhost:11434/v1
rubric_file: rubrics/caption_names_colour.md
media: output
timeout_s: 300oloproof evaluators try live_judge.yamlDas beurteilt die Fälle, die Ihre Reviewerin gelabelt hat, mit dem echten Modell und meldet seine Übereinstimmung, ohne es zu übernehmen. Um es zu übernehmen, setzen Sie es in oloproof.yaml an die Stelle des Ersatz-Judges: Ein neues Modell ist eine neue Evaluator-Version, also validieren Sie es an den Labels, wie oben.
Fehlerbehebung
| Symptom | Ursache und Abhilfe |
|---|---|
| caption_names_colour fehlt überall | Der Judge-Server läuft nicht oder nicht unter base_url; oloproof inspect RUN_ID --failures zeigt warum. |
| image_matches_reference stoppt den Lauf und nennt Pillow | Installieren Sie das Extra media: pip install 'oloproof[media]'. |
| Ein Fall fehlt mit media_unavailable | Eine aufgezeichnete Datei wurde nach dem Lauf verschoben oder gelöscht. Bewahren Sie die Dateien, die ein Lauf aufgezeichnet hat, so lange auf, wie Sie den Lauf aufbewahren. |
| Ein Lauf stoppt mit is not the recorded content | Eine aufgezeichnete Datei wurde nach dem Lauf geändert. Eine Prüfung oder ein Judge liest nie andere Bytes als die aufgezeichneten; schreiben Sie die Dateien jeder Version an einen eigenen Ort. |
| evaluator_not_validated, nachdem Sie validiert haben | Sie haben den Judge geändert (Modell, Endpunkt, Port, Rubrik, media) und eine neue Version erzeugt. Validieren Sie diese. |
Einschränkungen
- Der Ersatz-Judge zählt Pixel. Er demonstriert den Ablauf, nicht die Qualität des Urteilens.
- Der Wahrnehmungs-Hash liest Aufbau und Licht, nicht Farbe: Er unterscheidet ein Thumbnail des Fotos von einem leeren Bild oder einer anderen Szene, nicht aber ein Produkt von einem anderen aus demselben Studio. Flache Grafiken mit großen leeren Flächen verschieben ihn stärker als Fotos.
- Zwanzig Labels ergeben ein breites Übereinstimmungsintervall. Labeln Sie mehr, zufällig und blind, für einen Judge, auf den Sie sich verlassen.
- Audio und Video funktionieren genauso, mit anderen Einstellungen: siehe Bilder, Audio und Video.