Guias
Tutorial: imagens, com verificações e um juiz que as vê
Avalie uma função que cria imagens, aqui um gerador de miniaturas de produtos que também escreve uma legenda. Verifique as miniaturas com medições que não precisam de modelo, julgue as legendas com um modelo que vê cada miniatura, meça esse juiz contra uma pessoa e depois lance uma mudança real. Todo passo roda nesta máquina sem modelo e sem rede, e um passo opcional troca o juiz por um modelo de visão real.
O que você vai construir
Um serviço de catálogo recebe a foto de um produto, salva uma miniatura e escreve uma legenda de uma linha que nomeia a cor do produto. Três perguntas decidem se uma versão pode ser lançada:
- Cada miniatura cabe no catálogo: um formato da web, no máximo 256 por 256 pixels, no máximo 50 kB? Isso é uma medição, feita por media_check a partir dos bytes da miniatura.
- Cada miniatura ainda é a foto do produto, e não uma imagem em branco, um recorte ou outro produto? image_matches_reference a compara com a foto por hash perceptual.
- Cada legenda nomeia a cor que um comprador vê? Isso precisa de olhos, então um juiz de rubrica recebe a miniatura.
As fotos e as miniaturas ficam na pasta do projeto. O sistema as registra por referência (media/v1): onde cada arquivo está, o seu tipo, tamanho e sha256. Termos como caso, execução, juiz e gate são definidos em Conceitos; tudo sobre mídia está em Imagens, áudio e vídeo.
Pré-requisitos
- Python 3.11 ou posterior, e o Oloproof com o seu extra de mídia, que adiciona o Pillow para comparar imagens:
python3 -m venv .venv
. .venv/bin/activate
pip install 'oloproof[media]'- O projeto de exemplo, que vem com o pacote. Copie-o para um novo diretório e trabalhe nele:
oloproof init --example media product-thumbnails
cd product-thumbnails- A porta 8798 livre para o juiz substituto (se não estiver, mude-a nos dois lugares).
Todo passo até "Opcional: um modelo de visão real" é offline e determinístico: nenhuma chave de API, nenhuma conta de provedor, nenhum custo.
Os arquivos
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 sheetExecute todos os comandos a partir de product-thumbnails/.
A aplicação registra as suas imagens
# 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) descreve um arquivo: a sua localização file://, o tipo MIME, o tamanho e o sha256, com o nome do arquivo. role="input" marca a foto que o sistema recebeu; a miniatura é uma saída, o padrão. current_case().media(...) registra as duas como um artefato media/v1, e records: [media/v1] no oloproof.yaml declara que o sistema o registra. Nada é copiado: uma verificação ou um juiz lê cada arquivo de volta mais tarde e recusa um cujos bytes não correspondam mais ao registro.
Esta linha de base tem dois defeitos. As suas miniaturas têm 320 pixels de largura, e a sua legenda nomeia a cor do pixel central da foto, que é o fundo sempre que o produto está de um lado. Cada versão grava as suas miniaturas em thumbs/<version>/, então uma execução posterior nunca sobrescreve os arquivos que uma execução anterior registrou.
O dataset
{"id": "p03", "input": {"photo": "photos/p03.png"}, "expected": {"photo": "photos/p03.png", "colour": "yellow", "shape": "triangle"}}input é o que o sistema recebe. expected.photo é a referência com que a miniatura é comparada; colour e shape estão ali para uma pessoa que lê o caso, e nenhum avaliador os lê.
Escolhendo os avaliadores
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 mede cada imagem de saída: o formato pelos seus bytes iniciais, o tamanho pelo seu cabeçalho, nunca pelo que o sistema declarou. image_matches_reference calcula o hash da miniatura e da foto que expected.photo nomeia, e passa quando os hashes diferem em no máximo 10 dos seus 64 bits; um redimensionamento ou uma recompressão desloca um hash em poucos bits, outra imagem o desloca em muitos. Nenhum dos dois chama um modelo, então os dois decidem desde a primeira execução.
media: output anexa as imagens de saída do caso à requisição do juiz, cada uma nomeada no seu prompt. Um juiz não decide nada até que os rótulos das pessoas mostrem que ele concorda com elas, então a terceira regra espera por isso.
O juiz substituto
Um juiz de rubrica envia o seu prompt e as imagens anexadas a um modelo e lê de volta {"pass": true|false, "rationale": "..."}. Aqui o modelo é judge_server.py, que fala a mesma API em 127.0.0.1 e não é um modelo: ele decodifica a miniatura anexada, encontra a sua cor mais comum além do branco e do cinza do fundo, e aprova uma legenda que a nomeia. Inicie-o em um segundo terminal e deixe-o rodando:
python judge_server.py --port 8798A 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.80Toda miniatura precisa caber e precisa ser a sua foto. Pelo menos 80% das legendas precisam nomear a cor certa, o que só é decidido quando o limite inferior do intervalo supera 80%.
Execute
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 │Nenhuma miniatura cabe, toda miniatura é a sua foto, e 12 de 20 legendas nomeiam a cor certa. A regra do juiz fica indecidida porque ninguém mediu o juiz ainda.
Inspecione as falhas
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.'O motivo de uma verificação é uma medição, marcada como measured; o de um juiz são as suas próprias palavras, que ninguém verificou. As fotos e as miniaturas também aparecem ao lado de cada veredito na página do caso no workbench, depois de oloproof export RUN_ID, lidas desta pasta; um espaço de trabalho hospedado faz o seu navegador buscá-las do oloproof collect e nunca as guarda (Imagens, áudio e vídeo).
Meça o juiz contra uma pessoa
Sorteie uma amostra aleatória dos casos da execução em uma planilha, sem os vereditos do juiz, peça a uma pessoa que olhe cada miniatura e legenda, e importe o que ela disse. Aqui fill_labels.py faz o papel dessa pessoa, copiando os vereditos dela 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.csvDepois meça o juiz contra esses rótulos, e registre quem o fez:
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.000Um juiz que vê imagens é medido exatamente como um que lê texto. Em um espaço de trabalho hospedado, a fila de revisão mostra as imagens de cada caso ao revisor, buscadas pelo navegador dele a partir do seu coletor (Juízes).
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)O juiz agora pode decidir, e 60% não basta.
Faça uma mudança real
app_v2.py cria miniaturas de 256 pixels e nomeia a cor mais comum da foto além da do fundo, onde quer que o produto esteja. Copie-o por cima de app.py, defina version: main-colour em system no oloproof.yaml, mantenha o juiz rodando, 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 │O juiz é a mesma versão do avaliador que foi validada, então os seus vereditos contam.
Compare o candidato com a linha de 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: um modelo de visão real
Este passo sai do caminho offline. Ele precisa de um modelo que leia imagens: localmente, o Ollama com um modelo de visão (ollama pull qwen2.5vl), sem chave e sem custo; na nuvem, provider: anthropic ou openai com api_key_env, cobrado por imagem assim como por palavra.
# 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.yamlIsso julga com o modelo real os casos que o seu revisor rotulou e informa a sua concordância, sem adotá-lo. Para adotá-lo, coloque-o em oloproof.yaml no lugar do substituto: um novo modelo é uma nova versão do avaliador, então valide-o contra os rótulos, como acima.
Solução de problemas
| Sintoma | Causa e correção |
|---|---|
| caption_names_colour todo faltante | O servidor do juiz não está rodando ou não está em base_url; oloproof inspect RUN_ID --failures mostra por quê. |
| image_matches_reference interrompe a execução, nomeando o Pillow | Instale o extra de mídia: pip install 'oloproof[media]'. |
| Um caso fica faltante com media_unavailable | Um arquivo registrado foi movido ou apagado depois da execução. Guarde os arquivos que uma execução registrou enquanto guardar a execução. |
| Uma execução para com is not the recorded content | Um arquivo registrado foi alterado depois da execução. Uma verificação ou um juiz nunca lê bytes diferentes como se fossem os registrados; grave os arquivos de cada versão em um lugar próprio. |
| evaluator_not_validated depois de validar | Você mudou o juiz (modelo, endpoint, porta, rubrica, media) e criou uma nova versão. Valide essa. |
Limitações
- O juiz substituto conta pixels. Ele demonstra o fluxo de trabalho, não a qualidade do julgamento.
- O hash perceptual lê composição e luz, não cor: ele distingue uma miniatura da foto de uma imagem em branco ou de uma cena diferente, não um produto de outro no mesmo estúdio. Gráficos planos com grandes áreas em branco o deslocam mais do que fotografias.
- Vinte rótulos dão um intervalo de concordância largo. Rotule mais, ao acaso e às cegas, para um juiz em que você confia.
- Áudio e vídeo funcionam do mesmo jeito com outras configurações: veja Imagens, áudio e vídeo.