diff --git a/SovereignAI-Starter/README.md b/SovereignAI-Starter/README.md index 0bc73b8..38e78d0 100644 --- a/SovereignAI-Starter/README.md +++ b/SovereignAI-Starter/README.md @@ -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 بعينه بالنموذج. diff --git a/SovereignAI-Starter/ROADMAP.md b/SovereignAI-Starter/ROADMAP.md index f92a2fd..0357dba 100644 --- a/SovereignAI-Starter/ROADMAP.md +++ b/SovereignAI-Starter/ROADMAP.md @@ -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. diff --git a/SovereignAI-Starter/evals/baseline_arabic_programming.jsonl b/SovereignAI-Starter/evals/baseline_arabic_programming.jsonl index a4e58ff..1974cc0 100644 --- a/SovereignAI-Starter/evals/baseline_arabic_programming.jsonl +++ b/SovereignAI-Starter/evals/baseline_arabic_programming.jsonl @@ -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 دون ادعاء غير صحيح"]} diff --git a/SovereignAI-Starter/evals/results/gemma4_e2b_2026-10-03_183058.json b/SovereignAI-Starter/evals/results/gemma4_e2b_2026-10-03_183058.json new file mode 100644 index 0000000..5b3773c --- /dev/null +++ b/SovereignAI-Starter/evals/results/gemma4_e2b_2026-10-03_183058.json @@ -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": "" + } + ] +} diff --git a/SovereignAI-Starter/evals/results/qwen2.5_1.5b-instruct-q4_K_M_2026-10-03_183140.json b/SovereignAI-Starter/evals/results/qwen2.5_1.5b-instruct-q4_K_M_2026-10-03_183140.json new file mode 100644 index 0000000..ad8f96b --- /dev/null +++ b/SovereignAI-Starter/evals/results/qwen2.5_1.5b-instruct-q4_K_M_2026-10-03_183140.json @@ -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": "" + } + ] +} diff --git a/SovereignAI-Starter/scripts/run_model_eval.py b/SovereignAI-Starter/scripts/run_model_eval.py index 3072ccb..4e49d5f 100644 --- a/SovereignAI-Starter/scripts/run_model_eval.py +++ b/SovereignAI-Starter/scripts/run_model_eval.py @@ -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']}") diff --git a/SovereignAI-Starter/tests/test_model_eval.py b/SovereignAI-Starter/tests/test_model_eval.py new file mode 100644 index 0000000..90c5b35 --- /dev/null +++ b/SovereignAI-Starter/tests/test_model_eval.py @@ -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()