fix: show knowledge excerpts when Gemma abstains
This commit is contained in:
@@ -3,6 +3,7 @@ __pycache__/
|
|||||||
*.py[cod]
|
*.py[cod]
|
||||||
.env
|
.env
|
||||||
.test-runtime/
|
.test-runtime/
|
||||||
|
/tmp/
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
*.sqlite3-shm
|
*.sqlite3-shm
|
||||||
*.sqlite3-wal
|
*.sqlite3-wal
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
## الحالة الحالية — 2026-10-04
|
## الحالة الحالية — 2026-10-04
|
||||||
|
|
||||||
|
- 2026-10-04 — تدقيق حالة «العمل على مشروع»: تأكد وجود إنشاء مجلد جديد أو تسجيل مجلد قائم من Flutter، وربطه بحساب المستخدم في SQLite، وفتح الملفات والبحث فيها واقتراح إنشاء/تعديل ملف عبر diff وموافقة صريحة. التطبيق وFastAPI/Gemma 4/SQLite يعملون محليًا الآن. الوكيل لا يشغّل بعد أوامر البناء أو الاختبارات؛ prototype AppContainer غير مدمج، وحد التخزين الصلب لم يثبت، لذا التنفيذ الآلي للمشروع ليس مدعومًا. في اختبار الحالتين على API التطبيق وجد الاسترجاع الدليل 2/2 لكن Gemma امتنعت مرة بصياغة عربية جديدة؛ أضيفت الصياغة لاختبار fallback، واختبارات مهارات الوكيل 21/21. أُعيد تشغيل API التطبيق على 8000 من الشفرة الحالية مع قاعدة SQLite نفسها؛ `/health` أكد Gemma 4 وSQLite وبقاء ملف قاعدة المحادثات. تحقق الاختبار الحي من السؤالين: الأول عرض مقتطف `migration.md` مع مصدره، والثاني ذكر الحقول الثلاثة؛ ثم أزال فهرس الاختبار وتسجيل المشروع والجلسة (`evals/results/retrieval_live_api_fix_2026-10-04.json`). التفاصيل في `evals/MODEL_REVIEW_2026-10.md`.
|
||||||
|
|
||||||
- 2026-10-04 — تسجيل المشاريع في الواجهة والـAPI: عند اختيار مجلد من Flutter يُسجّل API المحلي المسار في SQLite تحت هوية الحساب (`user_workspace_roots`) ثم يسرد الملفات المدعومة؛ عند إزالة المشروع يُلغى التسجيل ولا تُحذف الملفات. أضيف POST/DELETE لـ`/v1/agent/projects` ويُسمح بهما عبر loopback فقط. اختبارات API تثبت التسجيل والقراءة لصاحب المشروع، رفض الحساب الآخر، منع تسجيل مجلد متداخل بين حسابين أو مع جذر خصصه مسؤول لحساب آخر (409)، وإلغاء الوصول بعد الحذف أو بعد حذف المجلد الأصلي. اختبار حي على مجلد خارج جذر المنتج قرأ `README.md`، وسأل Gemma عبر الوكيل فأجاب من المحتوى باسم `Cedar`، ثم تحقق رفض الوصول بعد الإلغاء. اختبارات Python ذات الصلة 10/10 وFlutter 17/17، و`flutter analyze` بلا ملاحظات و`compileall` ناجح. أُعيد تشغيل API وفحص `/health` (`ok`, Gemma 4)، وتأكد وجود مساري POST/DELETE في OpenAPI، وبُني وشُغّل Windows Debug الحالي (PID 1328). تشغيل أوامر البناء/الاختبار من الوكيل لم يُدمج بعد؛ عزل نظام التشغيل لهذا التشغيل ما زال مفتوحًا.
|
- 2026-10-04 — تسجيل المشاريع في الواجهة والـAPI: عند اختيار مجلد من Flutter يُسجّل API المحلي المسار في SQLite تحت هوية الحساب (`user_workspace_roots`) ثم يسرد الملفات المدعومة؛ عند إزالة المشروع يُلغى التسجيل ولا تُحذف الملفات. أضيف POST/DELETE لـ`/v1/agent/projects` ويُسمح بهما عبر loopback فقط. اختبارات API تثبت التسجيل والقراءة لصاحب المشروع، رفض الحساب الآخر، منع تسجيل مجلد متداخل بين حسابين أو مع جذر خصصه مسؤول لحساب آخر (409)، وإلغاء الوصول بعد الحذف أو بعد حذف المجلد الأصلي. اختبار حي على مجلد خارج جذر المنتج قرأ `README.md`، وسأل Gemma عبر الوكيل فأجاب من المحتوى باسم `Cedar`، ثم تحقق رفض الوصول بعد الإلغاء. اختبارات Python ذات الصلة 10/10 وFlutter 17/17، و`flutter analyze` بلا ملاحظات و`compileall` ناجح. أُعيد تشغيل API وفحص `/health` (`ok`, Gemma 4)، وتأكد وجود مساري POST/DELETE في OpenAPI، وبُني وشُغّل Windows Debug الحالي (PID 1328). تشغيل أوامر البناء/الاختبار من الوكيل لم يُدمج بعد؛ عزل نظام التشغيل لهذا التشغيل ما زال مفتوحًا.
|
||||||
- 2026-10-04 — إرشاد تسجيل المشروع من شاشة البداية: عند عدم فتح مساحة عمل تعرض الواجهة بطاقة تشرح أن المطلوب اختيار مجلد موجود وتوفّر زرًا مباشرًا؛ بعد فتحه تعرض اسم المشروع وقدرات الوكيل وحدوده، ومنها أن تشغيل أوامر البناء والاختبار غير متاح بعد. تسمية زر الشريط العلوي صارت «تسجيل مجلد مشروع موجود». اختبارات `widget_test.dart` نجحت 7/7 و`flutter analyze --no-pub` بلا ملاحظات. بُني Windows Debug من نسخة مستقلة في `%TEMP%` دون إغلاق النوافذ الموجودة؛ سجل MSBuild تحذيرًا أن Temp لا يُفضّل للبناء التزايدي، لكن البناء نجح خلال 359.7 ثانية. شُغلت النسخة (PID 44280، مقبض نافذة غير صفري ومستجيبة)، وأعاد API المحلي `/health` الحالة `ok` مع `gemma4:e2b` وSQLite والوكيل. هذه معاينة Debug وليست إصدارًا موزعًا.
|
- 2026-10-04 — إرشاد تسجيل المشروع من شاشة البداية: عند عدم فتح مساحة عمل تعرض الواجهة بطاقة تشرح أن المطلوب اختيار مجلد موجود وتوفّر زرًا مباشرًا؛ بعد فتحه تعرض اسم المشروع وقدرات الوكيل وحدوده، ومنها أن تشغيل أوامر البناء والاختبار غير متاح بعد. تسمية زر الشريط العلوي صارت «تسجيل مجلد مشروع موجود». اختبارات `widget_test.dart` نجحت 7/7 و`flutter analyze --no-pub` بلا ملاحظات. بُني Windows Debug من نسخة مستقلة في `%TEMP%` دون إغلاق النوافذ الموجودة؛ سجل MSBuild تحذيرًا أن Temp لا يُفضّل للبناء التزايدي، لكن البناء نجح خلال 359.7 ثانية. شُغلت النسخة (PID 44280، مقبض نافذة غير صفري ومستجيبة)، وأعاد API المحلي `/health` الحالة `ok` مع `gemma4:e2b` وSQLite والوكيل. هذه معاينة Debug وليست إصدارًا موزعًا.
|
||||||
- 2026-10-04 — إنشاء مجلد مشروع جديد على سطح المكتب: يختار المستخدم مجلدًا أبًا واسمًا، فيُنشأ مجلد فارغ ثم يُسجّل للوكيل؛ يمكن بعدها طلب إنشاء ملفات مع مراجعة diff وموافقة قبل الحفظ. قُيدت الأسماء لمنع المسارات والمجلدات المخفية والمستثناة وأسماء Windows المحجوزة، ورفض التصادم دون استبدال الموجود. الميزة متاحة على Windows/macOS/Linux الأصلي فقط؛ لا يظهر الزر على الويب وAndroid وiOS. اختبارات خدمة المجلد 3/3، مجموعة Flutter كاملة 22/22، واختبار الواجهة 7/7، و`flutter analyze --no-pub` بلا ملاحظات. بُني Windows Debug بنجاح خلال 352.1 ثانية إلى نسخة مستقلة في `%TEMP%`؛ لم تُشغّل النسخة لتجنب إضافة نافذة خامسة فوق نوافذ Debug المفتوحة. تشغيل أوامر البناء والاختبار داخل المشروع ما زال غير متاح لحين اكتمال العزل.
|
- 2026-10-04 — إنشاء مجلد مشروع جديد على سطح المكتب: يختار المستخدم مجلدًا أبًا واسمًا، فيُنشأ مجلد فارغ ثم يُسجّل للوكيل؛ يمكن بعدها طلب إنشاء ملفات مع مراجعة diff وموافقة قبل الحفظ. قُيدت الأسماء لمنع المسارات والمجلدات المخفية والمستثناة وأسماء Windows المحجوزة، ورفض التصادم دون استبدال الموجود. الميزة متاحة على Windows/macOS/Linux الأصلي فقط؛ لا يظهر الزر على الويب وAndroid وiOS. اختبارات خدمة المجلد 3/3، مجموعة Flutter كاملة 22/22، واختبار الواجهة 7/7، و`flutter analyze --no-pub` بلا ملاحظات. بُني Windows Debug بنجاح خلال 352.1 ثانية إلى نسخة مستقلة في `%TEMP%`؛ لم تُشغّل النسخة لتجنب إضافة نافذة خامسة فوق نوافذ Debug المفتوحة. تشغيل أوامر البناء والاختبار داخل المشروع ما زال غير متاح لحين اكتمال العزل.
|
||||||
|
|||||||
@@ -727,6 +727,8 @@ def _is_knowledge_answer_insufficient(answer: str) -> bool:
|
|||||||
markers = (
|
markers = (
|
||||||
"لم يتم العثور على مقطع",
|
"لم يتم العثور على مقطع",
|
||||||
"لم يتم العثور على مقاطع",
|
"لم يتم العثور على مقاطع",
|
||||||
|
"لا يوجد مقطع",
|
||||||
|
"لا توجد مقاطع",
|
||||||
"لم أجد",
|
"لم أجد",
|
||||||
"لا يحتوي على معلومات مباشرة",
|
"لا يحتوي على معلومات مباشرة",
|
||||||
"لا تحتوي على معلومات مباشرة",
|
"لا تحتوي على معلومات مباشرة",
|
||||||
|
|||||||
@@ -50,6 +50,16 @@
|
|||||||
|
|
||||||
أضيف في 2026-10-04 مسار احتياطي يعرض مقتطفات المعرفة عندما تكون إجابة النموذج امتناعًا أو إحالة إلى المصدر بلا محتوى. اجتاز اختبار الوكيل الكامل 21/21، ثم أعاد API مؤقت مع Gemma 4 تشغيل الحالتين: `database-version-en` عرض نص `migration.md` الحرفي مع المصدر، و`model-routing-ar` أجاب من `routing.md`. بقي ترتيب المرجع والدليل 1/1 في الحالتين. شُغّل API على منفذ وقاعدة مؤقتين، وأكد المشغّل حذف مستندات الفهرس وإلغاء تسجيل مساحة العمل قبل إيقاف الخدمة. النتيجة تثبت أن المحتوى المطلوب أصبح ظاهرًا في الحالتين، لكنها لا تحل محل إعادة تقييم المجموعة كاملة أو مراجعة بشرية.
|
أضيف في 2026-10-04 مسار احتياطي يعرض مقتطفات المعرفة عندما تكون إجابة النموذج امتناعًا أو إحالة إلى المصدر بلا محتوى. اجتاز اختبار الوكيل الكامل 21/21، ثم أعاد API مؤقت مع Gemma 4 تشغيل الحالتين: `database-version-en` عرض نص `migration.md` الحرفي مع المصدر، و`model-routing-ar` أجاب من `routing.md`. بقي ترتيب المرجع والدليل 1/1 في الحالتين. شُغّل API على منفذ وقاعدة مؤقتين، وأكد المشغّل حذف مستندات الفهرس وإلغاء تسجيل مساحة العمل قبل إيقاف الخدمة. النتيجة تثبت أن المحتوى المطلوب أصبح ظاهرًا في الحالتين، لكنها لا تحل محل إعادة تقييم المجموعة كاملة أو مراجعة بشرية.
|
||||||
|
|
||||||
|
## إعادة على الخدمة التي يستخدمها التطبيق
|
||||||
|
|
||||||
|
في 2026-10-04 أُعيد تشغيل الحالتين على `127.0.0.1:8000` بعد إعادة تشغيل خدمة التطبيق. البحث وجد الملف والدليل في الحالتين (Hit@1=1.0، وظهور الدليل=1.0)، وأجاب Gemma عن `model-routing-ar`، لكنها عادت في `database-version-en` إلى صيغة امتناع جديدة: «لا يوجد مقطع ... يجيب مباشرة». كشف ذلك فجوة في كاشف الامتناع، لا في الاسترجاع. أضيفت الصيغة إلى الكشف واختبار انحدار مباشر، واجتازت مجموعة مهارات الوكيل 21/21. التقرير `evals/results/retrieval_live_api_restart_2026-10-04.json` يوثق الحالة قبل الإصلاح.
|
||||||
|
|
||||||
|
## إعادة إصلاح الامتناع على API منفصل
|
||||||
|
|
||||||
|
بعد إضافة صيغة الامتناع الجديدة واختبارها، شُغّل API من الشفرة الحالية على `127.0.0.1:8129` بقاعدة SQLite اختبار مستقلة. أعاد `/health` الحالة `ok` مع Gemma 4. على سؤال الترحيل عرض الوكيل fallback بمقتطف `migration.md` الحرفي؛ وعلى سؤال التوجيه أعاد `requested_model` و`model` و`auto_routed` من `routing.md`. أُزيلت ملفات الفهرس، وأُلغي تسجيل المشروع والجلسة بعد التحقق. النتيجة المهيكلة في `evals/results/retrieval_live_api_fix_2026-10-04.json`.
|
||||||
|
|
||||||
|
أُعيد تشغيل API التطبيق على `127.0.0.1:8000` بالشفرة الحالية مع توجيهه إلى ملف SQLite الموجود نفسه. أكد `/health` Gemma 4 وSQLite، وبقي ملف قاعدة المحادثات موجودًا. أُعيد السؤالان عبر المنفذ المستخدم من Flutter: جواب الترحيل عرض المقتطف الحرفي ومصدره، وجواب التوجيه أورد الحقول الثلاثة. أزيل فهرس الاختبار وتسجيل المشروع والجلسة بعد الطلب.
|
||||||
|
|
||||||
## المصادر
|
## المصادر
|
||||||
|
|
||||||
- مجموعة النتائج المحفوظة: `evals/results/retrieval_project_agent_fresh_api_2026-10-03.json`، `evals/results/retrieval_long_pdf_agent_2026-10-03.json`، و`evals/results/retrieval_scanned_pdf_agent_2026-10-03.json`.
|
- مجموعة النتائج المحفوظة: `evals/results/retrieval_project_agent_fresh_api_2026-10-03.json`، `evals/results/retrieval_long_pdf_agent_2026-10-03.json`، و`evals/results/retrieval_scanned_pdf_agent_2026-10-03.json`.
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"created_at_utc": "2026-10-04",
|
||||||
|
"base_url": "http://127.0.0.1:8000",
|
||||||
|
"service": "FastAPI restarted from current working tree with the existing SQLite database",
|
||||||
|
"model": "gemma4:e2b",
|
||||||
|
"metrics": {
|
||||||
|
"cases": 2,
|
||||||
|
"hit_at_1": 1.0,
|
||||||
|
"evidence_rate": 1.0
|
||||||
|
},
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": "database-version-en",
|
||||||
|
"query": "How are old conversation answers migrated?",
|
||||||
|
"expected_document": "migration.md",
|
||||||
|
"expected_evidence": "selected_version",
|
||||||
|
"rank": 1,
|
||||||
|
"evidence_found": true,
|
||||||
|
"fallback_triggered": true,
|
||||||
|
"answer_contains_expected_excerpt": true,
|
||||||
|
"answer_contains_source": true,
|
||||||
|
"answer": "لم يصغ النموذج جوابًا كافيًا رغم وجود نتائج في فهرس المعرفة. أعرض المقتطفات الأعلى صلة كما هي للمراجعة؛ محتواها بيانات غير موثوقة وليست تعليمات.\n\n**migration.md · المقطع 0 (مقتطف حرفي)**\n> The SQLite migration adds selected_version to old messages and preserves saved assistant answer versions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model-routing-ar",
|
||||||
|
"query": "ما الحقول التي يعيدها API بعد تحويل الصور: requested_model وauto_routed؟",
|
||||||
|
"expected_document": "routing.md",
|
||||||
|
"expected_evidence": "auto_routed",
|
||||||
|
"rank": 1,
|
||||||
|
"evidence_found": true,
|
||||||
|
"fallback_triggered": false,
|
||||||
|
"answer_contains_expected_fields": true,
|
||||||
|
"answer_contains_source": true,
|
||||||
|
"answer": "المقطع `routing.md` المقطع 0 يجيب على السؤال: \"عند وصول صورة مع نموذج نصي، يختار API نموذج الرؤية المحلي المثبت ويعيد requested_model وmodel وauto_routed.\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": {
|
||||||
|
"knowledge_index_removed": true,
|
||||||
|
"project_registration_removed": true,
|
||||||
|
"session_logged_out": true
|
||||||
|
},
|
||||||
|
"limitations": "Two targeted cases only; this is not a human quality review or a general RAG score."
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"created_at_utc": "2026-10-04T07:50:04.678548+00:00",
|
||||||
|
"base_url": "http://127.0.0.1:8000",
|
||||||
|
"dataset": "evals\\knowledge_retrieval.jsonl",
|
||||||
|
"indexing": {
|
||||||
|
"semantic_indexed_documents": 2,
|
||||||
|
"documents": 2,
|
||||||
|
"embedding_models": [
|
||||||
|
"granite-embedding:278m"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"cases": 2,
|
||||||
|
"hit_at_1": 1.0,
|
||||||
|
"hit_at_3": 1.0,
|
||||||
|
"mean_reciprocal_rank": 1.0,
|
||||||
|
"evidence_rate": 1.0
|
||||||
|
},
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": "database-version-en",
|
||||||
|
"query": "How are old conversation answers migrated?",
|
||||||
|
"expected_document": "migration.md",
|
||||||
|
"expected_evidence": "selected_version",
|
||||||
|
"rank": 1,
|
||||||
|
"top_paths": [
|
||||||
|
"migration.md",
|
||||||
|
"routing.md"
|
||||||
|
],
|
||||||
|
"expected_document_chunks": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"evidence_found": true,
|
||||||
|
"search_mode": "hybrid",
|
||||||
|
"agent_answer": "لا يوجد مقطع في فهرس المعرفة المحلي يجيب مباشرة على السؤال \"How are old conversation answers migrated?\".",
|
||||||
|
"agent_tool": "search_knowledge",
|
||||||
|
"agent_files": [
|
||||||
|
"migration.md",
|
||||||
|
"routing.md"
|
||||||
|
],
|
||||||
|
"agent_steps": [
|
||||||
|
{
|
||||||
|
"tool": "search_knowledge",
|
||||||
|
"status": "completed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"human_rating": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model-routing-ar",
|
||||||
|
"query": "ما الحقول التي يعيدها API بعد تحويل الصور: requested_model وauto_routed؟",
|
||||||
|
"expected_document": "routing.md",
|
||||||
|
"expected_evidence": "auto_routed",
|
||||||
|
"rank": 1,
|
||||||
|
"top_paths": [
|
||||||
|
"routing.md",
|
||||||
|
"migration.md"
|
||||||
|
],
|
||||||
|
"expected_document_chunks": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"evidence_found": true,
|
||||||
|
"search_mode": "hybrid",
|
||||||
|
"agent_answer": "المقطع الذي يجيب على سؤالك هو:\n\"عند وصول صورة مع نموذج نصي، يختار API نموذج الرؤية المحلي المثبت ويعيد requested_model وmodel وauto_routed.\"\nالملف: routing.md، المقطع 0",
|
||||||
|
"agent_tool": "search_knowledge",
|
||||||
|
"agent_files": [
|
||||||
|
"routing.md",
|
||||||
|
"migration.md"
|
||||||
|
],
|
||||||
|
"agent_steps": [
|
||||||
|
{
|
||||||
|
"tool": "search_knowledge",
|
||||||
|
"status": "completed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"human_rating": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"note": "Small deterministic fixture set. Retrieval metrics cover source ranking and evidence presence, not general RAG quality. Optional agent answers are retained for human review and are not auto-scored."
|
||||||
|
}
|
||||||
@@ -255,6 +255,7 @@ class AgentSkillTests(unittest.TestCase):
|
|||||||
"text": "The SQLite migration adds selected_version to old messages and preserves saved assistant answer versions.",
|
"text": "The SQLite migration adds selected_version to old messages and preserves saved assistant answer versions.",
|
||||||
}
|
}
|
||||||
refusal = "لم يتم العثور على مقاطع تجيب مباشرة على السؤال في فهرس المعرفة المحلي."
|
refusal = "لم يتم العثور على مقاطع تجيب مباشرة على السؤال في فهرس المعرفة المحلي."
|
||||||
|
live_refusal = 'لا يوجد مقطع في فهرس المعرفة المحلي يجيب مباشرة على السؤال "How are old conversation answers migrated?".'
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"app.main._search_local_knowledge",
|
"app.main._search_local_knowledge",
|
||||||
@@ -280,6 +281,7 @@ class AgentSkillTests(unittest.TestCase):
|
|||||||
|
|
||||||
citation_only = "المقطع `routing.md`, المقطع 0."
|
citation_only = "المقطع `routing.md`, المقطع 0."
|
||||||
self.assertTrue(_is_knowledge_answer_insufficient(refusal))
|
self.assertTrue(_is_knowledge_answer_insufficient(refusal))
|
||||||
|
self.assertTrue(_is_knowledge_answer_insufficient(live_refusal))
|
||||||
self.assertTrue(_is_knowledge_answer_insufficient(citation_only))
|
self.assertTrue(_is_knowledge_answer_insufficient(citation_only))
|
||||||
self.assertFalse(_is_knowledge_answer_insufficient("تضيف الهجرة selected_version."))
|
self.assertFalse(_is_knowledge_answer_insufficient("تضيف الهجرة selected_version."))
|
||||||
self.assertIn("لم يصغ النموذج جوابًا كافيًا رغم وجود نتائج", result["result"])
|
self.assertIn("لم يصغ النموذج جوابًا كافيًا رغم وجود نتائج", result["result"])
|
||||||
|
|||||||
Reference in New Issue
Block a user