Guides
Tutoriel : des images, avec des vérifications et un juge qui les voit
Évaluez une fonction qui produit des images, ici un générateur de vignettes de produits qui écrit aussi une légende. Vérifiez les vignettes avec des mesures qui ne demandent aucun modèle, jugez les légendes avec un modèle qui voit chaque vignette, mesurez ce juge par rapport à une personne, puis livrez une vraie modification. Chaque étape s’exécute sur cette machine sans modèle ni réseau, et une étape facultative fait intervenir un vrai modèle de vision.
Ce que vous allez construire
Un service de catalogue prend une photo de produit, enregistre une vignette et écrit une légende d’une ligne qui nomme la couleur du produit. Trois questions décident si une version peut être livrée :
- Chaque vignette convient-elle au catalogue : un format web, au plus 256 par 256 pixels, au plus 50 kB ? C’est une mesure, faite par media_check à partir des octets de la vignette.
- Chaque vignette est-elle toujours la photo du produit, et non une image vide, un recadrage ou un autre produit ? image_matches_reference la compare à la photo par hachage perceptuel.
- Chaque légende nomme-t-elle la couleur que voit un acheteur ? Il faut des yeux pour cela, donc un juge à grille voit la vignette.
Les photos et les vignettes restent dans le dossier du projet. Le système les enregistre par référence (media/v1) : où se trouve chaque fichier, son type, sa taille et son sha256. Les termes tels que cas, exécution, juge et porte sont définis dans Concepts ; tout ce qui concerne les médias est dans Images, audio et vidéo.
Prérequis
- Python 3.11 ou ultérieur, et Oloproof avec son extra media, qui ajoute Pillow pour comparer les images :
python3 -m venv .venv
. .venv/bin/activate
pip install 'oloproof[media]'- Le projet d’exemple, livré avec le paquet. Copiez-le dans un nouveau répertoire et travaillez-y :
oloproof init --example media product-thumbnails
cd product-thumbnails- Le port 8798 libre pour le juge de substitution (sinon, changez-le aux deux endroits).
Chaque étape jusqu’à « Facultatif : un vrai modèle de vision » est hors ligne et déterministe : aucune clé d’API, aucun compte de fournisseur, aucun coût.
Les fichiers
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 sheetLancez chaque commande depuis product-thumbnails/.
L’application enregistre ses images
# 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) décrit un fichier : son emplacement file://, son type MIME, sa taille et son sha256, nommé d’après le fichier. role="input" marque la photo que le système a reçue ; la vignette est une sortie, le rôle par défaut. current_case().media(...) enregistre les deux comme un seul artefact media/v1, et records: [media/v1] dans oloproof.yaml déclare que le système l’enregistre. Rien n’est copié : une vérification ou un juge relit chaque fichier plus tard et refuse celui dont les octets ne correspondent plus à l’enregistrement.
Cette référence a deux défauts. Ses vignettes font 320 pixels de large, et sa légende nomme la couleur du pixel central de la photo, qui est le fond chaque fois que le produit est sur un côté. Chaque version écrit ses vignettes sous thumbs/<version>/, donc une exécution ultérieure n’écrase jamais les fichiers qu’une exécution antérieure a enregistrés.
Le jeu de données
{"id": "p03", "input": {"photo": "photos/p03.png"}, "expected": {"photo": "photos/p03.png", "colour": "yellow", "shape": "triangle"}}input est ce que reçoit le système. expected.photo est la référence à laquelle la vignette est comparée ; colour et shape sont là pour une personne qui lit le cas, et aucun évaluateur ne les lit.
Choisir les évaluateurs
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 mesure chaque image de sortie : le format à partir de ses premiers octets, la taille à partir de son en-tête, jamais à partir de ce que le système a déclaré. image_matches_reference hache la vignette et la photo que nomme expected.photo, et réussit quand les hachages diffèrent d’au plus 10 de leurs 64 bits ; un redimensionnement ou une recompression déplace un hachage de quelques bits, une autre image le déplace de beaucoup. Aucun des deux n’appelle de modèle, donc les deux décident dès la première exécution.
media: output joint les images de sortie du cas à la requête du juge, chacune nommée dans son prompt. Un juge ne décide rien tant que les étiquettes de personnes ne montrent pas qu’il est d’accord avec elles, donc la troisième règle attend cela.
Le juge de substitution
Un juge à grille envoie son prompt et les images jointes à un modèle et lit en retour {"pass": true|false, "rationale": "..."}. Ici le modèle est judge_server.py, qui parle la même API sur 127.0.0.1 et n’est pas un modèle : il décode la vignette jointe, trouve sa couleur la plus fréquente en dehors du blanc et du gris du fond, et fait passer une légende qui la nomme. Lancez-le dans un second terminal et laissez-le tourner :
python judge_server.py --port 8798La politique
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.80Chaque vignette doit convenir et doit être sa photo. Au moins 80 % des légendes doivent nommer la bonne couleur, ce qui n’est décidé que lorsque la borne inférieure de l’intervalle dépasse 80 %.
L’exécuter
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 │Aucune vignette ne convient, chaque vignette est sa photo, et 12 légendes sur 20 nomment la bonne couleur. La règle du juge n’est pas tranchée parce que personne n’a encore mesuré le juge.
Inspecter les échecs
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.'La raison d’une vérification est une mesure, marquée measured ; celle d’un juge est formulée avec ses propres mots, que personne n’a vérifiés. Les photos et les vignettes apparaissent aussi à côté de chaque verdict sur la page du cas dans l’atelier, après oloproof export RUN_ID, lues dans ce dossier ; un espace de travail hébergé les fait récupérer par votre navigateur auprès de oloproof collect et ne les détient jamais (Images, audio et vidéo).
Mesurer le juge par rapport à une personne
Tirez un échantillon aléatoire des cas de l’exécution dans une feuille, sans les verdicts du juge, faites regarder chaque vignette et chaque légende par une personne, et importez ce qu’elle a dit. Ici, fill_labels.py remplace cette personne, en copiant ses verdicts depuis 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.csvMesurez ensuite le juge par rapport à ces étiquettes, et enregistrez qui l’a fait :
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 juge qui voit des images est mesuré exactement comme un juge qui lit du texte. Dans un espace de travail hébergé, la file de relecture montre les images de chaque cas au relecteur, récupérées par son navigateur auprès de votre collecteur (Juges).
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)Le juge peut maintenant décider, et 60 % ne suffit pas.
Faire une vraie modification
app_v2.py produit des vignettes de 256 pixels et nomme la couleur la plus fréquente de la photo en dehors du fond, où que soit le produit. Copiez-le par-dessus app.py, réglez version: main-colour sous system dans oloproof.yaml, laissez le juge tourner, et :
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 │Le juge est la même version d’évaluateur que celle qui a été validée, donc ses verdicts comptent.
Comparer le candidat à la référence
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_zeroFacultatif : un vrai modèle de vision
Cette étape quitte le chemin hors ligne. Elle demande un modèle qui lit les images : en local, Ollama avec un modèle de vision (ollama pull qwen2.5vl), sans clé et sans coût ; dans le cloud, provider: anthropic ou openai avec api_key_env, facturé par image comme par mot.
# 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.yamlCela juge avec le vrai modèle les cas que votre relecteur a étiquetés et indique son accord, sans l’adopter. Pour l’adopter, mettez-le dans oloproof.yaml à la place du juge de substitution : un nouveau modèle est une nouvelle version d’évaluateur, donc validez-le par rapport aux étiquettes comme ci-dessus.
Dépannage
| Symptôme | Cause et correction |
|---|---|
| caption_names_colour entièrement manquant | Le serveur du juge ne tourne pas ou n’est pas sur base_url ; oloproof inspect RUN_ID --failures montre pourquoi. |
| image_matches_reference arrête l’exécution en nommant Pillow | Installez l’extra media : pip install 'oloproof[media]'. |
| Un cas est manquant avec media_unavailable | Un fichier enregistré a été déplacé ou supprimé après l’exécution. Gardez les fichiers qu’une exécution a enregistrés aussi longtemps que vous gardez l’exécution. |
| Une exécution s’arrête avec is not the recorded content | Un fichier enregistré a été modifié après l’exécution. Une vérification ou un juge ne lit jamais des octets différents comme s’ils étaient ceux enregistrés ; écrivez les fichiers de chaque version à leur propre emplacement. |
| evaluator_not_validated après validation | Vous avez modifié le juge (modèle, point de terminaison, port, grille, media) et créé une nouvelle version. Validez celle-ci. |
Limites
- Le juge de substitution compte des pixels. Il montre le déroulement, pas la qualité du jugement.
- Le hachage perceptuel lit la composition et la lumière, pas la couleur : il distingue une vignette de la photo d’une image vide ou d’une autre scène, mais pas un produit d’un autre photographié dans le même studio. Les graphiques en aplats avec de grandes zones vides le font bouger davantage que les photographies.
- Vingt étiquettes donnent un intervalle d’accord large. Étiquetez-en davantage, au hasard et à l’aveugle, pour un juge sur lequel vous comptez.
- L’audio et la vidéo fonctionnent de la même manière avec d’autres réglages : voyez Images, audio et vidéo.