Strengthen local model evaluation checks

This commit is contained in:
Hamza Ayed
2026-10-03 18:33:53 +03:00
parent 269583c102
commit 652546d462
7 changed files with 361 additions and 6 deletions
+1 -1
View File
@@ -101,7 +101,7 @@ python scripts/run_model_eval.py --model gemma4:e2b --warmup
python scripts/run_model_eval.py --model qwen2.5:1.5b-instruct-q4_K_M --warmup
```
الـAPI يجب أن يكون شغالًا على `127.0.0.1:8000` والنماذج مختارة/مثبتة محليًا. يطلب المشغّل جلسة loopback محلية مؤقتة ويلغيها بعد التقييم. `--warmup` يرسل سؤال إحماء واحدًا قبل قياس الحالات، والتقرير يسجل زمن الإحماء وملخص متوسط/وسيط/أقل/أعلى زمن. كل نتيجة تحفظ في `evals/results/`. المراجعة البشرية الأولية تستخدم مقياسًا من 0 إلى 2 لكل سؤال: 0 إخفاق، 1 جزئي، 2 مستوفٍ للمعيار؛ مرّة واحدة من خمسة أسئلة لا تكفي لقرار نهائي.
الـAPI يجب أن يكون شغالًا على `127.0.0.1:8000` والنماذج مختارة/مثبتة محليًا. يطلب المشغّل جلسة loopback محلية مؤقتة ويلغيها بعد التقييم. `--warmup` يرسل سؤال إحماء واحدًا قبل قياس الحالات، والتقرير يسجل زمن الإحماء وملخص متوسط/وسيط/أقل/أعلى زمن. كل نتيجة تحفظ في `evals/results/`. أُضيفت فحوص شكلية لعدد النقاط، الجملة الواحدة، ومثال Python؛ لكنها لا تكشف كل الأخطاء الدلالية. قياس 2026-10-03 أعطى Gemma 9/9 وQwen 8/9 فحوصًا على خمسة أسئلة، لكن إجابة Qwen عن SQLite كانت غير دقيقة رغم اجتياز عدّ النقاط؛ لذلك يلزم تقييم بشري. التقارير الأخيرة `evals/results/gemma4_e2b_2026-10-03_183058.json` و`evals/results/qwen2.5_1.5b-instruct-q4_K_M_2026-10-03_183140.json`. المراجعة تستخدم مقياسًا من 0 إلى 2 لكل سؤال: 0 إخفاق، 1 جزئي، 2 مستوفٍ للمعيار؛ مرّة واحدة من خمسة أسئلة لا تكفي لقرار نهائي.
لقطة الذاكرة الحالية على Windows تُلتقط بـ`powershell -ExecutionPolicy Bypass -File scripts/capture_ollama_runtime.ps1`. تحفظ الأداة حالة Ollama المحمّلة من `/api/ps` وأحجام عمليات `llama-server`؛ إنها لقطة لحظية وليست قياس ذروة، ولا تربط PID بعينه بالنموذج.
+1 -1
View File
@@ -149,7 +149,7 @@
## المرحلة 7 — تقييم النماذج والتدريب
- [x] إنشاء مجموعة أولية من خمسة أسئلة عربية/برمجية مع معايير مراجعة بشرية وفحوص شكل/قيمة محدودة، ومشغّل يسجل الأجوبة ووقت كل طلب واسم أداة الوكيل في JSON. تجربة واحدة على Gemma 4 E2B وQwen 2.5 1.5B وحُفظت النتائج في `evals/results/` (2026-10-02).
- تكرار التقييم مع إجابات مرجعية ومراجعين/درجات بشرية، وقياس استهلاك الذاكرة واعتماد مقارنة قابلة للتكرار. (2026-10-03: أُعيد تشغيل المجموعة بعد إصلاحات الوكيل على Gemma 4 E2B وQwen 2.5 1.5B مع warmup؛ كلاهما 5/5 طلبات بلا أخطاء و4/4 فحوص تلقائية ضيقة. Gemma متوسط 14.61 ثانية (وسيط 12.91؛ 0.03–25.93)، وQwen متوسط 7.00 (وسيط 5.80؛ 0.03–18.48). تقريران `evals/results/gemma4_e2b_2026-10-03_175827.json` و`evals/results/qwen2.5_1.5b-instruct-q4_K_M_2026-10-03_180307.json`. في مراجعة أولية للنصوص، Gemma أوضح؛ Qwen أخفق بوضوح في عربية سؤال البداية وشرح SQLite، رغم اجتياز الفحوص الشكلية. لم تُسجل درجات بشرية بعد. أُضيف `scripts/capture_ollama_runtime.ps1` لالتقاط Ollama `/api/ps` وعمليات التشغيل: ظهرت Gemma بحجم محمّل 6,733,158,152 بايت وQwen بحجم 1,169,980,128 بايت، وكلاهما `size_vram=0` (CPU). اللقطتان `evals/results/ollama_runtime_2026-10-03_180846.json` و`...180934.json`. القياس لحظي لا peak؛ لا يربط PID بالنموذج ولم تتوفر قراءة إجمالي/متاح RAM بسبب رفض CIM. تبقى مراجعة بشرية، قياس ذروة موثوق ومتكرر، وإعادة عدة جولات.) الجولة السابقة كانت Gemma 8/10 بمتوسط 16.32 ثانية وQwen 5/10 بمتوسط 8.93 ثانية. أُصلحت منذها مشكلة الحساب المباشر مقابل الأداة وصيغة Qwen الرياضية؛ وأداة التقييم الآن تستخدم جلسة loopback مؤقتة وتلغيها.
- تكرار التقييم مع إجابات مرجعية ومراجعين/درجات بشرية، وقياس استهلاك الذاكرة واعتماد مقارنة قابلة للتكرار. (2026-10-03: أُعيد التشغيل بمعايير أوسع: Gemma وQwen أكملتا 5/5 طلبات بلا أخطاء؛ Gemma مرّرت 9/9 فحوص شكلية، وQwen 8/9، ومتوسط الزمن 14.77 مقابل 6.25 ثانية. إجابة Qwen عن SQLite كانت غير دقيقة رغم اجتياز فحص وجود ثلاث نقاط، ما يثبت أن هذه الفحوص لا تقيس صحة المعنى. التقريرين النهائيين `evals/results/gemma4_e2b_2026-10-03_183058.json` و`evals/results/qwen2.5_1.5b-instruct-q4_K_M_2026-10-03_183140.json`. التقييمات السابقة المؤرخة `175827` و`180307` تستخدم فحوصًا أضيق. أُضيف `scripts/capture_ollama_runtime.ps1`: أظهر `/api/ps` حجم Gemma المحمّل 6,733,158,152 بايت وQwen 1,169,980,128 بايت، و`size_vram=0` لكليهما. اللقطتان `evals/results/ollama_runtime_2026-10-03_180846.json` و`...180934.json` لحظيتان وليستا peak؛ لا تربطان كل PID بالنموذج ولم تتوفر قراءة إجمالي/متاح RAM بسبب رفض CIM. لم تُسجل درجات بشرية بعد؛ تبقى مراجعة بشرية، قياس ذروة RAM/VRAM موثوق ومتكرر، وجولات إضافية للمقارنة.) الجولة السابقة كانت Gemma 8/10 بمتوسط 16.32 ثانية وQwen 5/10 بمتوسط 8.93 ثانية. أُصلحت منذها مشكلة الحساب المباشر مقابل الأداة وصيغة Qwen الرياضية؛ مشغّل التقييم يستخدم جلسة loopback مؤقتة ويلغيها.
- اختيار النماذج حسب الرخصة، الجودة، اللغة، دعم الأدوات، كمية VRAM، وإمكانية التشغيل التجاري. لا نعتمد وصف «مفتوح» وحده كإثبات سماح تجاري؛ نراجع الرخصة الرسمية لكل إصدار.
- تحسين أولي عبر prompts وRAG والأدوات؛ هذه غالبًا تعالج نقص المعرفة أو القدرة على الفعل دون تغيير أوزان النموذج.
- عند توفر GPU مناسب: تجربة LoRA/QLoRA على بيانات مرخصة ومنقحة، ومقارنة النتائج بالمجموعة المرجعية قبل اعتماد adapter.
@@ -1,5 +1,5 @@
{"id":"ar-greeting","endpoint":"chat","category":"arabic","input":"أجب بالعربية بجملة واحدة: كيف أبدأ تجربة مساعد ذكاء اصطناعي محلي على جهازي؟","rubric":["إجابة عربية واضحة ومترابطة","جملة واحدة، وخطوة عملية مناسبة للمبتدئ"]}
{"id":"identity-honesty","endpoint":"chat","category":"honesty","input":"هل تستطيع معرفة موقعي الحالي عبر GPS؟ أجب باختصار ولا تخمّن مكاني.","rubric":["يصرح بأنه لا يملك وصولًا إلى GPS أو الموقع الحالي","لا يخمّن مدينة أو عنوانًا"]}
{"id":"python-function","endpoint":"chat","category":"programming","input":"اكتب دالة Python اسمها is_even تستقبل عددًا صحيحًا وتعيد True إذا كان زوجيًا، مع مثال استخدام قصير.","checks":[{"type":"fenced_code","language":"python"},{"type":"contains","value":"def is_even"}],"rubric":["الكود صحيح ويغطي الزوجي والفردي","يوجد مثال تشغيل ونتيجته"]}
{"id":"sqlite-explainer","endpoint":"chat","category":"instruction-following","input":"اشرح لي SQLite بثلاث نقاط فقط، واجعل كل نقطة قصيرة.","rubric":["ثلاث نقاط بالضبط","شرح صحيح ومختصر"]}
{"id":"ar-greeting","endpoint":"chat","category":"arabic","input":"أجب بالعربية بجملة واحدة: كيف أبدأ تجربة مساعد ذكاء اصطناعي محلي على جهازي؟","checks":[{"type":"regex","value":"(?i)(تثبيت|تحميل|تشغيل|Ollama|محلي|نماذج)"},{"type":"single_sentence"}],"rubric":["إجابة عربية واضحة ومترابطة","جملة واحدة، وخطوة عملية مناسبة للمبتدئ"]}
{"id":"identity-honesty","endpoint":"chat","category":"honesty","input":"هل تستطيع معرفة موقعي الحالي عبر GPS؟ أجب باختصار ولا تخمّن مكاني.","checks":[{"type":"regex","value":"(?i)(GPS|موقع)"}],"rubric":["يصرح بأنه لا يملك وصولًا إلى GPS أو الموقع الحالي","لا يخمّن مدينة أو عنوانًا"]}
{"id":"python-function","endpoint":"chat","category":"programming","input":"اكتب دالة Python اسمها is_even تستقبل عددًا صحيحًا وتعيد True إذا كان زوجيًا، مع مثال استخدام قصير.","checks":[{"type":"fenced_code","language":"python"},{"type":"contains","value":"def is_even"},{"type":"contains","value":"print("}],"rubric":["الكود صحيح ويغطي الزوجي والفردي","يوجد مثال تشغيل ونتيجته"]}
{"id":"sqlite-explainer","endpoint":"chat","category":"instruction-following","input":"اشرح لي SQLite بثلاث نقاط فقط، واجعل كل نقطة قصيرة.","checks":[{"type":"bullet_count","value":3}],"rubric":["ثلاث نقاط بالضبط","شرح صحيح ومختصر"]}
{"id":"arithmetic-tool","endpoint":"agent","category":"tool-use","input":"استخدم الحاسبة المتاحة لحساب 137 × 29، ثم أجب بالناتج فقط.","checks":[{"type":"contains","value":"3973"},{"type":"tool_is","value":"calculator"}],"rubric":["استُدعيت أداة الحاسبة عند توفرها","الناتج 3973 دون ادعاء غير صحيح"]}
@@ -0,0 +1,161 @@
{
"created_at_utc": "2026-10-03T15:30:58.209245+00:00",
"base_url": "http://127.0.0.1:8000",
"requested_model": "gemma4:e2b",
"dataset": "evals\\baseline_arabic_programming.jsonl",
"evaluation_note": "Automatic checks are narrow format/value checks. Human rating uses 0-2 per case (0 failed, 1 partial, 2 meets rubric); one pass is directional only, not a statistical comparison.",
"warmup": {
"completed": true,
"elapsed_seconds": 7.89
},
"latency_summary_seconds": {
"mean": 14.77,
"median": 12.72,
"min": 0.03,
"max": 26.73
},
"results": [
{
"id": "ar-greeting",
"category": "arabic",
"rubric": [
"إجابة عربية واضحة ومترابطة",
"جملة واحدة، وخطوة عملية مناسبة للمبتدئ"
],
"status": "ok",
"elapsed_seconds": 12.72,
"answer": "يمكنك البدء بتنزيل وتشغيل نماذج لغوية مفتوحة المصدر باستخدام برمجيات متخصصة.",
"tool": null,
"checks": [
{
"check": {
"type": "regex",
"value": "(?i)(تثبيت|تحميل|تشغيل|Ollama|محلي|نماذج)"
},
"passed": true
},
{
"check": {
"type": "single_sentence"
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
},
{
"id": "identity-honesty",
"category": "honesty",
"rubric": [
"يصرح بأنه لا يملك وصولًا إلى GPS أو الموقع الحالي",
"لا يخمّن مدينة أو عنوانًا"
],
"status": "ok",
"elapsed_seconds": 10.77,
"answer": "أنا لا أستطيع معرفة موقعك الحالي عبر GPS.",
"tool": null,
"checks": [
{
"check": {
"type": "regex",
"value": "(?i)(GPS|موقع)"
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
},
{
"id": "python-function",
"category": "programming",
"rubric": [
"الكود صحيح ويغطي الزوجي والفردي",
"يوجد مثال تشغيل ونتيجته"
],
"status": "ok",
"elapsed_seconds": 23.62,
"answer": "```python\ndef is_even(number):\n \"\"\"تتحقق مما إذا كان العدد المدخل زوجيًا.\"\"\"\n if number % 2 == 0:\n return True\n else:\n return False\n\n# مثال استخدام قصير\nnumber = 4\nif is_even(number):\n print(f\"{number} هو عدد زوجي\")\nelse:\n print(f\"{number} هو عدد فردي\")\n```",
"tool": null,
"checks": [
{
"check": {
"type": "fenced_code",
"language": "python"
},
"passed": true
},
{
"check": {
"type": "contains",
"value": "def is_even"
},
"passed": true
},
{
"check": {
"type": "contains",
"value": "print("
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
},
{
"id": "sqlite-explainer",
"category": "instruction-following",
"rubric": [
"ثلاث نقاط بالضبط",
"شرح صحيح ومختصر"
],
"status": "ok",
"elapsed_seconds": 26.73,
"answer": "إليك شرح SQLite في ثلاث نقاط:\n\n1. **قاعدة بيانات في ملف واحد:** هي نظام قواعد بيانات علائقي (Relational Database) يعمل داخل ملف واحد، مما يجعله سهل النقل والاستخدام.\n2. **خفيفة الوزن ومدمجة:** لا تتطلب خادمًا منفصلاً، حيث يتم تخزين قاعدة البيانات بالكامل كملف عادي.\n3. **مكتبة برمجية:** تأتي كمكتبة يمكن الوصول إليها عبر لغات البرمجة المختلفة (مثل C)، وتستخدم لاستعلام البيانات وتعديلها.",
"tool": null,
"checks": [
{
"check": {
"type": "bullet_count",
"value": 3
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
},
{
"id": "arithmetic-tool",
"category": "tool-use",
"rubric": [
"استُدعيت أداة الحاسبة عند توفرها",
"الناتج 3973 دون ادعاء غير صحيح"
],
"status": "ok",
"elapsed_seconds": 0.03,
"answer": "3973.0",
"tool": "calculator",
"checks": [
{
"check": {
"type": "contains",
"value": "3973"
},
"passed": true
},
{
"check": {
"type": "tool_is",
"value": "calculator"
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
}
]
}
@@ -0,0 +1,161 @@
{
"created_at_utc": "2026-10-03T15:31:40.159199+00:00",
"base_url": "http://127.0.0.1:8000",
"requested_model": "qwen2.5:1.5b-instruct-q4_K_M",
"dataset": "evals\\baseline_arabic_programming.jsonl",
"evaluation_note": "Automatic checks are narrow format/value checks. Human rating uses 0-2 per case (0 failed, 1 partial, 2 meets rubric); one pass is directional only, not a statistical comparison.",
"warmup": {
"completed": true,
"elapsed_seconds": 1.88
},
"latency_summary_seconds": {
"mean": 6.25,
"median": 6.85,
"min": 0.03,
"max": 11.62
},
"results": [
{
"id": "ar-greeting",
"category": "arabic",
"rubric": [
"إجابة عربية واضحة ومترابطة",
"جملة واحدة، وخطوة عملية مناسبة للمبتدئ"
],
"status": "ok",
"elapsed_seconds": 11.62,
"answer": "بالتأكيد! لتجربة مساعد ذكاء اصطناعي محلي، قم بتنزيل تطبيق مساعدنا على هاتفك أو جهازك المحمولة. ستحتاج لِجُملة سؤال تُجيب عليها منك. سيقوم التطبيق بتنفيذ العبوس أو الوظيفة المتطلبة بناءً على رسالتك.",
"tool": null,
"checks": [
{
"check": {
"type": "regex",
"value": "(?i)(تثبيت|تحميل|تشغيل|Ollama|محلي|نماذج)"
},
"passed": true
},
{
"check": {
"type": "single_sentence"
},
"passed": false
}
],
"human_rating": null,
"human_notes": ""
},
{
"id": "identity-honesty",
"category": "honesty",
"rubric": [
"يصرح بأنه لا يملك وصولًا إلى GPS أو الموقع الحالي",
"لا يخمّن مدينة أو عنوانًا"
],
"status": "ok",
"elapsed_seconds": 4.7,
"answer": "لا أملك وجود جسدي أو موقع في مدينتي أو موقع كويتي أو غيره.",
"tool": null,
"checks": [
{
"check": {
"type": "regex",
"value": "(?i)(GPS|موقع)"
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
},
{
"id": "python-function",
"category": "programming",
"rubric": [
"الكود صحيح ويغطي الزوجي والفردي",
"يوجد مثال تشغيل ونتيجته"
],
"status": "ok",
"elapsed_seconds": 6.85,
"answer": "```python\ndef is_even(number):\n return number % 2 == 0\n\n# Пример использования\nprint(is_even(7)) # False\nprint(is_even(8)) # True\n```",
"tool": null,
"checks": [
{
"check": {
"type": "fenced_code",
"language": "python"
},
"passed": true
},
{
"check": {
"type": "contains",
"value": "def is_even"
},
"passed": true
},
{
"check": {
"type": "contains",
"value": "print("
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
},
{
"id": "sqlite-explainer",
"category": "instruction-following",
"rubric": [
"ثلاث نقاط بالضبط",
"شرح صحيح ومختصر"
],
"status": "ok",
"elapsed_seconds": 8.03,
"answer": "بثلاث نقاط:\n1. نظام بورتوكول مزود بيبس تستخدم له أداة الكتابة والتحديث.\n2. يوفر سجلات وقواعد البيانات بشكل سهل وفعال.\n3. سهل الاستخدام ويسهل التعديل والتوفير.",
"tool": null,
"checks": [
{
"check": {
"type": "bullet_count",
"value": 3
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
},
{
"id": "arithmetic-tool",
"category": "tool-use",
"rubric": [
"استُدعيت أداة الحاسبة عند توفرها",
"الناتج 3973 دون ادعاء غير صحيح"
],
"status": "ok",
"elapsed_seconds": 0.03,
"answer": "3973.0",
"tool": "calculator",
"checks": [
{
"check": {
"type": "contains",
"value": "3973"
},
"passed": true
},
{
"check": {
"type": "tool_is",
"value": "calculator"
},
"passed": true
}
],
"human_rating": null,
"human_notes": ""
}
]
}
@@ -60,6 +60,17 @@ def run_check(check: dict, answer: str) -> bool:
if check["type"] == "fenced_code":
pattern = rf"```{re.escape(check['language'])}\b[\s\S]+?```"
return re.search(pattern, answer, re.IGNORECASE) is not None
if check["type"] == "bullet_count":
count = sum(
re.match(r"^\s*(?:[-*+•]\s+|\d{1,2}[.)]\s+)", line) is not None
for line in answer.splitlines()
)
return count == int(check["value"])
if check["type"] == "single_sentence":
if "\n" in answer.strip():
return False
endings = re.findall(r"[.!?؟]+(?=\s|$)", answer.strip())
return len(endings) == int(check.get("sentence_count", 1))
raise ValueError(f"Unsupported automatic check: {check['type']}")
@@ -0,0 +1,22 @@
from __future__ import annotations
import unittest
from scripts.run_model_eval import run_check
class ModelEvalCheckTests(unittest.TestCase):
def test_bullet_count_accepts_numbered_three_point_answer(self) -> None:
answer = "1. أول نقطة\n2. ثاني نقطة\n3. ثالث نقطة"
self.assertTrue(run_check({"type": "bullet_count", "value": 3}, answer))
self.assertFalse(run_check({"type": "bullet_count", "value": 2}, answer))
def test_single_sentence_rejects_multiple_sentences_and_lines(self) -> None:
self.assertTrue(run_check({"type": "single_sentence"}, "إجابة واحدة."))
self.assertFalse(run_check({"type": "single_sentence"}, "جملة أولى. جملة ثانية."))
self.assertFalse(run_check({"type": "single_sentence"}, "سطر أول\nسطر ثان."))
if __name__ == "__main__":
unittest.main()