Skip to content

가이드

진행 상황과 동시성

운영 중인 모델에 대해 스위트를 실행하면 몇 분이 걸리며, 그 대부분은 모델을 기다리는 시간입니다. 이 페이지는 Oloproof가 동시에 몇 개의 호출을 진행시키는지, 호출이 진행되는 동안 무엇을 보여 주는지, 그리고 결정하지 못한 규칙에 결론을 내려면 얼마나 더 실행해야 하는지 알아내는 방법을 다룹니다.

동시성

oloproof.yaml의 concurrency:는 동시에 진행되는 호출 수를 제한합니다.

concurrency:
  system: 4
  judge: 4

system은 테스트 대상 시스템에 대한 호출을 제한하며 기본값은 8입니다. judge는 LLM 심사 모델에 대한 호출을 제한하며 기본값은 4입니다. 둘을 분리한 것은 보통 서로 다른 속도 제한 뒤에 있기 때문입니다.

호출당 0.1초가 걸리는 시스템에 대해 케이스 120개를 실행한 경우입니다.

system:경과 시간
113.4s
161.7s
변경 없이 재실행0.4s

마지막 행은 동시성이 아니라 캐시 덕분입니다. 모든 실행 결과가 재사용되었습니다. 동시성은 시스템의 정체성에 속하지 않으므로, 이를 바꿔도 저장된 것이 무효화되지 않습니다.

모든 케이스는 각각 하나의 호출입니다. Oloproof는 케이스를 제공자의 배치 API로 묶지 않으므로, 제공자의 배치 할인은 Oloproof를 통해서는 받을 수 없습니다. 실행을 빠르게 하는 것은 동시성과 캐시입니다.

재시도

심사 제공자나 HTTP 시스템이 429 또는 5xx로 응답하거나, 타임아웃되거나, 연결이 끊기면 백오프를 두고 최대 네 번까지 재시도하며, Retry-After 헤더가 요청한 것보다 빨리 재시도하지는 않습니다. 호출 가능 시스템은 oloproof의 TransientError를 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의 기본값은 False입니다. 이것이 없으면 오류는 케이스에 기록되고, 그 케이스는 재시도되지 않고 누락으로 처리됩니다. 케이스 30개 각각에 대해 첫 호출에서 그렇게 예외를 발생시키고 두 번째 호출에서 답한 시스템의 결과입니다.

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

같은 시스템에서 retryable=True를 제거한 경우입니다.

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

실행 중에 보여 주는 것

터미널에서 oloproof run은 표준 오류에 실시간 화면을 다시 그립니다. 완료된 케이스, 캐시 적중, 오류, 그리고 각 이진 지표의 잠정 추정치입니다. 한 프레임은 다음과 같습니다.

                    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.

캡션이 규칙입니다. 잠정 구간은 지금까지 끝난 것에 대한 Wilson 구간으로, 지켜보기에는 유용하지만 결정의 근거로 삼을 것은 아닙니다. 케이스가 도착할 때마다 다시 계산되며, 좋아 보일 때까지 반복해서 확인한 구간은 더 이상 95% 구간이 아닙니다. 이를 근거로 실행을 일찍 멈추는 것은 없습니다. 결정은 완료된 근거에 대해, 정책이 지정한 방법으로 한 번 내려집니다.

CI처럼 터미널이 아닌 환경에서는 실시간 화면을 그리지 않고, 마지막에 표를 한 번 출력합니다.

이벤트 스트림

--json은 같은 진행 상황을 표준 출력에 한 줄당 JSON 객체 하나로 쓰고, 표는 표준 오류에 씁니다.

oloproof run --json

케이스 120개의 실행을 run.ndjson에 저장하고 jq -r '.type' run.ndjson | sort | uniq -c로 세면 다음이 나옵니다.

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

각 이벤트에는 실행 ID와 타임스탬프가 들어 있습니다.

{"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에는 각 이진 지표의 누적 Wilson 구간이 들어 있습니다.

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]

파이프를 일찍 닫지 마십시오. head처럼 처음 몇 줄만 읽고 멈추는 리더는 실행이 마지막 케이스를 저장하기 전에 실행을 끝내며, 그 실행은 RUN_ERROR/PARTIAL로 기록됩니다.

얼마나 더 있어야 결정되는가

INSUFFICIENT_EVIDENCE로 읽히는 규칙은 실패한 것이 아닙니다. 스위트가 너무 작아 결과를 임계값과 구분할 수 없었던 것입니다. oloproof plan은 스위트가 얼마나 더 커야 하는지를, 실행이 이미 소비한 것을 바탕으로 비용을 산정해 알려 줍니다. 케이스 18개짜리 examples/support_bot/의 경우입니다.

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

실행 규칙의 표본 크기 산정은 통과/실패 비율에만 허용됩니다. 평균에 대해서는 추측하는 대신 그렇다고 밝힙니다.

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

대신 비교 ID를 주면 비교의 규칙을 계획하며, 플래그가 필요 없습니다.

다음 단계