Spaces:
Running
Running
| import re | |
| import json | |
| import logging | |
| from llm_provider import llm | |
| logger = logging.getLogger("classifier") | |
| def classify_request(text=None, file_bytes=None, file_type=None): | |
| # تصنيف الملفات أولاً | |
| if file_bytes and file_type: | |
| if "image" in file_type: | |
| return {"task": "image_analysis", "domain": "site"} | |
| if "pdf" in file_type: | |
| return {"task": "pdf_analysis", "domain": "office"} | |
| if not text: | |
| return {"task": "general_chat", "domain": "general"} | |
| t = text.lower().strip() | |
| # ========== 1. القواعد الهندسية الصارمة (Priority 1) ========== | |
| # أ- التصميم الإنشائي (أهم حاجة) | |
| # لو فيه أرقام (أبعاد) + كلمة إنشائية -> تصميم | |
| has_numbers = bool(re.search(r'\d', t)) | |
| # قاموس شامل للعناصر الإنشائية | |
| structural_elements = { | |
| "كمرة": "beam_tool", "beam": "beam_tool", "ميدة": "beam_tool", | |
| "بلاطة": "slab_tool", "سقف": "slab_tool", "slab": "slab_tool", | |
| "عمود": "column_tool", "column": "column_tool", | |
| "قاعدة": "foundation_tool", "أساس": "foundation_tool", "foundation": "foundation_tool", | |
| "سلم": "stair_tool", "stair": "stair_tool", | |
| "جدار استنادي": "retaining_wall_tool", "retaining wall": "retaining_wall_tool" | |
| } | |
| for keyword, task in structural_elements.items(): | |
| if keyword in t: | |
| # لو فيه أرقام، يبقى أكيد تصميم | |
| if has_numbers: | |
| return {"task": task, "domain": "design"} | |
| # لو مفيش أرقام، ممكن يكون سؤال عام "ايه هي الكمرة؟"، نوجهه للمحادثة العامة | |
| # إلا لو الكلمة صريحة زي "صمم" أو "حساب" | |
| if any(word in t for word in ["صمم", "حساب", "تصميم", "design", "calculate"]): | |
| return {"task": task, "domain": "design"} | |
| # ب- الحصر (BOQ) | |
| boq_keywords = ["حصر", "كمية", "كميات", "حديد", "خرسانة", "بلوك", "أسمنت", "رمل", "طوب", | |
| "نحسب", "حساب", "تكلفة", "سعر", "boq", "ton", "m3", "طن", "متر مكعب"] | |
| if any(word in t for word in boq_keywords): | |
| return {"task": "boq_tool", "domain": "boq"} | |
| # ج- الموقع والتقارير | |
| site_keywords = ["تشك ليست", "checklist", "استلام", "مراجعة", "معاينة", "موقع"] | |
| if any(word in t for word in site_keywords): | |
| return {"task": "checklist_tool", "domain": "site"} | |
| report_keywords = ["تقرير موقع", "تقرير يومي", "تسجيل يومي", "حالة الطقس", "عدد العمال"] | |
| if any(word in t for word in report_keywords): | |
| return {"task": "site_report", "domain": "site"} | |
| # ========== 2. قواعد السياق (Context Awareness) ========== | |
| # لو الجملة فيها أرقام كتير ومفيش كلمات مفتاحية -> ممكن يكون تصميم ضImplicit | |
| # مثال: "5 متر 2 طن" من غير كلمة كمرة -> نعتبره طلب تصميم عام | |
| if has_numbers and len(re.findall(r'\d+', t)) >= 2: | |
| # لو الجملة قصيرة وفيها أرقام -> تصميم | |
| if len(t.split()) < 10: | |
| return {"task": "beam_tool", "domain": "design"} | |
| # ========== 3. الذكاء الاصطناعي (LLM Fallback) ========== | |
| # لو مفيش أي قاعدة اشتغلت، نبعت للـ AI يحاول يفهم | |
| if len(t) > 10: | |
| try: | |
| import asyncio | |
| prompt = f""" | |
| Analyze this user request and determine the domain. | |
| Possible domains: 'design' (structural calculations), 'boq' (quantity takeoff), 'site' (site reports/checklists), 'general' (general engineering chat). | |
| Return JSON: {{"domain": "...", "task": "..."}} | |
| Text: "{text}" | |
| """ | |
| # هنا بنستخدم الموديلز المجانية اللي اشتغلت في الاختبار | |
| loop = asyncio.new_event_loop() | |
| asyncio.set_event_loop(loop) | |
| response = loop.run_until_complete(llm.generate_text(prompt, model_preference=["gemini-flash", "mistral-small", "gpt-4o-mini"])) | |
| loop.close() | |
| json_match = re.search(r'\{.*\}', response, re.DOTALL) | |
| if json_match: | |
| result = json.loads(json_match.group()) | |
| # لو الـ AI قال "design" أو "boq"، نصدقه | |
| if result.get("domain") in ["design", "boq", "site"]: | |
| # نضبط الـ task name | |
| task = result.get("task", "general_chat") | |
| if result["domain"] == "design": task = "beam_tool" | |
| if result["domain"] == "boq": task = "boq_tool" | |
| return {"task": task, "domain": result["domain"]} | |
| except Exception as e: | |
| logger.warning(f"LLM fallback failed: {e}") | |
| # ========== 4. الوضع الافتراضي (General Chat) ========== | |
| # لو كل المحاولات فشلت، نخليه يتكلم كـ "Blue" المهندس العام | |
| return {"task": "general_chat", "domain": "general"} |