Skip to content

Guides

Progression et concurrence

Une suite exécutée face à un modèle en service prend plusieurs minutes, dont la plupart passées à attendre le modèle. Cette page explique combien d’appels Oloproof garde en vol, ce qu’il vous montre pendant qu’ils s’exécutent, et comment savoir combien d’exécution supplémentaire permettrait de trancher une règle qui ne l’a pas été.

Concurrence

concurrency: dans oloproof.yaml borne le nombre d’appels en vol simultanément :

concurrency:
  system: 4
  judge: 4

system borne les appels au système testé et vaut 8 par défaut. judge borne les appels aux juges LLM et vaut 4 par défaut. Ils sont distincts parce que les deux se trouvent généralement derrière des limites de débit différentes.

Cent vingt cas face à un système qui met un dixième de seconde par appel :

system:Temps écoulé
113,4 s
161,7 s
inchangé, relancé0,4 s

La dernière ligne relève du cache, pas de la concurrence : chaque exécution a été réutilisée. La concurrence ne fait pas partie de l’identité d’un système ; la modifier n’invalide donc jamais ce qui est stocké.

Chaque cas est un appel distinct. Oloproof ne regroupe pas les cas dans l’API de traitement par lots d’un fournisseur ; la remise d’un fournisseur sur le traitement par lots n’est donc pas accessible par son intermédiaire. Ce sont la concurrence et le cache qui rendent une exécution plus rapide.

Nouvelles tentatives

Un fournisseur de juge ou un système HTTP qui répond 429 ou 5xx, dépasse son délai ou coupe la connexion est retenté avec un délai croissant, jusqu’à quatre tentatives, et jamais plus tôt que ne le demande un en-tête Retry-After. Un système appelable s’y inscrit en levant TransientError depuis oloproof avec 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 vaut False par défaut. Sans lui, l’erreur est enregistrée sur le cas, qui est alors manquant plutôt que retenté. Un système qui a levé une telle exception au premier appel pour chacun de trente cas, puis a répondu au second :

│ exact_label │ 100.0%   │ [88.4%, 100.0%] │ 30 / 30 observed · 0 missing · 0 excluded │

Le même système, sans retryable=True :

│ exact_label │          │ [0.0%, 100.0%] │ 0 / 0 observed · 30 missing · 0 excluded │

Ce qu’une exécution montre pendant qu’elle s’exécute

Dans un terminal, oloproof run redessine une vue en direct sur la sortie d’erreur standard : cas terminés, succès de cache, erreurs, et une estimation provisoire pour chaque métrique binaire. Une image :

                    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.

La légende est la règle. Un intervalle provisoire est un intervalle de Wilson sur ce qui est terminé, ce qui est utile à surveiller mais pas de quoi décider : il est recalculé à mesure que les cas arrivent, et un intervalle consulté de manière répétée jusqu’à ce qu’il semble bon n’est plus un intervalle à 95 %. Rien n’arrête une exécution prématurément sur cette base. La décision est prise une seule fois, sur les preuves complètes, avec la méthode que nomme la politique.

Hors d’un terminal, par exemple en CI, la vue en direct n’est pas dessinée et les tableaux sont affichés une seule fois, à la fin.

Le flux d’événements

--json écrit la même progression sous forme d’un objet JSON par ligne sur la sortie standard, et les tableaux sur la sortie d’erreur standard :

oloproof run --json

Une exécution de 120 cas, enregistrée dans run.ndjson et comptée avec jq -r '.type' run.ndjson | sort | uniq -c, émet :

 120 case_executed
 120 case_judged
  11 provisional_metrics
   1 run_finished
   2 run_phase_changed
   1 run_started

Chacun porte l’identifiant de l’exécution et un horodatage :

{"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 porte l’intervalle de Wilson courant pour chaque métrique binaire :

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]

Ne fermez pas le tube prématurément. Un lecteur qui s’arrête après les premières lignes, comme head, termine l’exécution avant qu’elle ne stocke ses derniers cas, et l’exécution est enregistrée comme RUN_ERROR/PARTIAL.

Combien de plus permettrait de trancher

Une règle qui donne INSUFFICIENT_EVIDENCE n’a pas échoué ; la suite était trop petite pour distinguer le résultat du seuil. oloproof plan indique de combien elle devrait grandir, chiffré à partir de ce que l’exécution a déjà dépensé. Pour examples/support_bot/ et ses dix-huit cas :

oloproof plan RUN_ID --run
Run 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 cost

Le dimensionnement d’une règle d’exécution n’est admis que pour les taux de réussite/échec. Sur une moyenne, le plan le dit plutôt que de deviner :

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 need

Avec un identifiant de comparaison à la place, il planifie les règles de la comparaison, et aucune option n’est nécessaire.

Pour aller plus loin

à partir desquels ces estimations sont chiffrées.

  • Exécution en CI traite de la lecture du flux d’événements depuis un script.