Guias
Progresso e concorrência
Uma suíte contra um modelo real leva minutos, e a maior parte deles é gasta esperando o modelo. Esta página trata de quantas chamadas o Oloproof mantém em andamento, do que ele mostra enquanto elas rodam e de como descobrir quanto mais de execução resolveria uma regra que não decidiu.
Concorrência
concurrency: em oloproof.yaml limita quantas chamadas ficam em andamento ao mesmo tempo:
concurrency:
system: 4
judge: 4system limita as chamadas ao sistema sob teste e tem 8 como padrão. judge limita as chamadas a juízes LLM e tem 4 como padrão. Eles são separados porque os dois normalmente ficam atrás de limites de taxa diferentes.
Cento e vinte casos contra um sistema que leva um décimo de segundo por chamada:
| system: | Tempo decorrido |
|---|---|
| 1 | 13,4 s |
| 16 | 1,7 s |
| sem alteração, reexecutado | 0,4 s |
A última linha é o cache, não a concorrência: todas as execuções foram reaproveitadas. A concorrência não faz parte da identidade de um sistema, então alterá-la nunca invalida o que está armazenado.
Cada caso é uma chamada própria. O Oloproof não agrupa casos na API de lote de um provedor, então o desconto de lote de um provedor não está disponível por meio dele; a concorrência e o cache são o que tornam uma execução mais rápida.
Novas tentativas
Um provedor de juiz ou um sistema HTTP que responde 429 ou um 5xx, estoura o tempo limite ou derruba a conexão é tentado de novo com backoff, até quatro tentativas, e nunca antes do que um cabeçalho Retry-After pede. Um sistema callable adere a isso levantando TransientError de oloproof com retryable=True:
from oloproof import TransientError, system
@system(name="example-support-bot", version="1")
def answer(case):
...
raise TransientError("provider timed out", retryable=True)retryable tem False como padrão. Sem ele, o erro é registrado no caso, que então fica ausente em vez de ser tentado de novo. Um sistema que levantou a exceção dessa forma na primeira chamada de cada um de trinta casos, e respondeu na segunda:
│ exact_label │ 100.0% │ [88.4%, 100.0%] │ 30 / 30 observed · 0 missing · 0 excluded │O mesmo sistema com retryable=True removido:
│ exact_label │ │ [0.0%, 100.0%] │ 0 / 0 observed · 30 missing · 0 excluded │O que uma execução mostra enquanto roda
Em um terminal, oloproof run redesenha uma visualização ao vivo na saída de erro padrão: casos concluídos, acertos de cache, erros e uma estimativa provisória para cada métrica binária. Um quadro:
56/120 cases · 0 cached · 0 errors
┏━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Metric ┃ Estimate ┃ Provisional interval ┃ Cases ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ exact_label │ 89.8% │ [78.2%, 95.6%] │ 49 observed · 0 missing │
└─────────────┴──────────┴──────────────────────┴─────────────────────────┘
Provisional Wilson estimates over finished cases; not a decision.A legenda é a regra. Um intervalo provisório é um intervalo de Wilson sobre o que já terminou, o que é útil de acompanhar e não algo com base no qual decidir: ele é recalculado à medida que os casos chegam, e um intervalo verificado repetidamente até parecer bom deixa de ser um intervalo de 95%. Nada interrompe uma execução antecipadamente com base nele. A decisão é tomada uma única vez, sobre a evidência concluída, com o método que a política nomeia.
Fora de um terminal, como no CI, a visualização ao vivo não é desenhada e as tabelas são impressas uma única vez, no final.
O fluxo de eventos
--json grava o mesmo progresso como um objeto JSON por linha na saída padrão, e as tabelas na saída de erro padrão:
oloproof run --jsonUma execução de 120 casos, salva em run.ndjson e contada com jq -r '.type' run.ndjson | sort | uniq -c, emite:
120 case_executed
120 case_judged
11 provisional_metrics
1 run_finished
2 run_phase_changed
1 run_startedCada um traz o id da execução e um timestamp:
{"run_id":"run_01M3C3ZNXPVQJCDF31DJSBXBHC","timestamp":"2026-09-25T11:07:37.782628Z","type":"run_started","suite_digest":"sha256:c6ae32f25d38ddac175f688c15c40991c1e0ec5348f32bfabd9c493a3f688c28","cases":120}
{"run_id":"run_01M3C3ZNXPVQJCDF31DJSBXBHC","timestamp":"2026-09-25T11:07:37.885640Z","type":"case_executed","scenario_id":"q000","status":"OK","from_cache":false,"latency_ms":102.11420899941004}
{"run_id":"run_01M3C3ZNXPVQJCDF31DJSBXBHC","timestamp":"2026-09-25T11:07:37.885667Z","type":"case_judged","scenario_id":"q000","criterion":"exact_label","status":"OK","passed":true,"score":null,"from_cache":false}
{"run_id":"run_01M3C3ZNXPVQJCDF31DJSBXBHC","timestamp":"2026-09-25T11:07:41.362779Z","type":"run_finished","status":"DECIDED","completeness":"COMPLETE","exit_code":0}provisional_metrics traz o intervalo de Wilson corrente para cada métrica binária:
jq -c 'select(.type == "provisional_metrics") | [.cases_done, .metrics[0].estimate, .metrics[0].wilson_lower, .metrics[0].wilson_upper]' run.ndjson[1,1.0,0.20654931411298355,1.0]
[13,0.8461538461538461,0.5776536895684791,0.9567418216820717]
[25,0.88,0.7004420606159933,0.9583318285288502]
[37,0.8918918918918919,0.7529146844205937,0.9571481006263428]Não feche o pipe antes da hora. Um leitor que para depois das primeiras linhas, como head, encerra a execução antes que ela armazene os seus últimos casos, e a execução é registrada como RUN_ERROR/PARTIAL.
Quanto mais seria necessário para decidir
Uma regra que mostra INSUFFICIENT_EVIDENCE não falhou; a suíte era pequena demais para separar o resultado do limiar. oloproof plan diz o quanto maior ela precisaria ser, com o preço calculado a partir do que a execução já gastou. Para o examples/support_bot/ de dezoito casos:
oloproof plan RUN_ID --runRun run_01M3C3WS0SBTFAG55M7ECM1EZ4
observed 18 cases
exact-label-floor: about 1614 more cases would decide it, if the observed rate holds (1632 in total)
time <1s – 27s
tokens none reported by this run's providers
assuming the cases to come resemble the 18 already run
cases run one after another; concurrency divides the time and not the costO dimensionamento de uma regra de execução só é admitido para taxas de aprovação/reprovação. Em uma média, o plano diz isso em vez de chutar:
Run run_01M3C3W5YWJY65X9YM6N02F3W4: no sample size can be computed for a rule that did not decide.
error-budget: sizing a run rule is admitted for binary rates only, and days_error is a MEAN metric: a run stores its summary, not the per-case values sizing one would needSe receber o id de uma comparação, ele planeja as regras da comparação e não precisa de flag.
Próximos passos
- Registrando o que um sistema fez trata dos números de latência e de tokens a partir
dos quais essas estimativas são calculadas.
- Executando no CI trata da leitura do fluxo de eventos a partir de um script.