الأدلة الإرشادية
درس تعليمي: توليد النص مع حَكَم بمعيار
قيّم دالة تكتب نصًا حرًا، وهي هنا مُلخِّص للتذاكر، بفحوص تنسيق وحَكَم بمعيار؛ وقِس ذلك الحَكَم مقابل وسوم شخص ما قبل أن يُسمح له بحسم أي شيء؛ ثم قارن تغييرًا حقيقيًا. يعمل الحَكَم على هذا الجهاز دون نموذج ودون شبكة، وتستبدل به خطوة اختيارية نموذجًا حقيقيًا.
ما الذي ستبنيه
مُلخِّص يحوّل تذكرة دعم إلى جملة أو جملتين. "الجيد" حكم تقديري، لا مطابقة نصية، لذا يحسم نجاحَ المهمة حَكَمُ LLM بمعيار: هل يذكر الملخص الحقائق التي يحتاجها موظف الدعم؟ ويفحص مُقيِّمان حتميان التنسيق، الذي لا يحتاج إلى مرجع. والمصطلحات مثل الحالة والتشغيل والمقياس والحَكَم والبوابة معرَّفة في المفاهيم.
الشكل نفسه يناسب الاستخراج أو أي توليد آخر: دالة تعيد نصًا في قاموس، والمرجع يقول ما يجب أن تحتويه الإجابة الجيدة، والمعيار يقول كيف يُحسم الأمر.
المتطلبات المسبقة
- Python 3.11 أو أحدث، وOloproof في بيئة افتراضية:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- مشروع المثال، وهو يأتي مع الحزمة. انسخه إلى مجلد جديد واعمل هناك:
oloproof init --example generation ticket-summaries
cd ticket-summaries- المنفذ 8799 شاغر للحَكَم البديل (غيّره في الموضعين إن لم يكن كذلك).
كل خطوة حتى "اختياري: نموذج حقيقي حَكَمًا" تعمل دون اتصال وحتمية: لا مفتاح API، ولا حساب لدى مزوِّد، ولا تكلفة.
الملفات
ticket-summaries/
app.py the summariser under test (baseline)
app_v2.py the candidate change
judge_server.py a stand-in judge speaking the OpenAI API on 127.0.0.1
rubrics/covers_facts.md the judge's rubric
oloproof.yaml the suite
release.yaml rules for a run
compare.yaml a rule for a comparison
data/tickets.jsonl 20 cases
labels/reviewer_verdicts.csv one person's verdicts on the baseline's summaries
fill_labels.py copies those verdicts into a labelling sheetشغّل كل أمر من ticket-summaries/.
الحَكَم البديل، وما ليس هو
حَكَم المعيار مُقيِّم يرسل موجّهًا (المعيار، ومُدخَل الحالة، وexpected الخاص بها، والمُخرَج) إلى نموذج ويقرأ في المقابل {"pass": true|false, "rationale": "..."}. يتحدث Oloproof مع أي خادم يتكلم واجهة الدردشة البرمجية لـ OpenAI، والخادم على localhost لا يحتاج إلى مفتاح.
judge_server.py خادم من هذا النوع، لكنه ليس نموذجًا. فهو لا يُنجح ملخصًا إلا حين يحتوي على كل عبارة تحت must_mention في expected الحالة، دون اعتبار لحالة الأحرف. تلك قاعدة ثابتة، لذا يعطي الدرس الأرقام نفسها على كل جهاز. ولا يستطيع أن يلاحظ حقيقة مختلَقة، وهو ما يُطلب من حَكَم نموذج حقيقي. شغّله في طرفية ثانية واتركه يعمل:
python judge_server.py --port 8799stand-in judge on http://127.0.0.1:8799/v1التطبيق ومحوِّله
# app.py
@system(name="ticket-summariser", version="first-sentence")
def summarise(case: dict[str, Any]) -> dict[str, str]:
return {"summary": sentences(str(case["ticket"]))[0]}المحوِّل لتطبيق Python هو الدالة: تتلقى input الحالة وتعيد قاموسًا. ولمولِّدك أنت، استدعِ نموذجك أو سلسلتك داخلها وأعِد النص تحت مفتاح. يستدعيها Oloproof مرة لكل حالة ويخزّن المُخرَج مؤقتًا وفق مصدر الدالة وversion المُعلَنة؛ ولا يدير عميل نموذجك ولا موجّهاتك ولا حالتك. واسرد الملفات التي تقرؤها الدالة، مثل قالب موجّه، تحت system.code_paths.
مجموعة البيانات
{"id":"t01","input":{"ticket":"Hello. Order 1042 arrived with a cracked screen. I would like a replacement, not a refund."},"expected":{"must_mention":["1042","cracked","replacement"]}}
{"id":"t06","input":{"ticket":"Please cancel my subscription at the end of this month. I am moving abroad."},"expected":{"must_mention":["cancel","end of this month"]}}input هو ما تتلقاه الدالة. وexpected هو المرجع الذي يقرؤه الحَكَم: وهو هنا قائمة حقائق يجب أن يحملها الملخص، لا ملخص مرجعي كامل، لأن ملخصات كثيرة مختلفة تكون صحيحة. والمُخرَج لـ t01 هو {"summary": "Hello."}.
اختيار المُقيِّمات
version: 1
project: ticket-summaries
dataset: data/tickets.jsonl
system:
name: ticket-summariser
version: first-sentence
callable: app:summarise
timeout_s: 30
evaluators:
- type: json_schema
criterion: format_valid
field: null
schema:
type: object
required: [summary]
properties:
summary: {type: string, minLength: 1}
additionalProperties: false
- type: regex
criterion: short_enough
field: summary
pattern: '^.{1,160}$'
pass_if: match
- type: rubric_judge
criterion: covers_facts
provider: openai_compatible
model: stand-in-judge
base_url: http://127.0.0.1:8799/v1
rubric_file: rubrics/covers_facts.md| المعيار | المُقيِّم | يحتاج إلى expected | يقيس |
|---|---|---|---|
| format_valid | json_schema | لا | التنسيق: حقل نصي واحد غير فارغ |
| short_enough | regex | لا | التنسيق: 160 حرفًا على الأكثر |
| covers_facts | rubric_judge | نعم | نجاح المهمة، كما يعرّفه المعيار |
تجتاز Hello. فحصَي التنسيق كليهما. والحَكَم وحده يقول إنه ملخص عديم الفائدة. ويمكن للحَكَم أيضًا أن يعمل دون مرجع: معيار مثل "PASS if the summary contains no greeting" لا يقرأ إلا المُدخَل والمُخرَج، والحالة التي بلا expected يُحكم عليها مع ذلك. وما لا يستطيعه حينها هو التحقق من الحقائق مقابل إجابة تثق بها.
المعيار:
PASS when the summary states every fact listed under must_mention in the expected answer, in
words a support agent would recognise, and adds nothing the ticket does not say.
FAIL when any listed fact is missing, changed or contradicted.السياسة
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
rules:
- id: valid-format
metric: format_valid
kind: observed_count
max_failures: 0
- id: short-enough
metric: short_enough
kind: observed_count
max_failures: 0
- id: covers-facts-floor
metric: covers_facts
min: 0.60require_validated_evaluators: true هو الافتراضي في المحرك، مكتوبًا هنا صراحة لأنه جوهر هذا الدرس: الحَكَم الذي لم يقارنه أحد بالبشر لا يجوز له أن يحسم قاعدة.
شغّله
oloproof runRun run_01M4... [DECIDED/COMPLETE]
Gate: BLOCK (exit 3)
│ valid-format │ format_valid │ PASS │ observed_failures_within_limit │
│ short-enough │ short_enough │ PASS │ observed_failures_within_limit │
│ covers-facts-floor │ covers_facts │ INSUFFICIENT_EVIDENCE │ evaluator_not_validated │
covers-facts-floor: the judge (or model or custom evaluator) behind this rule has not been measured against
people yet, so it may not decide.
Label a sample: oloproof review run_01M4... --criterion covers_facts --by YOU --sample 20
Then measure it: oloproof evaluators validate EVALUATOR_ID --by YOU (ids: oloproof evaluators list)
│ format_valid │ 100.0% │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ short_enough │ 100.0% │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ covers_facts │ 45.0% │ [23.0%, 68.5%] │ 9 / 20 observed · 0 missing · 0 excluded │
Cache: execution 0 hit/20 miss; judgment 0 hit/60 missتنجح قواعد التنسيق. أنجح الحَكَم 9 من 20 ملخصًا، لكن القاعدة INSUFFICIENT_EVIDENCE مع السبب evaluator_not_validated، وتحجب البوابة برمز الخروج 3. لم تحسم القاعدة بناءً على 45%: فمعدل خطأ الحَكَم مجهول حتى يُقاس، لذا فالفترة المبنية على أحكامه ستحمل خطأً غير مُعلَن. يُبلغ المحرك عن ذلك بوصفه INSUFFICIENT_EVIDENCE، لا MANUAL_REVIEW ولا FAIL: فالأدلة اللازمة للحسم غائبة، ويطبع المُخرَج الأمرين اللذين يوفّرانها.
افحص الإخفاقات
oloproof inspect RUN_ID --failures11 of 20 cases failed, errored or did not finish
t01
output: {"summary": "Hello."}
covers_facts: failed
judge text, not verified: missing: 1042, cracked, replacement
t02
output: {"summary": "I was charged twice for order 2210."}
covers_facts: failed
judge text, not verified: missing: 49
...يُعرض تعليل الحَكَم بوصفه "judge text, not verified": إنه شرح النموذج، لا دليل. والنمط واضح على أي حال: الجملة الأولى كثيرًا ما تكون تحية.
قِس الحَكَم مقابل شخص
يقارن التحقق أحكام الحَكَم بأحكام شخص على الإجابات نفسها. اسحب عيّنة عشوائية من حالات التشغيل إلى ورقة. وتُترك أحكام الحَكَم خارجها، كي لا يتأثر بها واضع الوسوم:
oloproof labels export RUN_ID --criterion covers_facts --sample 20 --local --out sample.csvWrote 20 cases to sample.csv, drawn at random with seed 2701013296, without the judge's verdict.
This is a local sample, good-faith only, because it was drawn on this machine.
Fill in `passed` (pass or fail) and `labelled_by` on each row you judge, then run `oloproof labels import sample.csv`.يسحب --local العيّنة على هذا الجهاز دون أن يطلب ذلك من مساحة عمل مستضافة؛ ويختار المحرك البذرة. ومع 20 حالة، تكون العيّنة من 20 هي الحالات كلها. وفي الممارسة، يقرأ شخص تذكرة كل صف وملخصه ويملأ passed. ولهذا الدرس، يحمل labels/reviewer_verdicts.csv أحكامًا أعطاها مراجع على ملخصات خط الأساس، وينسخها fill_labels.py إلى الورقة:
python fill_labels.py sample.csv
oloproof labels import sample.csvfilled 20 rows of sample.csv
Recorded 20 labels from sample.csv (20 measurement).اختلف المراجع مع الحَكَم مرة واحدة: في t02 ("I was charged twice for order 2210.") رأى أن المبلغ الغائب غير جوهري وأنجحه. والوسوم تسمّي الإجابة بالضبط التي حكمت عليها، لذا لا تنطبق هذه الأحكام إلا على تشغيل خط الأساس.
اعثر على معرّف نسخة الحَكَم وتحقق منه:
oloproof evaluators list
oloproof evaluators validate EVALUATOR_ID --by alicecovers_facts LLM_JUDGE UNVALIDATED (declared) sha256:a662...
covers_facts: sha256:a662... is now VALIDATED
agreement 95.0% [75.1%, 99.9%] · 19 of 20 labelled cases agreed · 0 labelled but not judged · kappa 0.900
bias -5.0 points [-32.4, +20.7] · the judge's pass rate minus the people's · 20 cases · 0 labelled but not judged
passes what people pass 90.0% [55.4%, 99.8%] · the judge passed 9 of 10 cases people passed · 0 labelled but not judged
fails what people fail 100.0% [69.1%, 100.0%] · the judge failed 10 of 10 cases people failed · 0 labelled but not judgedاقرأ الفترات، لا نسبة 95%: تُظهر 20 وسمًا اتفاقًا لا يقل عن 75.1%. ويمكن لسياسة أن تطلب أكثر بـ minimum_evaluator_agreement، الذي يقارن ذلك الحد الأدنى، ويرفض validate حَكَمًا دونه. ويغطي دليل الحُكّام المستوى المطلوب، والانحياز، والمسابير، وoloproof review لوضع الوسوم في الطرفية.
والآن أعِد حسم التشغيل المخزَّن دون استدعاء المُلخِّص أو الحَكَم:
oloproof gate RUN_ID --policy release.yamlvalid-format: PASS (observed_failures_within_limit)
short-enough: PASS (observed_failures_within_limit)
covers-facts-floor: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
no sample size would make this PASS: the observed rate (0.500) is itself below the threshold (0.600), so more cases would move it toward FAIL
Gate: BLOCK (exit 3)يجوز للحَكَم أن يحسم الآن، والقرار يخص المُلخِّص: المعدل الذي يذكره، 0.500، ليس نسبة الحَكَم 45%. ولأن لهذا التشغيل عيّنة عمياء عشوائية من وسوم القياس، تقرأ البوابة الحَكَم مصحَّحًا بتلك الوسوم ("Judge-corrected gates" في دليل الحُكّام). والتصحيح هو PPI، أي الاستدلال المدعوم بالتنبؤ: يستخدم العيّنة الموسومة لقياس بُعد معدل الحَكَم عن معدل البشر، ويزيح التقدير ويوسّع الفترة بذلك القدر. وهذا أيضًا ما تشير إليه ملاحظات التصدير عن PPI. وفي كلتا الحالتين، لا يبلغ خط الأساس الحد الأدنى، ولن يغيّر المزيد من الحالات ذلك.
أجرِ تغييرًا حقيقيًا
يتخطى app_v2.py عبارات المجاملة القصيرة ويحتفظ بالجملتين التاليتين. انسخه فوق app.py، واضبط version: skip-pleasantries تحت system في oloproof.yaml، وأبقِ الحَكَم يعمل، ثم:
oloproof runGate: ALLOW (exit 0)
│ covers-facts-floor │ covers_facts │ PASS │ lower_bound_meets_minimum │
│ covers_facts │ 100.0% │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
Cache: execution 0 hit/20 miss; judgment 6 hit/54 missالحَكَم هو النسخة نفسها المُتحقَّق منها، لذا تحسم قاعدته مباشرة. وجاءت ستة أحكام من التخزين المؤقت، على ملخصات كتبتها النسختان بشكل متطابق. لم يضع أحد وسومًا على هذه الملخصات الجديدة؛ والتحقق من الحَكَم هو ما يتيح لأحكامه أن تُعتمد.
قارن المرشَّح بخط الأساس
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
rules:
- id: covers-more-facts
kind: superiority
metric: covers_factsoloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlformat_valid: +0.0 points [-23.6, +23.6] · 20 paired · 0 missing · 0 excluded
short_enough: +0.0 points [-23.6, +23.6] · 20 paired · 0 missing · 0 excluded
covers_facts: +55.0 points [+13.0, +84.4] · 20 paired · 0 missing · 0 excluded
Decisions
covers-more-facts covers_facts superiority PASS difference_above_zero
Gate: ALLOW (exit 0)لا تطبّق المقارنة تصحيح PPI: فهي تقارن أحكام الحَكَم نفسه على التشغيلين، ولهذا يبدأ المكسب من نسبة الحَكَم 45% لا من القيمة المصحَّحة 0.500 أعلاه. تحسّن أحد عشر ملخصًا ولم يسُؤ أي منها؛ وفترة المكسب كلها فوق الصفر، فتنجح قاعدة التفوق ويخرج الأمر بالرمز 0. ويحرس التنسيقَ قواعدُ التشغيل، التي لا تسمح بأي إخفاق، لا المقارنة: فعلى 20 حالة، لا تستطيع مقارنة درجتي تنسيق كاملتين إلا أن تقول إن الفرق ضمن 23.6 نقطة.
اختياري: نموذج حقيقي حَكَمًا
تغادر هذه الخطوة المسار غير المتصل. فهي تحتاج إلى خادم نماذج، ومع مزوِّد سحابي إلى مفتاح ومال.
- محليًا، دون مفتاح ودون تكلفة: Ollama أو LM Studio أو llama.cpp على localhost. اسحب نموذج دردشة (في Ollama، ollama pull llama3.1).
- سحابيًا: provider: anthropic أو openai مع api_key_env الذي يسمّي المتغير الحامل لمفتاحك، أو openai_compatible مع base_url وapi_key_env. كل حالة استدعاء حَكَم واحد (اثنان حين لا يكون الرد الأول JSON صالحًا)، يُحاسَب بأسعار مزوِّدك، ولا يستدعي Oloproof حَكَمًا مرة أخرى أبدًا لإجابة سبق أن حكم عليها.
اكتب مسودة الحَكَم في ملف خاص بها، كما ستظهر تحت evaluators::
# live_judge.yaml
type: rubric_judge
criterion: covers_facts
provider: openai_compatible
model: llama3.1
base_url: http://localhost:11434/v1
rubric_file: rubrics/covers_facts.mdوجرّبها على الإجابات التي وضع عليها مراجعك وسومًا بالفعل، دون التحقق منها أو اعتمادها:
oloproof evaluators try live_judge.yamlتجيب الخوادم المحلية افتراضيًا عن طلب واحد في كل مرة؛ أضف concurrency: {system: 2, judge: 2} إلى oloproof.yaml كي لا تنتهي مهلة الاستدعاءات المنتظرة. وطبع تشغيلٌ لهذه الخطوة بنموذج محلي صغير (qwen2.5vl) على حاسوب محمول:
covers_facts: draft sha256:b88a... on 20 labelled cases · 20 judged now, 0 from cache, 11 errored
agreement 88.9% [19.1%, 99.9%] · 8 of 9 labelled cases agreed · 11 labelled but not judged · kappa 0.769انتهت مهلة أحد عشر استدعاءً، وفترة الاتفاق تحتسب كلًّا منها في الاتجاهين، فتنزل حتى 19.1%: الحَكَم الذي لا يجيب لا يُقاس. والإصلاح نموذج أكبر، أو مهلة أطول، أو استدعاءات متزامنة أقل. ولاعتماد النموذج، ضعه في oloproof.yaml مكان البديل. تلك نسخة مُقيِّم جديدة: تهيئتها (النموذج، ونقطة النهاية، والمعيار) هي هويتها، لذا لا ينتقل إليها التحقق من البديل. شغّل خط الأساس مرة أخرى بها وتحقق منها مقابل الوسوم، كما سبق.
استكشاف الأخطاء وإصلاحها
| العَرَض | السبب والإصلاح |
|---|---|
| covers_facts كلها مفقودة، no_observations | خادم الحَكَم لا يعمل أو ليس على base_url. وقع خطأ في كل استدعاء للحَكَم؛ ويُظهر oloproof inspect RUN_ID --failures السبب. |
| evaluator_not_validated بعد أن تحققت | غيّرت الحَكَم (النموذج، نقطة النهاية، المنفذ، المعيار) فأنشأت نسخة جديدة. تحقق من تلك. |
| يرفض labels import الملف ويسمّي صفًا | يسمّي الصف حالة أو تنفيذًا لا يحمله التشغيل؛ صدّر من جديد من التشغيل الذي تضع عليه الوسوم. |
| يقول labels export إنه تعذّر الوصول إلى مساحة عمل | أنت مسجّل الدخول إلى واحدة، فطلب منها السحب. يسحب --local هنا بدلًا من ذلك. |
| يُخفق حَكَم سحابي قبل أي استدعاء | مفتاحه ليس في المتغير الذي يسمّيه api_key_env. |
القيود
- الحَكَم البديل مطابقة عبارات. وهو يوضّح سير العمل، لا جودة الحكم.
- لا توجد مُقيِّمات BLEU أو ROUGE أو تشابه التضمينات. في SDK، اكتب واحدًا بـ @evaluator؛ ولا يستطيع oloproof.yaml بعد تسمية مُقيِّم مخصّص.
- يرى الحَكَم نصًا: JSON للمُدخَل والمرجع والمُخرَج. ولا يرى صورًا ولا صوتًا.
- عشرون وسمًا تعطي فترة اتفاق واسعة. ضع وسومًا أكثر، عشوائيًا وعلى نحو أعمى، لحَكَم تعتمد عليه.
- العيّنة المحلية حسنة النية فقط. ولحَكَم يعتمد عليه آخرون، أرسل التشغيل ودع مساحة عمل مستضافة تسحب العيّنة (الحُكّام).