Guide
Tutorial: immagini, con controlli e un giudice che le vede
Valuta una funzione che crea immagini, qui un generatore di miniature di prodotti che scrive anche una didascalia. Controlla le miniature con misurazioni che non richiedono un modello, giudica le didascalie con un modello che vede ogni miniatura, misura quel giudice rispetto a una persona, poi rilascia una modifica reale. Ogni passo gira su questa macchina senza modello e senza rete, e un passo facoltativo lo sostituisce con un vero modello di visione.
Che cosa costruirai
Un servizio di catalogo riceve la foto di un prodotto, salva una miniatura e scrive una didascalia di una riga che nomina il colore del prodotto. Tre domande decidono se una versione può essere rilasciata:
- Ogni miniatura rispetta i requisiti del catalogo: un formato web, al massimo 256 per 256 pixel, al massimo 50 kB? È una misurazione, fatta da media_check a partire dai byte della miniatura.
- Ogni miniatura è ancora la foto del prodotto, e non un'immagine vuota, un ritaglio o un altro prodotto? image_matches_reference la confronta con la foto tramite hash percettivo.
- Ogni didascalia nomina il colore che vede un acquirente? Servono degli occhi, quindi a un giudice a rubrica viene mostrata la miniatura.
Le foto e le miniature restano nella cartella del progetto. Il sistema le registra per riferimento (media/v1): dove si trova ogni file, il suo tipo, la dimensione e lo sha256. Termini come caso, esecuzione, giudice e gate sono definiti in Concetti; tutto ciò che riguarda i media è in Immagini, audio e video.
Prerequisiti
- Python 3.11 o successivo, e Oloproof con il suo extra media, che aggiunge Pillow per confrontare le immagini:
python3 -m venv .venv
. .venv/bin/activate
pip install 'oloproof[media]'- Il progetto di esempio, incluso nel pacchetto. Copialo in una nuova directory e lavora lì:
oloproof init --example media product-thumbnails
cd product-thumbnails- La porta 8798 libera per il giudice sostitutivo (altrimenti cambiala in entrambi i punti).
Ogni passo fino a "Facoltativo: un vero modello di visione" è offline e deterministico: nessuna chiave API, nessun account di provider, nessun costo.
I file
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 sheetEsegui ogni comando da product-thumbnails/.
L'applicazione registra le sue immagini
# 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) descrive un file: la sua posizione file://, il tipo MIME, la dimensione e lo sha256, con il nome del file. role="input" contrassegna la foto che il sistema ha ricevuto; la miniatura è un output, il predefinito. current_case().media(...) registra entrambe come un unico artefatto media/v1, e records: [media/v1] in oloproof.yaml dichiara che il sistema lo registra. Nulla viene copiato: un controllo o un giudice rilegge ogni file in seguito e rifiuta quello i cui byte non corrispondono più al record.
Questa baseline ha due difetti. Le sue miniature sono larghe 320 pixel, e la sua didascalia nomina il colore del pixel centrale della foto, che è lo sfondo ogni volta che il prodotto sta da un lato. Ogni versione scrive le sue miniature sotto thumbs/<version>/, così un'esecuzione successiva non sovrascrive mai i file che un'esecuzione precedente ha registrato.
Il dataset
{"id": "p03", "input": {"photo": "photos/p03.png"}, "expected": {"photo": "photos/p03.png", "colour": "yellow", "shape": "triangle"}}input è ciò che il sistema riceve. expected.photo è il riferimento con cui viene confrontata la miniatura; colour e shape ci sono per una persona che legge il caso, e nessun valutatore li legge.
Scegliere i valutatori
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 misura ogni immagine di output: il formato dai suoi byte iniziali, la dimensione dalla sua intestazione, mai da ciò che il sistema ha dichiarato. image_matches_reference calcola l'hash della miniatura e della foto nominata da expected.photo, e passa quando gli hash differiscono al massimo in 10 dei loro 64 bit; un ridimensionamento o una ricompressione sposta un hash di pochi bit, un'altra immagine lo sposta di molti. Nessuno dei due chiama un modello, quindi entrambi decidono dalla prima esecuzione.
media: output allega alla richiesta del giudice le immagini di output del caso, ciascuna nominata nel suo prompt. Un giudice non decide nulla finché le etichette delle persone non mostrano che è d'accordo con loro, quindi la terza regola aspetta quel momento.
Il giudice sostitutivo
Un giudice a rubrica invia il suo prompt e le immagini allegate a un modello e ne rilegge {"pass": true|false, "rationale": "..."}. Qui il modello è judge_server.py, che parla la stessa API su 127.0.0.1 e non è un modello: decodifica la miniatura allegata, trova il suo colore più frequente diverso dal bianco e dal grigio dello sfondo, e fa passare una didascalia che lo nomina. Avvialo in un secondo terminale e lascialo in esecuzione:
python judge_server.py --port 8798La 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.80Ogni miniatura deve rispettare i requisiti e deve essere la sua foto. Almeno l'80% delle didascalie deve nominare il colore giusto, deciso solo quando il limite inferiore dell'intervallo supera l'80%.
Eseguilo
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 │Nessuna miniatura rispetta i requisiti, ogni miniatura è la sua foto, e 12 didascalie su 20 nominano il colore giusto. La regola del giudice è indecisa perché nessuno ha ancora misurato il giudice.
Esamina i fallimenti
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.'Il motivo di un controllo è una misurazione, contrassegnata measured; quello di un giudice sono parole sue, che nessuno ha verificato. Le foto e le miniature compaiono anche accanto a ogni verdetto nella pagina del caso nel workbench, dopo oloproof export RUN_ID, lette da questa cartella; uno spazio di lavoro ospitato le fa recuperare al tuo browser da oloproof collect e non le detiene mai (Immagini, audio e video).
Misura il giudice rispetto a una persona
Estrai un campione casuale dei casi dell'esecuzione in un foglio, senza i verdetti del giudice, fai guardare a una persona ogni miniatura e didascalia, e importa ciò che ha detto. Qui fill_labels.py sostituisce quella persona, copiando i suoi verdetti da 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.csvPoi misura il giudice rispetto a quelle etichette, e registra chi lo ha fatto:
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.000Un giudice che vede immagini viene misurato esattamente come uno che legge testo. In uno spazio di lavoro ospitato la coda di revisione mostra al revisore le immagini di ogni caso, recuperate dal suo browser dal tuo collector (Giudici).
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)Ora il giudice può decidere, e il 60% non basta.
Fai una modifica reale
app_v2.py crea miniature da 256 pixel e nomina il colore più frequente della foto diverso dallo sfondo, ovunque si trovi il prodotto. Copialo sopra app.py, imposta version: main-colour sotto system in oloproof.yaml, lascia il giudice in esecuzione, e:
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 │Il giudice è la stessa versione del valutatore che è stata validata, quindi i suoi verdetti contano.
Confronta il candidato con la baseline
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_zeroFacoltativo: un vero modello di visione
Questo passo lascia il percorso offline. Richiede un modello che legga immagini: in locale, Ollama con un modello di visione (ollama pull qwen2.5vl), senza chiave e senza costi; nel cloud, provider: anthropic o openai con api_key_env, fatturato per immagine oltre che per parola.
# 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.yamlQuesto giudica con il modello reale i casi che il tuo revisore ha etichettato e ne riporta l'accordo, senza adottarlo. Per adottarlo, mettilo in oloproof.yaml al posto del sostituto: un nuovo modello è una nuova versione del valutatore, quindi validalo rispetto alle etichette come sopra.
Risoluzione dei problemi
| Sintomo | Causa e rimedio |
|---|---|
| caption_names_colour tutto mancante | Il server del giudice non è in esecuzione o non è su base_url; oloproof inspect RUN_ID --failures mostra perché. |
| image_matches_reference arresta l'esecuzione, nominando Pillow | Installa l'extra media: pip install 'oloproof[media]'. |
| Un caso è mancante con media_unavailable | Un file registrato è stato spostato o cancellato dopo l'esecuzione. Conserva i file che un'esecuzione ha registrato per tutto il tempo in cui conservi l'esecuzione. |
| Un'esecuzione si arresta con is not the recorded content | Un file registrato è stato modificato dopo l'esecuzione. Un controllo o un giudice non legge mai byte diversi come se fossero quelli registrati; scrivi i file di ogni versione in un posto a sé. |
| evaluator_not_validated dopo la validazione | Hai cambiato il giudice (modello, endpoint, porta, rubrica, media) e creato una nuova versione. Valida quella. |
Limitazioni
- Il giudice sostitutivo conta i pixel. Dimostra il flusso di lavoro, non la qualità del giudizio.
- L'hash percettivo legge la composizione e la luce, non il colore: distingue una miniatura della foto da un'immagine vuota o da una scena diversa, non un prodotto da un altro nello stesso studio. La grafica piatta con ampie aree vuote lo sposta più delle fotografie.
- Venti etichette danno un intervallo di accordo ampio. Etichettane di più, a caso e alla cieca, per un giudice su cui fai affidamento.
- Audio e video funzionano allo stesso modo con altre impostazioni: vedi Immagini, audio e video.