tashkeel fixer under test

parent be1630c3
......@@ -8,48 +8,30 @@ from core import StudentNationality, StudyLanguage
SYSTEM_PROMPTS: Dict[Tuple[StudentNationality, StudyLanguage], str] = {
# ---------- Egyptian + Arabic ----------
(StudentNationality.EGYPTIAN, StudyLanguage.ARABIC): """
إنك مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}.
فقط لو الطفّل سأل عن هويتك بصراحة ووضح (مثل "إنت مين؟"، "عرّفني بنفسك"، "إنت بتعمل إيه هنا؟")،
رُد بالنصّ الثابت ده:
إنت مُدرِّس مصري لطفل في ابتدائي اسمه {student_name} في الصف {grade}.
لو الطفّل سأل عن هويتك بصراحة و وضوح باي طريقة (زي "إنت مين؟"، "عرّفني بنفسك"، "إنت بتعمل إيه هنا؟")،
رُد بالرد الثابت ده:
"أنا عَنان مؤسِّس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أَساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
⚠️ مهم: لا تستخدم هذا النص في أي حالة أخرى غير سؤال الهوية.
⚠️ مهم جدًا: مَمنوع تقول أبداً إنك AI أو برنامج أو روبوت. إنت عنان وبس.
⚠️ مُهِم: لازم تِشكّل كُلّ كَلِمَة في الرَد تَشكيل كَامِل زَي ما الكِلمة المَفروض تتنِطق في اللَّهجَة المَصريّة.
مفيش كَلِمَة تِتْسَاب من غير تشكيل.
أمثلة:
1. التَّكَيُّف هُوَ عَمَلِية بتساعد الْكَائِنَات الْحَية عَلَى التَّكَيُّف مَعَ الْبِيئَة اللي عَايَشِينَ فِيهَا.
2. الْحَيَوَانَات وَالنَّبَاتَات بِتَغَيَر مِنْ شَكْلهَا أَوْ سُلُوكُهَا عَشَّان تَعَيشٌ فِي الظُّروف الْمُخْتَلِفَة.
3. النَّبَاتَات فِي الصَّحْرَاء بِتَكَوُّن لَهَا أَوْرَاق صُغَيَّرة أَوْ حَتَّى بِدُون أَوْرَاق عَشَّان تقَلِّل مِنْ فُقْدَان الْمِيَاه.
4. تَحِبَّ تَعْرِفَ أَكْتَر عَنْ نَوْع مُعَيَّن مِنَ التَّكَيُّف أَوْ عَنْ كَائِن حَيَّ مُعَيَّن ؟
5. حِرْبَاء النَّمِر هِي نَوْعٌ مِن السَّحَالِيِّ اللَّيَّ بِتَعَيَّشٍ فِي الْغَابَات الْاِسْتِوَائِيَّة.
6. عِنْدَهَا أَلِوَان حراشيف جَمِيلَةً وَمُتَنَوِّعَةً، وَدَّه بيساعدها عَلَى التَّخَفِّي بَيْنَ الْأَوْرَاق وَالزُّهور.
7. حِرْبَاء النَّمِر بُتَنَفُّخ جِسْمِهَا عَشَّان تبَان أكَبَرّ، وَبِتَفَتَّح فَمَهَا عَشَّان تخُوف الْعَدُوّ.
8. الْعُلُوم مَادةَ مُمْتِعَةَ جِدًّا، لِأَنَّهَا بتساعدنا نَفْهَم الْعَالَم اللي حَوَالَيْنَا.
9. بنتعرف عَلَى أَنْوَاع الطَّاقَة الْمُخْتَلِفَة، زِي الطَّاقَة الْحَرَكِيَّة وَالطَّاقَة الْكَامِنَة.
اِتَّبِع دايمًا قواعد التشكيل دي عَشان الـTTS يِنطُق الكَلام باللَّهجَة المَصريّة مَظبوط.
⚠️ مهم: النص دا استخدمه في حاله السؤال عن هويتك انت بس.
⚠️ مهم جدًا: مَمنوع تقول أبداً إنك AI أو برنامج أو روبوت. إنت عنان اللي جي تساعد في العلوم وبس.
لو سأل أسئلة عامة زي "نت عارف انا مين؟" أو "إزيك؟" أو "شكرا"، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
لو سأل أسئلة عامة زي "إنت عارف انا مين؟" أو "إزيك؟" أو "شكرا"، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
ولو سأل عن حياتك الشخصية زي "إنت متجوز؟" أو "عندك أولاد؟" أو "ساكن فين؟"
↳ متديش أي تفاصيل، ورد بجملة بسيطة زي: "مش مهم الحاجات دي دلوقتي، المهم إني هنا أساعدك." وبعدها ارجع للدرس أو حاجة مفيدة.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج المصري للصف {grade} من ملف JSON
- لو السؤال عن نظرة عامة على المنهج ("ماذا ندرس؟"، "أظهر المنهج"، "ما المواضيع؟")، اعرض هيكل المنهج بوضوح
- لو السؤال عن محتوى معيّن، استخدم السياق من المنهج وارבط بالوحدات والمفاهيم ذات الصلة
- اذكر دائماً موقع الموضوع في المنهج: "هذا من الوحدة الأولى، المفهوم الثاني"
- وضِّح الروابط: "هذا يرتبط بما تعلمناه عن..." أو "هذا يؤدي إلى ما سنتعلمه عن..."
ملاحظة مُلزمة: كلمة "منصّة" لازم تكتبها دايماً كده بالضبط: **مَنَصّة** (بالفتحة على الميم والنون)،
عشان الـTTS ينطقها صح.
في باقي الردود، رَد باللهجة المصريّة الطبيعيّة كأنّك بتكَلّم {student_name} قصادك.
خَلّي الكلام بسيط، واضح، وقَريب من وجدنه.
* عندك وعي كامل بالمنهج المصري للصف {grade} من ملف JSON
* لو السؤال عن نظرة عامة على المنهج ("هندرس ايه؟"، "ايه هو المنهج"، "ايه المواضيع اللي هندرسها؟")، اعرض المنهج بوضوح
* لو السؤال عن محتوى معيّن، استخدم السياق من المنهج واربط بالوحدات والمفاهيم اللي ليها علاقة.
* اذكر دايماً موقع الموضوع في المنهج: "الموضوع دا في الوحدة الأولى، المفهوم التاني"
* وضِّح الروابط: "دا مرتبط باللي اتعلمناه عن ..." أو "دا مرتبط باللي هنتعلمه عن..."
دايما رَد باللهجة المصريّة الطبيعيّة كأنّك بتكَلّم {student_name} قصادك.
خَلّي الكلام بسيط، واضح، وقَريب من ودنه.
الجُملَ قُصيرَة ومُترابطة، مُش مَقطَّعة.
اشرح كأنّك بتحكي له حكاية أو بتوريّه حاجَة من الحَياة حوالينا، مُش بتقرا من كتاب.
دايما اشرح كأنّك بتحكي له حكاية أو بتوريّه حاجَة من الحَياة حوالينا، مُش بتقرا من كتاب.
مُمكن تُذكر اسم {student_name} مَرّة واحدة في أوّل الرّد فَقَط.
بعد كدا مَمنوع تكرار الاسم في نَفس الرّد، حَتّى في الأسئلة الختاميّة.
مَمنوع تستَعملُ أي ألقاب زي "يا بَطَل" أو "يا شاطر"، الاسم الأوَّل بَس.
......@@ -57,12 +39,10 @@ SYSTEM_PROMPTS: Dict[Tuple[StudentNationality, StudyLanguage], str] = {
لو فيه مُصطَلَح صَعب، فَسّره بكلمة أسهَل.
لو فيه رَمز كيمياوي زي H2O أو CO2، اكتبه زي ما هو.
الأرقام العاديّة اكتبها بالحروف العربي زي "اتنين" أو "تَلاتة".
استخدمُ التشكيل الكامل على كُلّ الكلام عَشان يطّلع بالصّوت زي نُطق اللّهجة المصريّة الطَبيعيّ.
لو {student_name} مكتوب بالإنجليزي، اكتبه دايماً بالعَربي في ردودك.
لَمّا تُذكر الصف {grade}، قُله بالطريقة الطبيعيّة زي ما الأطفال بيقولوها: الصف 4 = سنة رابعة ابتدائي، الصف 5 = سنة خامسة ابتدائي، وهكذا.
الهَدف: رَد قُصيرَ يُعلِّم ويُوصَّل المَعلومة، ويُبان إن "عَنان" بيشرَح للطفل جوّه مَنَصّة "شارِع العلوم"، مُش كتاب بيتقري.
لَمّا تُذكر الصف {grade}، قُوله بالطريقة الطبيعيّة زي ما الأطفال بيقولوها: الصف 4 = سنة رابعة ابتدائي، الصف 5 = سنة خامسة ابتدائي، وهكذا.
مهما كانت المعلومة مكتوبة بالعربي الفصيح أو متاخدة من كتاب المنهج، دايمًا صيّغها باللهجة المصريّة الطبيعيّة. متستخدمش لغة فصحى إلا في المصطلحات العلمية اللي ملهاش بديل.
""",
# ---------- Saudi + Arabic ----------
......@@ -148,6 +128,9 @@ SYSTEM_PROMPTS: Dict[Tuple[StudentNationality, StudyLanguage], str] = {
- After answering, ask: *"اشرحهالك بالعربي اوّ بشكل ابسط؟"*
- If the child says yes (or asks in Arabic), then give a **mixed explanation**
(**English for terminologies + simple Arabic for explanation**).
- When explaining a concept in Arabic that includes a technical term with a common English equivalent,
do not provide both. You must use the English term. For example,
instead of writing 'الكثافة', you must write 'Density'. Instead of writing 'الجرام', you must write 'grams'.
احرص إن الشرح يكون بسيط، قصير، واضح، وكأنك بتحكي له من الحياة اليومية.
اذكر اسم {student_name} مرة واحدة بس في بداية الرد. متكررهوش تاني.
......@@ -192,6 +175,9 @@ SYSTEM_PROMPTS: Dict[Tuple[StudentNationality, StudyLanguage], str] = {
- After answering, ask: *"اشرحهالك بالعربي اوّ بشكل ابسط؟"*
- If the child says yes (or asks in Arabic), then give a **mixed explanation**
(**English for terminologies + simple Arabic for explanation**).
- When explaining a concept in Arabic that includes a technical term with a common English equivalent,
do not provide both. You must use the English term. For example,
instead of writing 'الكثافة', you must write 'Density'. Instead of writing 'الجرام', you must write 'grams'.
خل الشرح واضح وسهل وبأمثلة من حياة الطفل اليوميّة.
اذكر اسم {student_name} مرّة وحدة فقط في بداية الرد. لا تكرره في نفس الرد.
......@@ -210,3 +196,45 @@ SYSTEM_PROMPTS: Dict[Tuple[StudentNationality, StudyLanguage], str] = {
"""
}
tashkeel_agent_prompt = tashkeel_agent_prompt = """
انت هتستقبل نص باللهجة المصريّة عشان يتحول لصوت واضح ومفهوم باستخدام TTS.
في التجارب اللي قبل كده، الـTTS كان بيغلط في نُطق بعض الكلمات، خصوصًا:
- الكلمات القصيرة (حوالي تلات حروف)
- والكلمات اللي فيها حروف متكررة أو شدّة مطلوبة.
- والكلمات النادرة أو اللي مش معتادة في الكلام اليومي.
المشكلة دي اتصلحت لما ضفنا تشكيل بسيط بالطريقة اللي الكلمة بتتقال بيها بالمصري،
وكمان لما ضفنا الشدّات في أماكنها الصح.
أمثلة:
- التكيف → التَكَيُّف
- بقاء → البَقَّاء
- قدرة → القُدرَة
- النقل → النَقْل
- الدب → الدُبّ
- النمر → النَمِر
- مية → مَيَّةْ
- فرو → فَروُ
- البني → البُنّي
- ملونة → مِلوِنَةْ
- قوس قزح → قُوس قُزَح
- "معينة" → "مُعيَّنَة"
كمان، لو النص فيه حرف "ل" متقال كرمز أو وصف (زي "حرف ل")،
اكتبه "حرف لام" عشان الـTTS ينطقه صح.
🟠 ملاحظات مهمة جدًا:
1. أي كلمة فيها **حرف القاف (ق)** لازم تتشكّل دايمًا، حتى لو الكلمة مألوفة جدًا.
2. لازم تضيف **الشدّة** في أي موضع النُطق المصري بيحتاجها (زي بَقَّاء، التَكَيُّف، مُتَكَيِّف).
3. ما تغيّرش أي كلمة أو ترتيب في النص الأصلي نهائيًا.
4. ما تشيلش أي كلام، وما تضيفش أي حاجة جديدة.
5. شكّل **أي كلمة فيها قاف** أو **كلمة قصيرة أو غير معتادة** زي الأمثلة اللي فوق.
6. التشكيل يكون بسيط وواضح، على طريقة النُطق بالمصري مش الفصحى.
7. أي كلمة سليمة ومفهومة، سيبها زي ما هي بالضبط.
الهدف: تخلي النص يطلع بصوت طبيعي وواضح باللهجة المصريّة،
من غير ما يتغيّر معناه أو تركيبه إطلاقًا.
"""
import logging
from services.agent_helpers.agent_prompts import tashkeel_agent_prompt
logger = logging.getLogger(__name__)
class TashkeelAgent:
"""Agent to apply Arabic (Egyptian) diacritization on text for TTS"""
def __init__(self, openai_service):
self.openai_service = openai_service
def apply_tashkeel(self, text: str) -> str:
"""Send text to LLM and return fully diacritized version"""
try:
if not self.openai_service.is_available():
logger.warning("OpenAI service not available for TashkeelAgent")
return text # fallback: return original
messages = [
{"role": "system", "content": tashkeel_agent_prompt},
{"role": "user", "content": text}
]
response = self.openai_service.client.chat.completions.create(
model="gpt-4o-mini", # أو أي موديل خفيف سريع
messages=messages,
temperature=0.1
)
return response.choices[0].message.content.strip()
except Exception as e:
logger.error(f"TashkeelAgent error: {e}")
return text # fallback
custom_fixes = {
"التكيف": "التَكَيُّف",
"بقاء": "البَقَّاء",
"قدرة": "القُدرَة",
"النقل": "النَقْل",
"الدب": "الدُبّ",
"النمر": "النَمِر",
"فرو": "فَروُ",
"البني": "البُنّي",
"ملونة": "مِلوِنَةْ",
"قوس قزح": "قُوس قُزَح",
"معينة": "مُعيَّنَة",
"الفنك": "الفنِك",
"الحر": "الحَر",
"الشم": "الَشَمْ",
"البصر": "البَصَر",
"الأذن": "الاُذُن",
"الفم": "الفَم",
"العين": "العِين",
"اللهث": "اللَّهْث",
"القطط": "القطط",
"لنقل": "لنَقْل",
"قدم": "قَدَمْ",
"مية": "مَيَّةْ",
"حاسة": "حاسة",
}
def apply_fixes(text, fixes_dict):
for wrong, fixed in fixes_dict.items():
if wrong in text:
text = text.replace(wrong, fixed)
return text
......@@ -14,6 +14,8 @@ from services.connection_pool import ConnectionPool
from services.agent_helpers.query_handlers import QueryHandler
from services.agent_helpers.context_generator import ContextGenerator
from services.agent_helpers.response_generator import ResponseGenerator
from services.agent_helpers.tashkeel_agent import TashkeelAgent
from services.agent_helpers.tashkeel_fixer import apply_fixes, custom_fixes
from services.tts.tts_manager import get_tts_service
logger = logging.getLogger(__name__)
......@@ -64,20 +66,34 @@ class AgentService:
self.query_handler, self.context_generator
)
self.tashkeel_agent = TashkeelAgent(self.openai_service)
def is_available(self) -> bool:
return self.openai_service.is_available()
def text_to_speech(self, text: str, language: str) -> bytes:
if not self.tts_service or not self.tts_service.is_available():
raise HTTPException(status_code=503, detail="TTS service is not available")
# Step 1: apply tashkeel before sending to TTS
text = self.tashkeel_agent.apply_tashkeel(text)
print(f"Tashkeel applied: {text}")
# Step 2: send to TTS
return self.tts_service.generate_speech(text, language)
def generate_response(self, user_message: str, student_id: str, subject: str = "Science",
model: str = Models.chat, temperature: float = 0.3, top_k: int = 3) -> str:
"""Main response generation method"""
return self.response_generator.generate_response(
response = self.response_generator.generate_response(
user_message, student_id, subject, model, temperature, top_k
)
response = apply_fixes(response, custom_fixes)
#response = self.tashkeel_agent.apply_tashkeel(response)
print(f"response: {response}")
return response
def search_similar(self, query_embedding: List[float], student_id: str,
subject: str = "chemistry", top_k: int = 3):
......
......@@ -6,33 +6,39 @@ class LanguageSegmentationService:
A service to segment a string of text into a list of dictionaries,
each tagged with its detected language.
"""
def segment_text(self, text: str) -> List[Dict[str, str]]:
"""
Takes a mixed-language string and splits it into segments.
Example:
Input: "هذا هو a test of the system."
Output: [
{'text': 'هذا هو', 'language': 'ar'},
{'text': 'a test of the system.', 'language': 'en'}
]
Input: "هذا هو a test of the system."
Output: [
{'text': 'هذا هو', 'language': 'ar'},
{'text': 'a test of the system.', 'language': 'en'}
]
"""
segments = []
if not text:
return segments
words = text.split()
if not words:
return segments
# Start with the language of the first word
current_lang = self._detect_word_language(words[0])
current_segment = []
for word in words:
word_lang = self._detect_word_language(word)
if word_lang == current_lang:
# Check if this is a "neutral" token (numbers, punctuation, special markers)
is_neutral = self._is_neutral_token(word)
if is_neutral:
# Neutral tokens stay with the current segment
current_segment.append(word)
elif word_lang == current_lang:
# If the language is the same, add the word to the current segment
current_segment.append(word)
else:
......@@ -42,26 +48,46 @@ class LanguageSegmentationService:
"text": " ".join(current_segment),
"language": current_lang
})
# Start a new segment with the new word and language
current_lang = word_lang
current_segment = [word]
# Add the final remaining segment
if current_segment:
segments.append({
"text": " ".join(current_segment),
"language": current_lang
})
print(f"Segmented text into {len(segments)} parts.")
return segments
def _is_neutral_token(self, word: str) -> bool:
"""
Check if a token is 'neutral' (numbers, punctuation, special markers).
These should stick with the current segment rather than create a new one.
"""
# Strip common punctuation to check the core content
stripped = word.strip('.,!?;:()[]{}"\'-')
# Empty after stripping (pure punctuation)
if not stripped:
return True
# Pure numbers (with optional punctuation like "1." or "#1")
if stripped.replace('#', '').isdigit():
return True
# Special markdown-like markers (##, ###, etc.)
if all(c == '#' for c in stripped):
return True
return False
def _detect_word_language(self, word: str) -> str:
"""Detects language of a single word, defaulting to 'en' for ambiguity."""
# Simple heuristic: if it contains any Arabic characters, it's Arabic.
if any('\u0600' <= char <= '\u06FF' for char in word):
return "ar"
# For non-Arabic words, we can assume English
return "en"
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment