Guías
Tutorial: imágenes, con comprobaciones y un juez que las ve
Evalúe una función que crea imágenes, aquí un generador de miniaturas de productos que también escribe un pie de foto. Compruebe las miniaturas con mediciones que no necesitan modelo, juzgue los pies de foto con un modelo que ve cada miniatura, mida ese juez frente a una persona y después publique un cambio real. Cada paso se ejecuta en esta máquina sin modelo y sin red, y un paso opcional lo sustituye por un modelo de visión real.
Lo que va a construir
Un servicio de catálogo recibe una foto de producto, guarda una miniatura y escribe un pie de foto de una línea que nombra el color del producto. Tres preguntas deciden si una versión puede publicarse:
- ¿Encaja cada miniatura en el catálogo: un formato web, como mucho 256 por 256 píxeles, como mucho 50 kB? Eso es una medición, que hace media_check a partir de los bytes de la miniatura.
- ¿Sigue siendo cada miniatura la foto del producto, y no una imagen en blanco, un recorte u otro producto? image_matches_reference la compara con la foto mediante un hash perceptual.
- ¿Nombra cada pie de foto el color que ve un comprador? Eso necesita ojos, así que a un juez de rúbrica se le muestra la miniatura.
Las fotos y las miniaturas se quedan en la carpeta del proyecto. El sistema las registra por referencia (media/v1): dónde está cada archivo, su tipo, su tamaño y su sha256. Términos como caso, ejecución, juez y gate se definen en Conceptos; todo lo relativo a archivos multimedia está en Imágenes, audio y vídeo.
Requisitos previos
- Python 3.11 o posterior, y Oloproof con su extra multimedia, que añade Pillow para comparar imágenes:
python3 -m venv .venv
. .venv/bin/activate
pip install 'oloproof[media]'- El proyecto de ejemplo, que viene con el paquete. Cópielo en un directorio nuevo y trabaje allí:
oloproof init --example media product-thumbnails
cd product-thumbnails- El puerto 8798 libre para el juez sustituto (si no, cámbielo en los dos sitios).
Todos los pasos hasta "Opcional: un modelo de visión real" son sin conexión y deterministas: sin clave de API, sin cuenta de proveedor, sin coste.
Los archivos
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 sheetEjecute cada comando desde product-thumbnails/.
La aplicación registra sus imágenes
# 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) describe un archivo: su ubicación file://, su tipo MIME, su tamaño y su sha256, con el nombre del archivo. role="input" marca la foto que recibió el sistema; la miniatura es una salida, el valor por defecto. current_case().media(...) registra ambas como un único artefacto media/v1, y records: [media/v1] en oloproof.yaml declara que el sistema lo registra. No se copia nada: una comprobación o un juez vuelve a leer cada archivo más tarde y rechaza uno cuyos bytes ya no coinciden con el registro.
Esta línea base tiene dos defectos. Sus miniaturas miden 320 píxeles de ancho, y su pie de foto nombra el color del píxel central de la foto, que es el fondo siempre que el producto está a un lado. Cada versión escribe sus miniaturas en thumbs/<version>/, así que una ejecución posterior nunca sobrescribe los archivos que registró una ejecución anterior.
El conjunto de datos
{"id": "p03", "input": {"photo": "photos/p03.png"}, "expected": {"photo": "photos/p03.png", "colour": "yellow", "shape": "triangle"}}input es lo que recibe el sistema. expected.photo es la referencia con la que se compara la miniatura; colour y shape están ahí para una persona que lea el caso, y ningún evaluador los lee.
Elegir los evaluadores
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 mide cada imagen de salida: el formato a partir de sus primeros bytes, el tamaño a partir de su cabecera, nunca a partir de lo que declaró el sistema. image_matches_reference calcula el hash de la miniatura y de la foto que nombra expected.photo, y aprueba cuando los hashes difieren en como mucho 10 de sus 64 bits; un redimensionado o una recompresión mueve un hash unos pocos bits, otra imagen lo mueve muchos. Ninguno de los dos llama a un modelo, así que ambos deciden desde la primera ejecución.
media: output adjunta las imágenes de salida del caso a la petición del juez, cada una nombrada en su prompt. Un juez no decide nada hasta que las etiquetas de personas muestran que coincide con ellas, así que la tercera regla espera a eso.
El juez sustituto
Un juez de rúbrica envía su prompt y las imágenes adjuntas a un modelo y lee la respuesta {"pass": true|false, "rationale": "..."}. Aquí el modelo es judge_server.py, que habla la misma API en 127.0.0.1 y no es un modelo: decodifica la miniatura adjunta, encuentra su color más frecuente aparte del blanco y el gris del fondo, y aprueba un pie de foto que lo nombra. Inícielo en un segundo terminal y déjelo en marcha:
python judge_server.py --port 8798La política
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.80Cada miniatura debe encajar y debe ser su foto. Al menos el 80% de los pies de foto deben nombrar el color correcto, algo que solo se decide cuando el límite inferior del intervalo supera el 80%.
Ejecútelo
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 │Ninguna miniatura encaja, cada miniatura es su foto, y 12 de 20 pies de foto nombran el color correcto. La regla del juez está sin decidir porque nadie ha medido todavía el juez.
Inspeccionar los fallos
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.'El motivo de una comprobación es una medición, marcada como measured; el de un juez son sus propias palabras, que nadie ha comprobado. Las fotos y las miniaturas también aparecen junto a cada veredicto en la página del caso en el workbench, después de oloproof export RUN_ID, leídas de esta carpeta; un espacio de trabajo alojado hace que su navegador las obtenga de oloproof collect y nunca las guarda (Imágenes, audio y vídeo).
Medir el juez frente a una persona
Extraiga una muestra aleatoria de los casos de la ejecución en una hoja, sin los veredictos del juez, haga que una persona mire cada miniatura y cada pie de foto, e importe lo que dijo. Aquí fill_labels.py sustituye a esa persona, copiando sus veredictos de 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.csvDespués mida el juez frente a esas etiquetas, y registre quién lo hizo:
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 juez que ve imágenes se mide exactamente igual que uno que lee texto. En un espacio de trabajo alojado, la cola de revisión muestra al revisor las imágenes de cada caso, que su navegador obtiene de su colector (Jueces).
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)El juez ya puede decidir, y el 60% no basta.
Hacer un cambio real
app_v2.py crea miniaturas de 256 píxeles y nombra el color más frecuente de la foto aparte del fondo, esté donde esté el producto. Cópielo sobre app.py, ponga version: main-colour bajo system en oloproof.yaml, mantenga el juez en marcha y:
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 │El juez es la misma versión del evaluador que se validó, así que sus veredictos cuentan.
Comparar el candidato con la línea base
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_zeroOpcional: un modelo de visión real
Este paso abandona el camino sin conexión. Necesita un modelo que lea imágenes: en local, Ollama con un modelo de visión (ollama pull qwen2.5vl), sin clave y sin coste; en la nube, provider: anthropic u openai con api_key_env, facturado por imagen además de por palabra.
# 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.yamlEso juzga con el modelo real los casos que etiquetó su revisor e informa de su concordancia, sin adoptarlo. Para adoptarlo, póngalo en oloproof.yaml en lugar del sustituto: un modelo nuevo es una nueva versión del evaluador, así que valídelo frente a las etiquetas como arriba.
Solución de problemas
| Síntoma | Causa y solución |
|---|---|
| caption_names_colour falta en todos los casos | El servidor del juez no está en marcha o no está en base_url; oloproof inspect RUN_ID --failures muestra por qué. |
| image_matches_reference detiene la ejecución y nombra Pillow | Instale el extra multimedia: pip install 'oloproof[media]'. |
| Un caso falta con media_unavailable | Un archivo registrado se movió o se borró después de la ejecución. Conserve los archivos que registró una ejecución mientras conserve la ejecución. |
| Una ejecución se detiene con is not the recorded content | Un archivo registrado cambió después de la ejecución. Una comprobación o un juez nunca lee bytes distintos como si fueran los registrados; escriba los archivos de cada versión en su propio lugar. |
| evaluator_not_validated después de validar | Cambió el juez (modelo, endpoint, puerto, rúbrica, media) y creó una nueva versión. Valide esa. |
Limitaciones
- El juez sustituto cuenta píxeles. Demuestra el flujo de trabajo, no la calidad del juicio.
- El hash perceptual lee la composición y la luz, no el color: distingue una miniatura de la foto de una imagen en blanco o de una escena distinta, no un producto de otro en el mismo estudio. Los gráficos planos con grandes zonas en blanco lo mueven más que las fotografías.
- Veinte etiquetas dan un intervalo de concordancia ancho. Etiquete más, al azar y a ciegas, para un juez del que dependa.
- El audio y el vídeo funcionan de la misma manera con otros ajustes: consulte Imágenes, audio y vídeo.