Mr-Help commited on
Commit
5798293
·
verified ·
1 Parent(s): a722165

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +27 -111
app.py CHANGED
@@ -156,28 +156,21 @@ def detect_gender(text: str) -> Optional[str]:
156
  """
157
  t = normalize_arabic(text)
158
 
 
159
  female_markers = [
160
  "انا مهتمه",
161
  "مهتمه",
162
  "عايزه",
163
  "عاوزه",
164
  "عاوزة",
 
165
  "حابه",
166
  "حابة",
167
  "محتاجه",
168
  "محتاجة",
169
- "عايزه اقدم",
170
- "عايزه اعرف",
171
- "عايزه اسال",
172
- "عايزة",
173
- "عايزة اعرف",
174
- "عايزة اقدم",
175
- "عايزة اسال",
176
  "انا ام",
177
  "انا ماما",
178
  "انا والده",
179
- "بنتي",
180
- "لبنتي",
181
  ]
182
 
183
  male_markers = [
@@ -186,17 +179,11 @@ def detect_gender(text: str) -> Optional[str]:
186
  "عايز",
187
  "حابب",
188
  "محتاج",
189
- "عايز اقدم",
190
- "عايز اعرف",
191
- "عايز اسال",
192
  "انا اب",
193
  "انا بابا",
194
  "انا والد",
195
- "ابني",
196
- "لابني",
197
  ]
198
 
199
- # female first to avoid "مهتم" matching inside "مهتمه"
200
  for marker in female_markers:
201
  if marker in t:
202
  return "female"
@@ -373,17 +360,11 @@ def build_courses_menu_reply(gender: Optional[str] = None) -> str:
373
 
374
  def build_center_info_reply(gender: Optional[str] = None) -> str:
375
  center = KB["center"]
376
- closing = choose_variant(
377
- gender,
378
- "لو عندك سؤال محدد، أقدر أوصله لخدمة العملاء.",
379
- "لو عندك سؤال محدد، أقدر أوصله لخدمة العملاء.",
380
- "لو عندك سؤال محدد، أقدر أوصله لخدمة العملاء."
381
- )
382
  return (
383
  f"ÄDK اتأسس سنة {center['established']}، و{center['branches']}.\n"
384
  f"الكورسات للكبار {center['adults_levels']}، وللأطفال {center['children_levels']}.\n"
385
  f"وكمان هو {center['goethe']}.\n"
386
- f"{closing}"
387
  )
388
 
389
 
@@ -440,7 +421,7 @@ def build_course_details_reply(course_key: str, gender: Optional[str] = None) ->
440
  )
441
  return (
442
  f"{start_line}\n"
443
- f"ممكن تحجز كل level لوحده من A1.1 لحد B1.3.\n"
444
  f"النظام {details[1]}، و{details[2]}.\n"
445
  f"مدة الكورس {details[3].replace('مدة الكورس ', '')}.\n"
446
  f"{ask_line}"
@@ -500,22 +481,25 @@ def generate_other_reply(user_text: str, gender: Optional[str] = None) -> str:
500
  "تحب أبلّغهم إنك مهتم أو مهتمة بالحضور؟"
501
  )
502
  return (
503
- "تمام، بالنسبة للعنوان ده بيكون خاص بالكورسات الحضوري.\n"
504
  "أقدر أوصّل استفسارك لفريق خدمة العملاء عشان يبعثوا لك الفرع المناسب.\n"
505
  f"{ask_line}"
506
  )
507
 
508
  if subtype == "SCHEDULE_QUERY":
 
 
 
 
 
 
509
  line2 = choose_variant(
510
  gender,
511
  "قولّي تفضّل أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء.",
512
  "قولّي تفضّلي أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء.",
513
  "قولّي تفضّل أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء."
514
  )
515
- return (
516
- "تمام، المواعيد بتختلف حسب إذا كنت مهتم بأونلاين أو حضور، وكمان حسب الفترة المناسبة لك.\n"
517
- f"{line2}"
518
- )
519
 
520
  if subtype == "REGISTRATION_QUERY":
521
  ask_line = choose_variant(
@@ -559,7 +543,7 @@ def generate_other_reply(user_text: str, gender: Optional[str] = None) -> str:
559
  "قولّي بس سؤالك عن الكورسات، التقديم، المواعيد، ولا الحضور؟"
560
  )
561
 
562
- return fallback_customer_service_reply(gender)
563
 
564
 
565
  def sanitize_reply(text: str, gender: Optional[str] = None) -> str:
@@ -578,6 +562,8 @@ def sanitize_reply(text: str, gender: Optional[str] = None) -> str:
578
  "يمكنني": "أقدر",
579
  "العميل": "إنت",
580
  "المستخدم": "إنت",
 
 
581
  }
582
 
583
  for old, new in replacements.items():
@@ -593,23 +579,6 @@ def sanitize_reply(text: str, gender: Optional[str] = None) -> str:
593
  if len(english_words) > 10:
594
  return fallback_customer_service_reply(gender)
595
 
596
- blocked_patterns = [
597
- r"\b\d+\s*جنيه\b",
598
- r"\b\d+\s*egp\b",
599
- r"\bالعنوان\b",
600
- r"\bالمعاد\b",
601
- r"\bالساعة\b",
602
- r"\bالسعر\b",
603
- r"\bالاسعار\b",
604
- r"\bالأسعار\b",
605
- r"\bفرع\s+\w+",
606
- ]
607
-
608
- lowered = normalize_arabic(text)
609
- for pattern in blocked_patterns:
610
- if re.search(pattern, lowered, flags=re.IGNORECASE):
611
- return fallback_customer_service_reply(gender)
612
-
613
  return text
614
 
615
 
@@ -675,7 +644,7 @@ def generate_rule_based_reply(intent: str, user_text: str, gender: Optional[str]
675
 
676
 
677
  # =========================
678
- # LLM Helpers
679
  # =========================
680
 
681
  def build_llm_fallback_system_prompt() -> str:
@@ -683,13 +652,13 @@ def build_llm_fallback_system_prompt() -> str:
683
  أنت مساعد واتساب رسمي لمركز ÄDK.
684
 
685
  التعليمات:
686
- - رد بالعربية المصرية بشكل مهذب ورسمي وخفيف.
687
  - الرد يكون من 2 إلى 4 سطور فقط.
688
  - لا تضف أي معلومة غير موجودة في المعلومات المتاحة.
689
  - ممنوع ذكر أسعار فعلية أو مواعيد فعلية أو عناوين فعلية أو أرقام فروع.
690
- - لو السؤال خارج المعلومات المتاحة، اقترح بشكل منطقي توجيه الاستفسار لفريق خدمة العملاء.
 
691
  - لا تذكر أنك نموذج ذكاء اصطناعي.
692
- - لا تكتب مقدمات طويلة.
693
 
694
  المعلومات المتاحة فقط:
695
  1) المركز:
@@ -735,56 +704,6 @@ def process_with_llm(user_text: str, gender: Optional[str] = None) -> str:
735
  return fallback_customer_service_reply(gender)
736
 
737
 
738
- def rewrite_reply_with_llm(reply: str, gender: Optional[str] = None) -> str:
739
- """
740
- LLM participates here only as a rewriter, not as a source of truth.
741
- """
742
- if not api_key or not reply:
743
- return reply
744
-
745
- client = openai.OpenAI(api_key=api_key, base_url=base_url)
746
-
747
- gender_hint = "غير محدد"
748
- if gender == "male":
749
- gender_hint = "مذكر"
750
- elif gender == "female":
751
- gender_hint = "مؤنث"
752
-
753
- prompt = f"""
754
- أعد صياغة الرسالة التالية بالعربية المصرية بشكل طبيعي ولطيف ورسمي قليلًا.
755
-
756
- قواعد مهمة جدًا:
757
- - لا تضف أي معلومة جديدة
758
- - لا تغير المعنى
759
- - لا تضف أسعار أو مواعيد أو عناوين أو أرقام
760
- - حافظ على الرد قصير: من 2 إلى 5 سطور
761
- - لو كانت الصياغة الحالية جيدة، يمكنك إرجاعها كما هي
762
- - حاول مراعاة الجنس اللغوي إن كان واضحًا: {gender_hint}
763
-
764
- الرسالة:
765
- {reply}
766
- """.strip()
767
-
768
- try:
769
- resp = client.chat.completions.create(
770
- model=model_link,
771
- messages=[
772
- {"role": "system", "content": "You rewrite Arabic business chat replies without adding information."},
773
- {"role": "user", "content": prompt},
774
- ],
775
- max_tokens=140,
776
- temperature=0.2,
777
- top_p=0.5,
778
- stream=False,
779
- )
780
-
781
- enhanced = resp.choices[0].message.content.strip()
782
- return sanitize_reply(enhanced, gender)
783
-
784
- except Exception:
785
- return reply
786
-
787
-
788
  # =========================
789
  # Main Processing
790
  # =========================
@@ -804,35 +723,32 @@ def process_text(user_text: str):
804
 
805
  intent = detect_intent(user_text)
806
 
807
- # 1) Known intents -> rule-based
808
  answer = generate_rule_based_reply(intent, user_text, gender)
809
  if answer:
810
- answer = sanitize_reply(answer, gender)
811
- answer = rewrite_reply_with_llm(answer, gender)
812
  answer = sanitize_reply(answer, gender)
813
  return {
814
  "ok": True,
815
  "intent": intent,
816
  "gender": gender,
817
- "source": "rule_based+llm_rewrite",
818
  "reply": answer
819
  }
820
 
821
- # 2) Smart fallback for OTHER
822
- smart_other_answer = generate_other_reply(user_text, gender)
823
- if smart_other_answer:
824
- smart_other_answer = sanitize_reply(smart_other_answer, gender)
825
- smart_other_answer = rewrite_reply_with_llm(smart_other_answer, gender)
826
  smart_other_answer = sanitize_reply(smart_other_answer, gender)
827
  return {
828
  "ok": True,
829
  "intent": intent,
830
  "gender": gender,
831
- "source": "smart_fallback+llm_rewrite",
832
  "reply": smart_other_answer
833
  }
834
 
835
- # 3) Last resort LLM
836
  llm_answer = process_with_llm(user_text, gender)
837
  return {
838
  "ok": True,
 
156
  """
157
  t = normalize_arabic(text)
158
 
159
+ # مهم: ما نستنتجش النوع من "ابني / بنتي" لأنها بتوصف الطفل مش المتكلم
160
  female_markers = [
161
  "انا مهتمه",
162
  "مهتمه",
163
  "عايزه",
164
  "عاوزه",
165
  "عاوزة",
166
+ "عايزة",
167
  "حابه",
168
  "حابة",
169
  "محتاجه",
170
  "محتاجة",
 
 
 
 
 
 
 
171
  "انا ام",
172
  "انا ماما",
173
  "انا والده",
 
 
174
  ]
175
 
176
  male_markers = [
 
179
  "عايز",
180
  "حابب",
181
  "محتاج",
 
 
 
182
  "انا اب",
183
  "انا بابا",
184
  "انا والد",
 
 
185
  ]
186
 
 
187
  for marker in female_markers:
188
  if marker in t:
189
  return "female"
 
360
 
361
  def build_center_info_reply(gender: Optional[str] = None) -> str:
362
  center = KB["center"]
 
 
 
 
 
 
363
  return (
364
  f"ÄDK اتأسس سنة {center['established']}، و{center['branches']}.\n"
365
  f"الكورسات للكبار {center['adults_levels']}، وللأطفال {center['children_levels']}.\n"
366
  f"وكمان هو {center['goethe']}.\n"
367
+ "لو عندك سؤال محدد، أقدر أوصله لخدمة العملاء."
368
  )
