| import os |
| import re |
| from typing import Optional |
|
|
| import openai |
| from dotenv import load_dotenv |
| from fastapi import FastAPI, Request |
| from fastapi.responses import HTMLResponse, JSONResponse |
| from pydantic import BaseModel |
|
|
| |
| |
| |
|
|
| load_dotenv() |
| api_key = os.environ.get("HUGGINGFACEHUB_API_TOKEN") |
|
|
| model_link = "meta-llama/Meta-Llama-3-8B-Instruct" |
| base_url = "https://router.huggingface.co/v1" |
|
|
| app = FastAPI() |
|
|
|
|
| class Message(BaseModel): |
| message: str |
|
|
|
|
| @app.get("/", response_class=HTMLResponse) |
| async def read_root(): |
| return "Welcome to Up to 12 Chat Processor" |
|
|
|
|
| @app.get("/health") |
| async def health(): |
| return {"ok": True, "service": "up"} |
|
|
|
|
| |
| |
| |
|
|
| INTENTS = { |
| "GREETING": "تحية/بداية محادثة", |
| "COURSES_MENU": "طلب قائمة الكورسات", |
| "COURSE_TYPE_DETAILS": "سؤال عن نوع كورس محدد", |
| "CENTER_INFO": "سؤال عن المركز", |
| "CHILDREN_COURSES": "كورس أطفال", |
| "ONLINE_COURSES": "كورسات أونلاين", |
| "WEEKEND_COURSES": "كورسات ويكند", |
| "HUMAN_AGENT": "طلب خدمة عملاء/موظف", |
| "OTHER": "غير معروف/خارج النطاق", |
| } |
|
|
|
|
| |
| |
| |
|
|
| KB = { |
| "center": { |
| "name": "ÄDK - Egyptian-German Cultural Centre", |
| "established": "1998", |
| "branches": "له فروع مختلفة في القاهرة", |
| "adults_levels": "للكبار من A1 لحد C1", |
| "children_levels": "للأطفال من A1 لحد B1", |
| "goethe": "أول معهد معتمد من Goethe Institute في الشرق الأوسط وشمال أفريقيا", |
| }, |
| "courses": { |
| "express": { |
| "title": "Express Courses", |
| "details": [ |
| "كورسات Super intensive لتعلم الألماني بسرعة وكفاءة", |
| "النظام ده Challenging وبيحتاج مجهود", |
| "ممكن تحجز Stage كاملة (A1 - A2 - B1)", |
| "3 محاضرات في الأسبوع", |
| "كل محاضرة 4 ساعات", |
| "مدة الـ stage شهرين ونص", |
| ], |
| }, |
| "intensive": { |
| "title": "Intensive Courses", |
| "details": [ |
| "ممكن تحجز Stage كاملة (A1 - A2 - B1)", |
| "محاضرتين في الأسبوع", |
| "كل محاضرة 4 ساعات", |
| "مدة الـ stage 3 شهور ونص", |
| ], |
| }, |
| "regular": { |
| "title": "Regular Courses", |
| "details": [ |
| "ممكن تحجز كل level لوحده من A1.1 لحد B1.3", |
| "محاضرتين في الأسبوع", |
| "كل محاضرة 4 ساعات", |
| "مدة الكورس شهر ونص", |
| ], |
| }, |
| "weekend": { |
| "title": "Weekend Courses", |
| "details": [ |
| "مناسبة للناس اللي مش فاضيين خلال الأسبوع بسبب شغل أو دراسة", |
| "بتتبع نفس outline وساعات الـ Regular", |
| ], |
| }, |
| "online": { |
| "title": "Online Courses", |
| "details": [ |
| "متاحة بنظام أونلاين", |
| "الحضور بيكون online", |
| ], |
| }, |
| "children": { |
| "title": "Children Courses", |
| "details": [ |
| "من سن 5 سنين لحد 15 سنة", |
| "الهدف منها تنمية مهارات الطفل واللغة", |
| "وكمان تشجيع التفكير الحر والنقدي والاستقلالية", |
| ], |
| }, |
| }, |
| } |
|
|
|
|
| |
| |
| |
|
|
| def normalize_arabic(text: str) -> str: |
| if not text: |
| return "" |
|
|
| text = text.strip().lower() |
|
|
| replacements = { |
| "أ": "ا", |
| "إ": "ا", |
| "آ": "ا", |
| "ة": "ه", |
| "ى": "ي", |
| "ؤ": "و", |
| "ئ": "ي", |
| } |
|
|
| for old, new in replacements.items(): |
| text = text.replace(old, new) |
|
|
| return text |
|
|
|
|
| def detect_gender(text: str) -> Optional[str]: |
| """ |
| Returns: |
| - 'male' |
| - 'female' |
| - None |
| """ |
| t = normalize_arabic(text) |
|
|
| |
| female_markers = [ |
| "انا مهتمه", |
| "مهتمه", |
| "عايزه", |
| "عاوزه", |
| "عاوزة", |
| "عايزة", |
| "حابه", |
| "حابة", |
| "محتاجه", |
| "محتاجة", |
| "انا ام", |
| "انا ماما", |
| "انا والده", |
| ] |
|
|
| male_markers = [ |
| "انا مهتم", |
| "مهتم", |
| "عايز", |
| "حابب", |
| "محتاج", |
| "انا اب", |
| "انا بابا", |
| "انا والد", |
| ] |
|
|
| for marker in female_markers: |
| if marker in t: |
| return "female" |
|
|
| for marker in male_markers: |
| if marker in t: |
| return "male" |
|
|
| return None |
|
|
|
|
| def choose_variant(gender: Optional[str], male_text: str, female_text: str, neutral_text: str) -> str: |
| if gender == "male": |
| return male_text |
| if gender == "female": |
| return female_text |
| return neutral_text |
|
|
|
|
| def detect_intent(text: str) -> str: |
| t = normalize_arabic(text) |
|
|
| |
| if any( |
| x in t |
| for x in [ |
| "خدمه العملاء", |
| "خدمة العملاء", |
| "موظف", |
| "حد يرد", |
| "اكلم", |
| "اتواصل", |
| "support", |
| "agent", |
| ] |
| ): |
| return "HUMAN_AGENT" |
|
|
| |
| if any(x in t for x in ["online", "اونلاين", "اون لاين", "zoom", "زووم", "من البيت"]): |
| return "ONLINE_COURSES" |
|
|
| if any(x in t for x in ["weekend", "ويكند", "الجمعة", "السبت", "عطله", "عطلة"]): |
| return "WEEKEND_COURSES" |
|
|
| if any(x in t for x in ["children", "kids", "اطفال", "طفل", "سنين", "سن كام", "سن قد ايه"]): |
| return "CHILDREN_COURSES" |
|
|
| if any(x in t for x in ["express", "اكسبريس", "سريع", "super intensive"]): |
| return "COURSE_TYPE_DETAILS" |
|
|
| if any(x in t for x in ["intensive", "مكثف", "مكثفه"]): |
| return "COURSE_TYPE_DETAILS" |
|
|
| if any(x in t for x in ["regular", "ريجولار", "منتظم"]): |
| return "COURSE_TYPE_DETAILS" |
|
|
| |
| if any( |
| x in t |
| for x in [ |
| "المركز", |
| "adk", |
| "ädk", |
| "اتاسس", |
| "اتأسس", |
| "تاريخ", |
| "goethe", |
| "معتمد", |
| "نبذه", |
| "نبذة", |
| ] |
| ): |
| return "CENTER_INFO" |
|
|
| |
| if any( |
| x in t |
| for x in [ |
| "كورسات", |
| "دورات", |
| "courses", |
| "الكورسات المتاحه", |
| "الكورسات المتاحة", |
| "عايز كورس", |
| "عايزه كورس", |
| "عايز اتعلم", |
| "عايزه اتعلم", |
| "الماني عندكم", |
| "ألماني", |
| "الماني", |
| ] |
| ): |
| return "COURSES_MENU" |
|
|
| |
| if re.search(r"\b(hi|hello|hey|start)\b", t) or any( |
| x in t for x in ["اهلا", "السلام", "هاي", "مرحبا", "ابدأ", "ابدء", "مسا", "صباح الخير"] |
| ): |
| return "GREETING" |
|
|
| return "OTHER" |
|
|
|
|
| def detect_course_subtype(text: str) -> Optional[str]: |
| t = normalize_arabic(text) |
|
|
| if any(x in t for x in ["express", "اكسبريس", "سريع", "super intensive"]): |
| return "express" |
| if any(x in t for x in ["intensive", "مكثف", "مكثفه"]): |
| return "intensive" |
| if any(x in t for x in ["regular", "ريجولار", "منتظم"]): |
| return "regular" |
| if any(x in t for x in ["weekend", "ويكند", "الجمعة", "السبت", "عطله", "عطلة"]): |
| return "weekend" |
| if any(x in t for x in ["online", "اونلاين", "اون لاين", "zoom", "زووم", "من البيت"]): |
| return "online" |
| if any(x in t for x in ["children", "kids", "اطفال", "طفل", "سنين", "سن كام", "سن قد ايه"]): |
| return "children" |
|
|
| return None |
|
|
|
|
| def detect_other_subtype(text: str) -> Optional[str]: |
| t = normalize_arabic(text) |
|
|
| if any(x in t for x in ["العنوان", "مكانكم فين", "مكانكو فين", "فين المكان", "لوكيشن", "الموقع فين"]): |
| return "ADDRESS_QUERY" |
|
|
| if any(x in t for x in ["مواعيد", "ميعاد", "الميعاد", "امتى", "متى", "الفتره", "الفترة", "صباحي", "مسائي", "بعد الظهر", "بالليل"]): |
| return "SCHEDULE_QUERY" |
|
|
| if any(x in t for x in ["التقديم", "اقدم", "اقدملكم", "اسجل", "اسجل ازاي", "التسجيل", "التحاق", "احجز", "الحجز"]): |
| return "REGISTRATION_QUERY" |
|
|
| if any(x in t for x in ["السعر", "الاسعار", "الأسعار", "بكام", "كم", "التكلفه", "التكلفة", "الرسوم"]): |
| return "PRICE_QUERY" |
|
|
| if any(x in t for x in ["فروع", "فرع", "branch", "branches"]): |
| return "BRANCH_QUERY" |
|
|
| if any(x in t for x in ["رقم", "واتساب", "تليفون", "اتواصل", "التواصل", "اكلم"]): |
| return "CONTACT_QUERY" |
|
|
| return None |
|
|
|
|
| def fallback_customer_service_reply(gender: Optional[str] = None) -> str: |
| line2 = choose_variant( |
| gender, |
| "تحب أوجّه استفسارك لفريق خدمة العملاء؟", |
| "تحبي أوجّه استفسارك لفريق خدمة العملاء؟", |
| "تحب أو تحبي أوجّه استفسارك لفريق خدمة العملاء؟" |
| ) |
| return ( |
| "تمام — عشان نضمن لك إجابة دقيقة، فريق خدمة العملاء هيقدر يفيدك أكتر.\n" |
| f"{line2}" |
| ) |
|
|
|
|
| def build_courses_menu_reply(gender: Optional[str] = None) -> str: |
| ask_line = choose_variant( |
| gender, |
| "تحب تعرف تفاصيل أنهي نوع؟", |
| "تحبي تعرفي تفاصيل أنهي نوع؟", |
| "تحب تعرف تفاصيل أنهي نوع؟" |
| ) |
| return ( |
| "عندنا الأنواع دي من كورسات الألماني:\n" |
| "Regular / Intensive / Express / Weekend / Online / Children\n" |
| f"{ask_line}" |
| ) |
|
|
|
|
| def build_center_info_reply(gender: Optional[str] = None) -> str: |
| center = KB["center"] |
| return ( |
| f"ÄDK اتأسس سنة {center['established']}، و{center['branches']}.\n" |
| f"الكورسات للكبار {center['adults_levels']}، وللأطفال {center['children_levels']}.\n" |
| f"وكمان هو {center['goethe']}.\n" |
| "لو عندك سؤال محدد، أقدر أوصله لخدمة العملاء." |
| ) |
|
|
|
|
| def build_course_details_reply(course_key: str, gender: Optional[str] = None) -> str: |
| details = KB["courses"][course_key]["details"] |
|
|
| if course_key == "express": |
| ask_line = choose_variant( |
| gender, |
| "تحب تعرف الفرق بينه وبين الـ Intensive؟", |
| "تحبي تعرفي الفرق بينه وبين الـ Intensive؟", |
| "تحب تعرف الفرق بينه وبين الـ Intensive؟" |
| ) |
| start_line = choose_variant( |
| gender, |
| "الـ Express مناسب لو عايز تتعلم بسرعة.", |
| "الـ Express مناسب لو عايزة تتعلمي بسرعة.", |
| "الـ Express مناسب لو حابب أو حابة تتعلم بسرعة." |
| ) |
| return ( |
| f"{start_line}\n" |
| f"{details[0]}، و{details[1]}.\n" |
| f"ممكن تحجز Stage كاملة، والنظام {details[3]}، {details[4]}.\n" |
| f"مدة الـ stage {details[5].replace('مدة الـ stage ', '')}.\n" |
| f"{ask_line}" |
| ) |
|
|
| if course_key == "intensive": |
| ask_line = choose_variant( |
| gender, |
| "تحب أقارن لك بينه وبين الـ Regular؟", |
| "تحبي أقارن لك بينه وبين الـ Regular؟", |
| "تحب أقارن لك بينه وبين الـ Regular؟" |
| ) |
| return ( |
| "الـ Intensive ممكن تحجز فيه Stage كاملة من A1 لحد B1.\n" |
| f"النظام {details[1]}، و{details[2]}.\n" |
| f"مدة الـ stage {details[3].replace('مدة الـ stage ', '')}.\n" |
| f"{ask_line}" |
| ) |
|
|
| if course_key == "regular": |
| ask_line = choose_variant( |
| gender, |
| "تحب تعرف لو ده أنسب لك ولا الـ Intensive؟", |
| "تحبي تعرفي لو ده أنسب لك ولا الـ Intensive؟", |
| "تحب تعرف لو ده أنسب لك ولا الـ Intensive؟" |
| ) |
| start_line = choose_variant( |
| gender, |
| "الـ Regular مناسب لو تحب تمشي level level.", |
| "الـ Regular مناسب لو تحبي تمشي level level.", |
| "الـ Regular مناسب لو حابب أو حابة تمشي level level." |
| ) |
| return ( |
| f"{start_line}\n" |
| "ممكن تحجز كل level لوحده من A1.1 لحد B1.3.\n" |
| f"النظام {details[1]}، و{details[2]}.\n" |
| f"مدة الكورس {details[3].replace('مدة الكورس ', '')}.\n" |
| f"{ask_line}" |
| ) |
|
|
| if course_key == "weekend": |
| ask_line = choose_variant( |
| gender, |
| "تحب حضور في الويكند ولا كنت بتفكر في أونلاين؟", |
| "تحبي حضور في الويكند ولا كنتِ بتفكري في أونلاين؟", |
| "تحب حضور في الويكند ولا كنت بتفكر في أونلاين؟" |
| ) |
| return ( |
| "الـ Weekend مناسب لو إنت مشغول خلال الأسبوع بسبب شغل أو دراسة.\n" |
| "وبيمشي بنفس outline وساعات الـ Regular.\n" |
| f"{ask_line}" |
| ) |
|
|
| if course_key == "online": |
| ask_line = choose_variant( |
| gender, |
| "تحب أقولك على الأنواع المتاحة؟", |
| "تحبي أقولك على الأنواع المتاحة؟", |
| "تحب أقولك على الأنواع المتاحة؟" |
| ) |
| return ( |
| "عندنا كورسات أونلاين، والحضور بيكون online.\n" |
| "ولو مناسب لك النظام ده، أقدر أساعدك تختار النوع الأقرب لاحتياجك.\n" |
| f"{ask_line}" |
| ) |
|
|
| if course_key == "children": |
| ask_line = choose_variant( |
| gender, |
| "تحب أعرفك على نظام الأطفال بشكل أقرب؟", |
| "تحبي أعرفك على نظام الأطفال بشكل أقرب؟", |
| "تحب أعرفك على نظام الأطفال بشكل أقرب؟" |
| ) |
| return ( |
| "عندنا كورسات أطفال من سن 5 لحد 15 سنة.\n" |
| "الهدف منها تنمية مهارات الطفل واللغة.\n" |
| "وكمان بتشجع التفكير الحر والنقدي والاستقلالية.\n" |
| f"{ask_line}" |
| ) |
|
|
| return fallback_customer_service_reply(gender) |
|
|
|
|
| def generate_other_reply(user_text: str, gender: Optional[str] = None) -> str: |
| subtype = detect_other_subtype(user_text) |
|
|
| if subtype == "ADDRESS_QUERY": |
| ask_line = choose_variant( |
| gender, |
| "تحب أبلّغهم إنك مهتم بالحضور؟", |
| "تحبي أبلّغهم إنك مهتمة بالحضور؟", |
| "تحب أبلّغهم إنك مهتم أو مهتمة بالحضور؟" |
| ) |
| return ( |
| "تمام، بالنسبة للعنوان ده خاص بالكورسات الحضوري.\n" |
| "أقدر أوصّل استفسارك لفريق خدمة العملاء عشان يبعثوا لك الفرع المناسب.\n" |
| f"{ask_line}" |
| ) |
|
|
| if subtype == "SCHEDULE_QUERY": |
| line1 = choose_variant( |
| gender, |
| "تمام، المواعيد بتختلف حسب إذا كنت مهتم بأونلاين أو حضور، وكمان حسب الفترة المناسبة لك.", |
| "تمام، المواعيد بتختلف حسب إذا كنتِ مهتمة بأونلاين أو حضور، وكمان حسب الفترة المناسبة لك.", |
| "تمام، المواعيد بتختلف حسب إذا كنت مهتم بأونلاين أو حضور، وكمان حسب الفترة المناسبة لك." |
| ) |
| line2 = choose_variant( |
| gender, |
| "قولّي تفضّل أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء.", |
| "قولّي تفضّلي أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء.", |
| "قولّي تفضّل أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء." |
| ) |
| return f"{line1}\n{line2}" |
|
|
| if subtype == "REGISTRATION_QUERY": |
| ask_line = choose_variant( |
| gender, |
| "تحب التقديم لكورس أونلاين ولا حضور؟", |
| "تحبي التقديم لكورس أونلاين ولا حضور؟", |
| "تحب التقديم لكورس أونلاين ولا حضور؟" |
| ) |
| return ( |
| "تمام، أقدر أوجّه استفسارك لفريق خدمة العملاء عشان يوضحوا لك خطوات التقديم أو التسجيل بشكل دقيق.\n" |
| f"{ask_line}" |
| ) |
|
|
| if subtype == "PRICE_QUERY": |
| line2 = choose_variant( |
| gender, |
| "قولّي مهتم بأنهي نوع كورس، وأنا أوجّه استفسارك لخدمة العملاء بشكل أوضح.", |
| "قولّي مهتمة بأنهي نوع كورس، وأنا أوجّه استفسارك لخدمة العملاء بشكل أوضح.", |
| "قولّي مهتم بأنهي نوع كورس، وأنا أوجّه استفسارك لخدمة العملاء بشكل أوضح." |
| ) |
| return ( |
| "تمام، الأسعار بتختلف حسب نوع الكورس والنظام المناسب لك.\n" |
| f"{line2}" |
| ) |
|
|
| if subtype == "BRANCH_QUERY": |
| ask_line = choose_variant( |
| gender, |
| "تحب أبلّغهم إنك مهتم بكورس حضور؟", |
| "تحبي أبلّغهم إنك مهتمة بكورس حضور؟", |
| "تحب أبلّغهم إنك مهتم بكورس حضور؟" |
| ) |
| return ( |
| "تمام، أقدر أوجّه استفسارك لفريق خدمة العملاء عشان يوضحوا لك الفرع المناسب.\n" |
| f"{ask_line}" |
| ) |
|
|
| if subtype == "CONTACT_QUERY": |
| return ( |
| "تمام، أقدر أوصّل استفسارك لفريق خدمة العملاء.\n" |
| "قولّي بس سؤالك عن الكورسات، التقديم، المواعيد، ولا الحضور؟" |
| ) |
|
|
| return "" |
|
|
|
|
| def sanitize_reply(text: str, gender: Optional[str] = None) -> str: |
| if not text or not text.strip(): |
| return fallback_customer_service_reply(gender) |
|
|
| text = text.strip() |
|
|
| replacements = { |
| "حضرتك": "إنت", |
| "مرحبا": "أهلا", |
| "مرحباً": "أهلا", |
| "يسعدني": "أقدر", |
| "يهتم بيك": "تحب", |
| "اقترحك": "أقترح لك", |
| "يمكنني": "أقدر", |
| "العميل": "إنت", |
| "المستخدم": "إنت", |
| "سأقوم": "هقوم", |
| "سوف": "ه", |
| } |
|
|
| for old, new in replacements.items(): |
| text = text.replace(old, new) |
|
|
| text = re.sub(r"[•\-]{2,}", "-", text) |
|
|
| lines = [line.strip() for line in text.splitlines() if line.strip()] |
| lines = lines[:5] |
| text = "\n".join(lines) |
|
|
| english_words = re.findall(r"[A-Za-z]{4,}", text) |
| if len(english_words) > 10: |
| return fallback_customer_service_reply(gender) |
|
|
| return text |
|
|
|
|
| |
| |
| |
|
|
| def generate_rule_based_reply(intent: str, user_text: str, gender: Optional[str] = None) -> str: |
| subtype = detect_course_subtype(user_text) |
|
|
| if intent == "GREETING": |
| greet_line = choose_variant( |
| gender, |
| "أهلا بيك في ÄDK.", |
| "أهلا بيكي في ÄDK.", |
| "أهلا بيك في ÄDK." |
| ) |
| ask_line = choose_variant( |
| gender, |
| "تحب تبدأ بإيه؟", |
| "تحبي تبدئي بإيه؟", |
| "تحب تبدأ بإيه؟" |
| ) |
| return ( |
| f"{greet_line}\n" |
| "أقدر أساعدك في أنواع الكورسات، تفاصيل نوع معين، أو معلومات عن المركز.\n" |
| f"{ask_line}" |
| ) |
|
|
| if intent == "HUMAN_AGENT": |
| ask_line = choose_variant( |
| gender, |
| "تحب استفسارك يكون عن أونلاين ولا حضور؟", |
| "تحبي استفسارك يكون عن أونلاين ولا حضور؟", |
| "تحب استفسارك يكون عن أونلاين ولا حضور؟" |
| ) |
| return ( |
| "تمام — هسجّل استفسارك لفريق خدمة العملاء عشان يردوا عليك بشكل أدق.\n" |
| f"{ask_line}" |
| ) |
|
|
| if intent == "COURSES_MENU": |
| return build_courses_menu_reply(gender) |
|
|
| if intent == "CENTER_INFO": |
| return build_center_info_reply(gender) |
|
|
| if intent == "ONLINE_COURSES": |
| return build_course_details_reply("online", gender) |
|
|
| if intent == "WEEKEND_COURSES": |
| return build_course_details_reply("weekend", gender) |
|
|
| if intent == "CHILDREN_COURSES": |
| return build_course_details_reply("children", gender) |
|
|
| if intent == "COURSE_TYPE_DETAILS": |
| if subtype in ["express", "intensive", "regular"]: |
| return build_course_details_reply(subtype, gender) |
| return build_courses_menu_reply(gender) |
|
|
| return "" |
|
|
|
|
| |
| |
| |
|
|
| def build_llm_fallback_system_prompt() -> str: |
| return """ |
| أنت مساعد واتساب رسمي لمركز ÄDK. |
| |
| التعليمات: |
| - رد بالعربية المصرية بشكل مهذب وبسيط. |
| - الرد يكون من 2 إلى 4 سطور فقط. |
| - لا تضف أي معلومة غير موجودة في المعلومات المتاحة. |
| - ممنوع ذكر أسعار فعلية أو مواعيد فعلية أو عناوين فعلية أو أرقام فروع. |
| - لو السؤال خارج المعلومات المتاحة، اقترح توجيه الاستفسار لفريق خدمة العملاء بشكل منطقي. |
| - لا تكتب بصيغة فصحى شديدة. |
| - لا تذكر أنك نموذج ذكاء اصطناعي. |
| |
| المعلومات المتاحة فقط: |
| 1) المركز: |
| - ÄDK اتأسس سنة 1998. |
| - له فروع مختلفة في القاهرة. |
| - بيقدم كورسات للكبار من A1 لحد C1. |
| - وبيقدم كورسات للأطفال من A1 لحد B1. |
| - وهو أول معهد معتمد من Goethe Institute في الشرق الأوسط وشمال أفريقيا. |
| |
| 2) أنواع الكورسات: |
| - Express: Super intensive، 3 محاضرات أسبوعيًا، كل محاضرة 4 ساعات، مدة الـ stage شهرين ونص، وممكن تحجز Stage كاملة. |
| - Intensive: محاضرتين أسبوعيًا، كل محاضرة 4 ساعات، مدة الـ stage 3 شهور ونص، وممكن تحجز Stage كاملة. |
| - Regular: تحجز كل level لوحده من A1.1 لحد B1.3، محاضرتين أسبوعيًا، كل محاضرة 4 ساعات، مدة الكورس شهر ونص. |
| - Weekend: مناسب للمشغولين خلال الأسبوع، وبيتبع نفس outline وساعات الـ Regular. |
| - Online: متاح بنظام أونلاين. |
| - Children: من سن 5 لحد 15 سنة، والهدف تنمية مهارات الطفل واللغة وتشجيع التفكير الحر والنقدي والاستقلالية. |
| """.strip() |
|
|
|
|
| def process_with_llm(user_text: str, gender: Optional[str] = None) -> str: |
| if not api_key: |
| return fallback_customer_service_reply(gender) |
|
|
| client = openai.OpenAI(api_key=api_key, base_url=base_url) |
|
|
| try: |
| resp = client.chat.completions.create( |
| model=model_link, |
| messages=[ |
| {"role": "system", "content": build_llm_fallback_system_prompt()}, |
| {"role": "user", "content": user_text}, |
| ], |
| max_tokens=120, |
| temperature=0.1, |
| top_p=0.4, |
| stream=False, |
| ) |
|
|
| answer = resp.choices[0].message.content.strip() |
| return sanitize_reply(answer, gender) |
|
|
| except Exception: |
| return fallback_customer_service_reply(gender) |
|
|
|
|
| |
| |
| |
|
|
| def process_text(user_text: str): |
| user_text = (user_text or "").strip() |
| gender = detect_gender(user_text) |
|
|
| if not user_text: |
| return { |
| "ok": True, |
| "intent": "OTHER", |
| "gender": gender, |
| "source": "rule_based", |
| "reply": "أرسل سؤالك عن الكورسات أو عن المركز، وأنا أساعدك.", |
| } |
|
|
| intent = detect_intent(user_text) |
|
|
| |
| answer = generate_rule_based_reply(intent, user_text, gender) |
| if answer: |
| answer = sanitize_reply(answer, gender) |
| return { |
| "ok": True, |
| "intent": intent, |
| "gender": gender, |
| "source": "rule_based", |
| "reply": answer |
| } |
|
|
| |
| other_subtype = detect_other_subtype(user_text) |
| if other_subtype: |
| smart_other_answer = generate_other_reply(user_text, gender) |
| smart_other_answer = sanitize_reply(smart_other_answer, gender) |
| return { |
| "ok": True, |
| "intent": intent, |
| "gender": gender, |
| "source": "smart_fallback", |
| "reply": smart_other_answer |
| } |
|
|
| |
| llm_answer = process_with_llm(user_text, gender) |
| return { |
| "ok": True, |
| "intent": intent, |
| "gender": gender, |
| "source": "llm_fallback", |
| "reply": llm_answer |
| } |
|
|
|
|
| |
| |
| |
|
|
| @app.post("/processtext") |
| async def receive_updates(request: Request): |
| try: |
| data = await request.json() |
| except Exception: |
| return JSONResponse( |
| content={"ok": False, "error": "Invalid JSON payload"}, |
| status_code=400 |
| ) |
|
|
| print("Received Update:", data) |
|
|
| result = process_text(data.get("message", "")) |
| print("Assistant:", result) |
|
|
| return JSONResponse(content=result) |