the-brain / python-services /greeting_handler.py
voice-rag's picture
making it final ready urdu only no english or arabic
a2c51f3
Raw
History Blame Contribute Delete
11.6 kB
# greeting_handler.py
# Classifies opening-turn messages and returns template short replies (no LLM).
# Supports Latin/Roman and native Urdu (Arabic-script) greetings.
#
# v2 — Issue 8 fix + arabic_clarify removal
#
# Issue 8 (English "Yes, I can hear you." reply in Urdu-only session):
# The connection_check tier in get_short_reply() had a fallback branch:
# if _is_audio_connection_check(text): return "جی ہاں، آواز آ رہی ہے۔"
# return "Yes, I can hear you." ← BUG: English fallback
# When the connection-check pattern matched BUT _is_audio_connection_check()
# returned False (e.g. a bare "ہیلو ہیلو" repetition matched the
# connection_check tier via _is_connection_check but didn't contain
# آواز/سن/bol keywords), the fallback English string was returned and
# synthesised in an English voice — exactly the "Yes, I can hear you."
# (chars=20) seen in the TTS log for session1 Turn1.
# Fix: both branches of connection_check now return Urdu strings.
# The English fallback is removed entirely (Urdu-only mode).
#
# arabic_clarify removal:
# classify_opening_message() previously accepted a `language_locked`
# param that was partly driven by arabic_clarify state. That param is
# unchanged (it's the session language lock flag, not arabic_clarify).
# No further changes needed in this file for arabic_clarify — it was
# only consumed by prompt_builder.py and language_detector.py, not here.
import re
from typing import Literal
from language_config import has_arabic_script
OpeningTier = Literal["pure_greeting", "connection_check", "greeting_with_substance", "normal"]
# Roman / Latin substance markers
SUBSTANCE_MARKERS_LATIN = {
"aap", "ap", "meri", "mera", "madad", "help", "kesse", "kaise", "kese", "kaisey",
"puchna", "poochna", "admission", "course", "problem", "issue", "sawal", "sawaal",
"question", "batana", "batao", "bata", "chahiye", "chahte", "chahti", "karna",
"how", "what", "when", "where", "why", "can", "could", "would", "need", "want",
"tell", "about", "regarding", "fee", "fees", "enroll", "register",
"mujhe", "main", "mein", "hum", "humein", "kya", "kab", "kahan", "kyun",
}
# Urdu-script substance markers (intent beyond a bare greeting)
SUBSTANCE_MARKERS_URDU = {
"آپ", "اپ", "مدد", "مجھے", "میرا", "میری", "مرا", "کیا", "کہاں", "کیوں", "کیسے",
"کسے", "بتائیں", "بتاؤ", "پوچھنا", "چاہیے", "ضرورت", "مسئلہ", "مسئله", "سوال",
"داخلہ", "کورس", "فیس", "کرنا", "کریں", "سکتے", "سکتا", "سکتی",
"بتانا", "رہنمائی", "معلومات", "جاننا", "جانکارى",
}
PURE_GREETING_PHRASES_LATIN = {
"hi", "hello", "hey", "helo", "hii", "hiii",
"salam", "adaab",
"assalam u alaikum", "assalamu alaikum", "asalam u alaikum", "assalam o alaikum",
"salam u alaikum", "salam alaikum", "assalam alaikum",
"alaikum assalam", "walaikum assalam", "walaykum assalam", "walaikum salam",
"kher he", "khair he", "khair mubarak", "kher mubarak", "mubarak",
"good morning", "good evening", "good afternoon", "good night",
"yes", "ok", "okay", "thanks", "thank you", "bye", "goodbye", "shukriya",
"ji", "jee", "han", "haan", "nah", "na",
}
# Native Urdu-script greeting phrases (punctuation stripped before match)
PURE_GREETING_PHRASES_URDU = {
"السلام علیکم", "السلام عليكم", "سلام علیکم", "سلام عليكم",
"وعلیکم السلام", "وعليكم السلام", "و علیکم السلام", "و عليكم السلام",
"سلام", "السلام", "آداب", "اداب", "خیر ہے", "خیر مبارک", "خیرمبارک", "مبارک",
"جی", "جی ہاں", "جی ہاں", "ہاں", "ہان", "شکریہ", "شکریا", "جزاک اللہ",
"ہیلو", "ہائے", "ہیلو ہیلو", "سلام سلام",
"الحمد للہ", "الحمد لله", "ماشاء اللہ", "ماشاء الله",
}
URDU_GREETING_TOKENS = {
"السلام", "علیکم", "عليكم", "وعلیکم", "وعليكم", "سلام", "آداب", "اداب",
"خیر", "مبارک", "جی", "ہاں", "ہان", "شکریہ", "شکریا", "ہیلو", "ہائے",
}
CONNECTION_CHECK_PATTERNS = [
# Latin / Roman
re.compile(r"\bhello\s+hello\b", re.I),
re.compile(r"\bhi\s+hi\b", re.I),
re.compile(r"\bhey\s+hey\b", re.I),
re.compile(r"awa?z\s+aara?hi", re.I),
re.compile(r"awa?z\s+aa\s+rahi", re.I),
re.compile(r"sun\s+pa\s+rahe", re.I),
re.compile(r"sun\s+rahe", re.I),
re.compile(r"koi\s+sun\s+raha", re.I),
re.compile(r"sunaai\s+de", re.I),
re.compile(r"sunai\s+de", re.I),
re.compile(r"bol\s+rahe", re.I),
# Urdu script
re.compile(r"ہیلو\s+ہیلو"),
re.compile(r"سلام\s+سلام"),
re.compile(r"آواز\s+آ\s*رہی"),
re.compile(r"آواز\s+آرہی"),
re.compile(r"آواز\s+آ\s*رہی"),
re.compile(r"سن\s+پا\s+رہے"),
re.compile(r"سن\s+رہے"),
re.compile(r"سن\s+رہی"),
re.compile(r"کوئی\s+سن\s+رہا"),
re.compile(r"سنائی\s+دے"),
re.compile(r"سناائی\s+دے"),
re.compile(r"بول\s+رہے"),
re.compile(r"بول\s+رہی"),
re.compile(r"آواز\s+آ\s*رہی\s+ہے"),
]
_GREETING_MIRROR = [
# Latin / Roman
(re.compile(r"assalam\s*u?\s*alaikum|assalamu\s*alaikum|salam\s*u?\s*alaikum", re.I), "وعلیکم السلام"),
(re.compile(r"walaikum\s*assalam|walaykum\s*assalam|alaikum\s*assalam", re.I), "السلام علیکم"),
(re.compile(r"khair\s*mubarak|kher\s*mubarak", re.I), "مبارک"),
(re.compile(r"\bkher\s+he\b|\bkhair\s+he\b", re.I), "مبارک"),
(re.compile(r"\badaab\b", re.I), "آداب"),
(re.compile(r"\bhello\b", re.I), "ہیلو"),
(re.compile(r"\bhi\b|\bhey\b|\bhelo\b", re.I), "ہیلو"),
(re.compile(r"\bsalam\b", re.I), "وعلیکم السلام"),
# Urdu script
(re.compile(r"السلام\s*علیکم|السلام\s*عليكم|سلام\s*علیکم|سلام\s*عليكم"), "وعلیکم السلام"),
(re.compile(r"وعلیکم\s*السلام|وعليكم\s*السلام"), "السلام علیکم"),
(re.compile(r"خیر\s*مبارک|خیرمبارک"), "مبارک"),
(re.compile(r"خیر\s*ہے"), "مبارک"),
(re.compile(r"آداب|اداب"), "آداب"),
(re.compile(r"ہیلو"), "ہیلو"),
(re.compile(r"ہائے"), "ہائے"),
(re.compile(r"^سلام$|^\s*سلام\s*$"), "وعلیکم السلام"),
(re.compile(r"^جی\s*ہاں$|^ہاں$|^ہان$"), "جی"),
(re.compile(r"شکریہ|شکریا"), "خوش آمدید"),
]
def _strip_punctuation(text: str) -> str:
return re.sub(r"[\s\u060C\u061B\u061F\u06D4\.,!?؛:]+", " ", text).strip()
def _normalize_latin(text: str) -> str:
return re.sub(r"\s+", " ", (text or "").strip().lower())
def _normalize_urdu(text: str) -> str:
return re.sub(r"\s+", " ", _strip_punctuation(text)).strip()
def _has_substance(text: str) -> bool:
if not text:
return False
latin_words = set(re.findall(r"[a-zA-Z]+", text.lower()))
if latin_words & SUBSTANCE_MARKERS_LATIN:
return True
urdu_words = set(re.findall(r"[\u0600-\u06FF]+", text))
if urdu_words & SUBSTANCE_MARKERS_URDU:
return True
if "?" in text or "؟" in text:
lowered = text.lower()
if any(m in lowered for m in SUBSTANCE_MARKERS_LATIN):
return True
if any(m in text for m in SUBSTANCE_MARKERS_URDU):
return True
return False
def _is_pure_greeting_latin(text: str) -> bool:
normalized = _normalize_latin(text)
if not normalized:
return False
if normalized in PURE_GREETING_PHRASES_LATIN:
return True
words = normalized.split()
filler = {"ji", "jee", "han", "haan"}
if words and all(w in PURE_GREETING_PHRASES_LATIN or w in filler for w in words):
return True
return False
def _is_pure_greeting_urdu(text: str) -> bool:
if not has_arabic_script(text):
return False
normalized = _normalize_urdu(text)
if not normalized:
return False
if normalized in PURE_GREETING_PHRASES_URDU:
return True
compact = normalized.replace("و ", "و")
if compact in PURE_GREETING_PHRASES_URDU:
return True
words = re.findall(r"[\u0600-\u06FF]+", normalized)
if words and all(w in URDU_GREETING_TOKENS or w == "ہے" for w in words):
if set(words) <= (URDU_GREETING_TOKENS | {"ہے"}):
return True
return False
def _is_pure_greeting(text: str) -> bool:
return _is_pure_greeting_latin(text) or _is_pure_greeting_urdu(text)
def _is_connection_check(text: str) -> bool:
for pattern in CONNECTION_CHECK_PATTERNS:
if pattern.search(text):
return True
normalized_latin = _normalize_latin(text)
if normalized_latin.count("hello") >= 2 or normalized_latin.count("hi") >= 2:
return True
normalized_urdu = _normalize_urdu(text)
if normalized_urdu.count("ہیلو") >= 2 or normalized_urdu.count("سلام") >= 2:
return True
return False
def _message_length(text: str) -> int:
stripped = _strip_punctuation(text)
return len(stripped.replace(" ", ""))
def classify_opening_message(text: str, turn_count: int, language_locked: bool) -> OpeningTier:
"""Classify message tier for early-turn routing."""
if turn_count > 3 or language_locked:
return "normal"
if _has_substance(text):
return "greeting_with_substance"
if _is_connection_check(text):
return "connection_check"
if _is_pure_greeting(text):
return "pure_greeting"
if _message_length(text) < 12 and not _has_substance(text):
return "pure_greeting"
return "normal"
def _is_audio_connection_check(text: str) -> bool:
latin = text.lower()
if any(c in latin for c in ("awaaz", "awaz", "sun")):
return True
return any(
marker in text
for marker in ("آواز", "سن", "سنائی", "سناائی", "بول رہ")
)
def get_short_reply(tier: OpeningTier, text: str, greeting_streak: int = 0) -> str:
"""
Deterministic short reply — no LLM. Urdu-only mode: all replies in Urdu.
Issue 8 fix: the previous connection_check fallback branch returned the
English string "Yes, I can hear you." when _is_audio_connection_check()
was False (e.g. bare "ہیلو ہیلو" repetition). This produced an English
TTS response in a Urdu-only session. Both branches now return Urdu.
"""
if tier == "connection_check":
if _is_audio_connection_check(text):
return "جی ہاں، آواز آ رہی ہے۔"
# Issue 8 fix: was "Yes, I can hear you." — now Urdu
return "جی، میں سن رہا ہوں۔"
if tier == "pure_greeting":
for pattern, reply in _GREETING_MIRROR:
if pattern.search(text):
return reply
if greeting_streak >= 2:
return "جی، میں آپ کی کیسے مدد کر سکتا ہوں؟"
if has_arabic_script(text):
return "وعلیکم السلام"
# Issue 8 fix: was "Hello" — now Urdu
return "ہیلو"
# Issue 8 fix: was "Hello" — now Urdu
return "جی"
def should_short_circuit(tier: OpeningTier) -> bool:
return tier in ("pure_greeting", "connection_check")