369
 
370
 
 
421
  )
422
  return (
423
  f"{start_line}\n"
424
+ "ممكن تحجز كل level لوحده من A1.1 لحد B1.3.\n"
425
  f"النظام {details[1]}، و{details[2]}.\n"
426
  f"مدة الكورس {details[3].replace('مدة الكورس ', '')}.\n"
427
  f"{ask_line}"
 
481
  "تحب أبلّغهم إنك مهتم أو مهتمة بالحضور؟"
482
  )
483
  return (
484
+ "تمام، بالنسبة للعنوان ده خاص بالكورسات الحضوري.\n"
485
  "أقدر أوصّل استفسارك لفريق خدمة العملاء عشان يبعثوا لك الفرع المناسب.\n"
486
  f"{ask_line}"
487
  )
488
 
489
  if subtype == "SCHEDULE_QUERY":
490
+ line1 = choose_variant(
491
+ gender,
492
+ "تمام، المواعيد بتختلف حسب إذا كنت مهتم بأونلاين أو حضور، وكمان حسب الفترة المناسبة لك.",
493
+ "تمام، المواعيد بتختلف حسب إذا كنتِ مهتمة بأونلاين أو حضور، وكمان حسب الفترة المناسبة لك.",
494
+ "تمام، المواعيد بتختلف حسب إذا كنت مهتم بأونلاين أو حضور، وكمان حسب الفترة المناسبة لك."
495
+ )
496
  line2 = choose_variant(
497
  gender,
498
  "قولّي تفضّل أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء.",
499
  "قولّي تفضّلي أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء.",
500
  "قولّي تفضّل أونلاين ولا حضور، وصباحي ولا مسائي، وأنا أوجّه استفسارك بشكل أوضح لخدمة العملاء."
501
  )
502
+ return f"{line1}\n{line2}"
 
 
 
503
 
504
  if subtype == "REGISTRATION_QUERY":
505
  ask_line = choose_variant(
 
543
  "قولّي بس سؤالك عن الكورسات، التقديم، المواعيد، ولا الحضور؟"
544
  )
545
 
546
+ return ""
547
 
548
 
549
  def sanitize_reply(text: str, gender: Optional[str] = None) -> str:
 
562
  "يمكنني": "أقدر",
563
  "العميل": "إنت",
564
  "المستخدم": "إنت",
565
+ "سأقوم": "هقوم",
566
+ "سوف": "ه",
567
  }
568
 
569
  for old, new in replacements.items():
 
579
  if len(english_words) > 10:
580
  return fallback_customer_service_reply(gender)
581
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  return text
583
 
584
 
 
644
 
645
 
646
  # =========================
647
+ # LLM Fallback only
648
  # =========================
649
 
650
  def build_llm_fallback_system_prompt() -> str:
 
652
  أنت مساعد واتساب رسمي لمركز ÄDK.
653
 
654
  التعليمات:
655
+ - رد بالعربية المصرية بشكل مهذب وبسيط.
656
  - الرد يكون من 2 إلى 4 سطور فقط.
657
  - لا تضف أي معلومة غير موجودة في المعلومات المتاحة.
658
  - ممنوع ذكر أسعار فعلية أو مواعيد فعلية أو عناوين فعلية أو أرقام فروع.
659
+ - لو السؤال خارج المعلومات المتاحة، اقترح توجيه الاستفسار لفريق خدمة العملاء بشكل منطقي.
660
+ - لا تكتب بصيغة فصحى شديدة.
661
  - لا تذكر أنك نموذج ذكاء اصطناعي.
 
662
 
663
  المعلومات المتاحة فقط:
664
  1) المركز:
 
704
  return fallback_customer_service_reply(gender)
705
 
706
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
707
  # =========================
708
  # Main Processing
709
  # =========================
 
723
 
724
  intent = detect_intent(user_text)
725
 
726
+ # 1) Known intents -> rule-based only
727
  answer = generate_rule_based_reply(intent, user_text, gender)
728
  if answer:
 
 
729
  answer = sanitize_reply(answer, gender)
730
  return {
731
  "ok": True,
732
  "intent": intent,
733
  "gender": gender,
734
+ "source": "rule_based",
735
  "reply": answer
736
  }
737
 
738
+ # 2) Smart fallback for known OTHER subtypes
739
+ other_subtype = detect_other_subtype(user_text)
740
+ if other_subtype:
741
+ smart_other_answer = generate_other_reply(user_text, gender)
 
742
  smart_other_answer = sanitize_reply(smart_other_answer, gender)
743
  return {
744
  "ok": True,
745
  "intent": intent,
746
  "gender": gender,
747
+ "source": "smart_fallback",
748
  "reply": smart_other_answer
749
  }
750
 
751
+ # 3) Last resort LLM only if nothing matched
752
  llm_answer = process_with_llm(user_text, gender)
753
  return {
754
  "ok": True,