الأدلة الإرشادية
التقدُّم والتزامن
تستغرق مجموعة اختبار تُشغَّل على نموذج حيّ دقائق، ويُقضى معظمها في انتظار النموذج. تشرح هذه الصفحة عدد الاستدعاءات التي يُبقيها Oloproof قيد التنفيذ في آنٍ واحد، وما يعرضه لك أثناء تنفيذها، وكيف تعرف مقدار التشغيل الإضافي الذي يكفي لحسم قاعدة لم تُحسَم.
التزامن
يحدّ concurrency: في oloproof.yaml عدد الاستدعاءات الجارية في آنٍ واحد:
concurrency:
system: 4
judge: 4يحدّ system الاستدعاءات الموجَّهة إلى النظام قيد الاختبار، وقيمته الافتراضية 8. ويحدّ judge الاستدعاءات الموجَّهة إلى حُكّام LLM، وقيمته الافتراضية 4. وهما منفصلان لأن الاثنين يقعان عادةً خلف حدود معدَّل مختلفة.
مئة وعشرون حالة على نظام يستغرق عُشر ثانية لكل استدعاء:
| system: | الزمن الفعلي |
|---|---|
| 1 | 13.4s |
| 16 | 1.7s |
| دون تغيير، إعادة التشغيل | 0.4s |
الصف الأخير هو ذاكرة التخزين المؤقت لا التزامن: أُعيد استخدام كل تنفيذ. التزامن ليس جزءًا من هوية النظام، لذا فإن تغييره لا يُبطل أبدًا ما هو مخزَّن.
كل حالة استدعاء مستقل. لا يجمع Oloproof الحالات في واجهة الدُّفعات (batch API) لدى المزوِّد، لذا لا يتاح عبره خصم الدُّفعات الذي يقدمه المزوِّد؛ فالتزامن وذاكرة التخزين المؤقت هما ما يجعلان التشغيل أسرع.
إعادة المحاولة
تُعاد المحاولة مع مهلة تراجعية، حتى أربع محاولات، مع مزوِّد حَكَم أو نظام HTTP يُجيب بـ 429 أو 5xx، أو تنتهي مهلته، أو يُسقط الاتصال، ولا يُعاد أبدًا قبل ما تطلبه ترويسة Retry-After. ويختار النظام القابل للاستدعاء ذلك برفع TransientError من oloproof مع 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. ومن دونها يُسجَّل الخطأ على الحالة، فتُعَدّ مفقودة بدلًا من إعادة محاولتها. نظامٌ رفع الخطأ بهذه الطريقة في الاستدعاء الأول لكل حالة من ثلاثين حالة، وأجاب في الاستدعاء الثاني:
│ 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يحمل كل حدث معرّف التشغيل وطابعًا زمنيًا:
{"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 بمقدار ما يلزم أن تكبر، مُسعَّرًا مما أنفقه التشغيل بالفعل. بالنسبة إلى examples/support_bot/ ذات الثماني عشرة حالة:
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 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وإذا أُعطيت معرّف مقارنة بدلًا من ذلك، فإنها تخطط لقواعد المقارنة، ولا تحتاج إلى أي خيار.
إلى أين بعد ذلك
- تشرح صفحة تسجيل ما فعله النظام أرقام زمن الاستجابة والرموز التي تُسعَّر منها
هذه التقديرات.
- تشرح صفحة التشغيل في CI قراءة تدفق الأحداث من سكربت.