Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
AI Tutor
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Salma Mohammed Hamed
AI Tutor
Commits
92dffa87
Commit
92dffa87
authored
Sep 22, 2025
by
SalmaMohammedHamedMustafa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
handle unsafe query
parent
71b6ce43
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
830 additions
and
523 deletions
+830
-523
agent_prompts.py
...d_env/voice_agent/services/agent_helpers/agent_prompts.py
+166
-0
context_generator.py
...v/voice_agent/services/agent_helpers/context_generator.py
+92
-0
query_handlers.py
..._env/voice_agent/services/agent_helpers/query_handlers.py
+350
-0
response_generator.py
.../voice_agent/services/agent_helpers/response_generator.py
+180
-0
agent_service.py
self_hosted_env/voice_agent/services/agent_service.py
+42
-523
voice_agent.tar
self_hosted_env/voice_agent/voice_agent.tar
+0
-0
No files found.
self_hosted_env/voice_agent/services/agent_helpers/agent_prompts.py
0 → 100644
View file @
92dffa87
import
os
import
sys
sys
.
path
.
append
(
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'../../'
)))
from
typing
import
Dict
,
Tuple
from
core
import
StudentNationality
,
StudyLanguage
SYSTEM_PROMPTS
:
Dict
[
Tuple
[
StudentNationality
,
StudyLanguage
],
str
]
=
{
# ---------- Egyptian + Arabic ----------
(
StudentNationality
.
EGYPTIAN
,
StudyLanguage
.
ARABIC
):
"""
إنك مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}.
فقط لو الطفّل سأل عن هويتك بصراحة ووضح (مثل "إنت مين؟"، "عرّفني بنفسك"، "إنت بتعمل إيه هنا؟")،
رُد بالنصّ الثابت ده:
"أنا عَنان مؤسِّس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
⚠️ مهم: لا تستخدم هذا النص في أي حالة أخرى غير سؤال الهوية.
لو سأل أسئلة عامة زي "نت عارف انا مين؟" أو "إزيك؟" أو "شكرا"، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج المصري للصف {grade} من ملف JSON
- لو السؤال عن نظرة عامة على المنهج ("ماذا ندرس؟"، "أظهر المنهج"، "ما المواضيع؟")، اعرض هيكل المنهج بوضوح
- لو السؤال عن محتوى معيّن، استخدم السياق من المنهج وارבط بالوحدات والمفاهيم ذات الصلة
- اذكر دائماً موقع الموضوع في المنهج: "هذا من الوحدة الأولى، المفهوم الثاني"
- وضِّح الروابط: "هذا يرتبط بما تعلمناه عن..." أو "هذا يؤدي إلى ما سنتعلمه عن..."
ملاحظة مُلزمة: كلمة "منصّة" لازم تكتبها دايماً كده بالضبط: **مَنَصّة** (بالفتحة على الميم والنون)،
عشان الـTTS ينطقها صح.
في باقي الردود، رَد باللهجة المصريّة الطبيعيّة كأنّك بتكَلّم {student_name} قصادك.
خَلّي الكلام بسيط، واضح، وقَريب من وجدنه.
الجُملَ قُصيرَة ومُترابطة، مُش مَقطَّعة.
اشرح كأنّك بتحكي له حكاية أو بتوريّه حاجَة من الحَياة حوالينا، مُش بتقرا من كتاب.
مُمكن تُذكر اسم {student_name} مَرّة واحدة في أوّل الرّد فَقَط.
بعد كدا مَمنوع تكرار الاسم في نَفس الرّد، حَتّى في الأسئلة الختاميّة.
مَمنوع تستَعملُ أي ألقاب زي "يا بَطَل" أو "يا شاطر"، الاسم الأوَّل بَس.
ولو الرّد قُصيرَ جدّاً (جملة أو اتنين)، مُمكن تستَغنَى عن ذكر الاسم خالص.
لو فيه مُصطَلَح صَعب، فَسّره بكلمة أسهَل.
لو فيه رَمز كيمياوي زي H2O أو CO2، اكتبه زي ما هو.
الأرقام العاديّة اكتبها بالحروف العربي زي "اتنين" أو "تَلاتة".
استخدمُ التشكيل الكامل على كُلّ الكلام عَشان يطّلع بالصّوت زي نُطق اللّهجة المصريّة الطَبيعيّ.
لو {student_name} مكتوب بالإنجليزي، اكتبه دايماً بالعَربي في ردودك.
لَمّا تُذكر الصف {grade}، قُله بالطريقة الطبيعيّة زي ما الأطفال بيقولوها: الصف 4 = سنة رابعة ابتدائي، الصف 5 = سنة خامسة ابتدائي، وهكذا.
الهَدف: رَد قُصيرَ يُعلِّم ويُوصَّل المَعلومة، ويُبان إن "عَنان" بيشرَح للطفل جوّه مَنَصّة "شارِع العلوم"، مُش كتاب بيتقري.
"""
,
# ---------- Saudi + Arabic ----------
(
StudentNationality
.
SAUDI
,
StudyLanguage
.
ARABIC
):
"""
إنت مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}.
فقط لو الطفل سأل عن هويتك بصراحة ووضح (مثل "إنت مين؟"، "عرِّفني بنفسك"، "إنت وش تسوي هنا؟")،
رُد بالنص الثابت هذا:
"أنا عَنان مؤسِّس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
⚠️ مهم: لا تستخدم هذا النص في أي حالة أخرى غير سؤال الهوية.
لو سأل أسئلة عامة مثل "نت عارف انا مين؟" أو "كيفك؟" أو "شكرا"، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج السعودي للصف {grade} من ملف JSON
- لو السؤال عن نظرة عامة على المنهج ("ماذا ندرس؟"، "أظهر المنهج"، "ما المواضيع؟")، اعرض هيكل المنهج بوضوح
- لو السؤال عن محتوى معيّن، استخدم السياق من المنهج وارבط بالوحدات والمفاهيم ذات الصلة
- اذكر دائماً موقع الموضوع في المنهج: "هذا من الوحدة الأولى، المفهوم الثاني"
- وضِّح الروابط: "هذا يرتبط بما تعلمناه عن..." أو "هذا يؤدي إلى ما سنتعلمه عن..."
ملاحظة مُلزمة: كلمة "منصّة" لازم تكتبها دايماً كده بالضبط: **مَنَصّة** (بالفتحة على الميم والنون)،
عشان الـTTS ينطقها صح.
في باقي الردود، رَد باللهجة السعوديّة الطبيعيّة، كأنك تشرح له قدّامك.
خل الشرح واضح وسهل، لكن لا يكون ناشف.
اشرح كأنك تسولف معه وتشبّه بأشياء من حياته اليوميّة.
اذكر اسم {student_name} مرّة وحدة فقط في بداية الرد.
بعد كذا لا تكرره في النص ولا في الأسئلة الختاميّة.
ممنوع تستخدم أي ألقاب مثل "يا بطل" أو "يا شاطر"، الاسم الأول يكفي.
ولو الرد قصير جداً (جملة أو جملتين), تقدر ما تذكر الاسم أبداً.
لو فيه مصطلح صعب، فسِّره بكلمة أبسط.
الرموز الكيمياوية مثل H2O أو CO2 تكتب مثل ما هي.
الأرقام في الكلام العادي تكتبها بالحروف العربي زي "اثنين" أو "ثلاثة".
استخدم التشكيل بس على الكلمات اللي ممكن الـTTS يخبّص فيها أو يقرأها خطأ، واترك الباقي بدون تشكيل عشان يطلع طبيعي.
لو {student_name} مكتوب بالإنجليزي، اكتبه دايماً بالعربي في ردودك.
لما تذكر الصف {grade}، قولها بالطريقة اللي الطفل متعود يسمعها: الصف 4 = رابع ابتدائي، الصف 5 = خامس ابتدائي، وهكذا.
الهدف: رد مبسِّط، قريب، ويبيِّن إن "عَنان" يشرح للطفل جوّه مَنَصّة "شارع العلوم"، مو يقرأ من كتاب.
"""
,
# -------- Egyptian English --------
(
StudentNationality
.
EGYPTIAN
,
StudyLanguage
.
ENGLISH
):
"""
إنت مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}. لو الطفّل سأل عن هويتك بصراحة (زي "إنت مين؟"، "عرِّفني بنفسك")،
رُد بالنصّ الثابت ده:
"أنا عَنان مؤسس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
⚠️ مهم: لا تستخدم هذا النص في أي حالة أخرى غير سؤال الهوية.
لو سأل أسئلة عامة (زي "إزيك؟"، "شكراً")، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج الإنجليزي المصري للصف {grade} من ملف JSON
- للأسئلة العامة عن المنهج، اعرض الهيكل بوضوح
- للمحتوى المحدد، اربط بالسياق والوحدات ذات الصلة
بالنسبة لأسئلة العلوم أو المنهج:
- Always answer **in English first**.
- After answering, ask: *"اشرحهالك بالعربي اوّ بشكل ابسط؟"*
- If the child says yes (or asks in Arabic), then give a **mixed explanation**
(**English for terminologies + simple Arabic for explanation**).
احرص إن الشرح يكون بسيط، قصير، واضح، وكأنك بتحكي له من الحياة اليومية.
اذكر اسم {student_name} مرة واحدة بس في بداية الرد. متكررهوش تاني.
ممنوع تستخدم ألقاب زي "يا بطل" أو "يا شاطر".
لو الرد قصير جداً (جملة أو اتنين) ممكن تستغنى عن الاسم.
لما تذكر الصف {grade}، قولها بالطريقة اللي الأطفال المصريين بيقولوها:
الصف 4 = سنة رابعة ابتدائي، الصف 5 = سنة خامسة ابتدائي، وهكذا.
المصطلحات العلميّة: سيبها بالإنجليزي (**roots**, **photosynthesis**, **glucose**) مع شرح بسيط.
الصيغ الكيمياويّة زي H2O أو CO2 لازم تكتب زي ما هي.
الأرقام في الجُملَ العاديّة بالإنجليزي بالحروف (two, three).
الهَدف: إجابة بالإنجليزي واضحة ومبسّطة، وبعدها عرض مساعدة إضافية بالعربي لو الطفّل حب،
بحيث يبان إن "عَنان" بيشرح جوّه مَنَصّة "شارِع العُلوم".
"""
,
# -------- Saudi English --------
(
StudentNationality
.
SAUDI
,
StudyLanguage
.
ENGLISH
):
"""
إنت مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}.
لو الطفل سأل عن هويتك بصراحة (زي "إنت مين؟"، "عرِّفني بنفسك"، "إنت وش تسوي هنا؟")،
رُد بالنصّ الثابت هذا:
"أنا عَنان مؤسس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
⚠️ مهم: لا تستخدم هذا النص في أي حالة أخرى غير سؤال الهوية.
لو سأل أسئلة عامة (زي "كيفك؟"، "شكراً")، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج الإنجليزي السعودي للصف {grade} من ملف JSON
- للأسئلة العامة عن المنهج، اعرض الهيكل بوضوح
- للمحتوى المحدد، اربط بالسياق والوحدات ذات الصلة
بالنسبة لأسئلة العلوم أو المنهج:
- Always answer **in English first**.
- After answering, ask: *"اشرحهالك بالعربي اوّ بشكل ابسط؟"*
- If the child says yes (or asks in Arabic), then give a **mixed explanation**
(**English for terminologies + simple Arabic for explanation**).
خل الشرح واضح وسهل وبأمثلة من حياة الطفل اليوميّة.
اذكر اسم {student_name} مرّة وحدة فقط في بداية الرد. لا تكرره في نفس الرد.
ممنوع تستخدم ألقاب زي "يا بطل" أو "يا شاطر". الاسم الأول يكفي.
ولو الرد قصير جداً (جملة أو جملتين)، ممكن ما تذكر الاسم أبداً.
لما تذكر الصف {grade}، قولها بالطريقة اللي الأطفال السعوديين متعودين عليها:
الصف 4 = رابع ابتدائي، الصف 5 = خامس ابتدائي، وهكذا.
المصطلحات العلميّة: خليها بالإنجليزي (**roots**, **photosynthesis**, **glucose**) مع شرح مبسّط.
الصيغ الكيمياويّة مثل H2O أو CO2 لازم تكتب مثل ما هي.
الأرقام في النصوص العاديّة بالإنجليزي بالحروف (two, three).
الهدف: إجابة بالإنجليزي مبسّطة، وبعدها عرض مساعدة بالعربي لو الطفل حب،
عشان يبان إن "عَنان" يشرح داخل مَنَصّة "شارع العلوم".
"""
}
\ No newline at end of file
self_hosted_env/voice_agent/services/agent_helpers/context_generator.py
0 → 100644
View file @
92dffa87
import
logging
import
os
import
sys
sys
.
path
.
append
(
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'../../'
)))
from
typing
import
Dict
,
Tuple
from
core
import
StudentNationality
,
StudyLanguage
logger
=
logging
.
getLogger
(
__name__
)
class
ContextGenerator
:
"""Handles context generation for AI responses"""
def
__init__
(
self
,
openai_service
,
pgvector_service
):
self
.
openai_service
=
openai_service
self
.
pgvector
=
pgvector_service
def
generate_enhanced_context
(
self
,
search_results
:
list
[
Dict
],
student_info
:
Dict
,
query_type
:
str
)
->
str
:
"""Generate enhanced context with JSON-based curriculum structure awareness"""
if
not
search_results
:
return
""
is_arabic
=
student_info
[
'is_arabic'
]
study_language
=
student_info
[
'study_language'
]
grade
=
student_info
[
'grade'
]
if
study_language
==
StudyLanguage
.
ENGLISH
:
context_message
=
f
"📚 من المنهج الإنجليزي لمادة العلوم للصف {grade}:
\n\n
"
else
:
context_message
=
f
"📚 من المنهج العربي لمادة العلوم للصف {grade}:
\n\n
"
for
result
in
search_results
:
# Basic information
unit_info
=
f
"الوحدة: {result['unit']}"
if
result
.
get
(
'unit'
)
else
""
concept_info
=
f
"المفهوم: {result['concept']}"
if
result
.
get
(
'concept'
)
else
""
lesson_info
=
f
"الدرس: {result['lesson']}"
if
result
.
get
(
'lesson'
)
else
""
# Build header
context_parts
=
[
info
for
info
in
[
unit_info
,
concept_info
,
lesson_info
]
if
info
]
if
context_parts
:
context_message
+=
f
"**{' → '.join(context_parts)}**
\n
"
# Add content
context_message
+=
f
"{result['chunk_text']}
\n
"
# Add curriculum context if available
if
'curriculum_context'
in
result
:
ctx
=
result
[
'curriculum_context'
]
if
ctx
.
get
(
'navigation_hint'
):
context_message
+=
f
"
\n
💡 {ctx['navigation_hint']}
\n
"
if
ctx
.
get
(
'related_concepts'
)
and
query_type
==
"specific_content"
:
related
=
', '
.
join
(
ctx
[
'related_concepts'
][:
3
])
if
is_arabic
:
context_message
+=
f
"🔗 مفاهيم ذات صلة: {related}
\n
"
else
:
context_message
+=
f
"🔗 Related concepts: {related}
\n
"
context_message
+=
"
\n
---
\n\n
"
# Add instruction for using the context
if
study_language
==
StudyLanguage
.
ENGLISH
:
context_message
+=
f
"استخدم هذه المعلومات لتقديم شرح دقيق للطفل. المنهج إنجليزي فاذكر المصطلحات الإنجليزية مع الشرح بالعربي."
else
:
context_message
+=
f
"استخدم هذه المعلومات لتقديم شرح دقيق ومناسب للطفل باستخدام المصطلحات العربية."
return
context_message
def
search_enhanced_content
(
self
,
query
:
str
,
student_info
:
Dict
,
subject
:
str
,
top_k
:
int
=
3
)
->
list
[
Dict
]:
"""Search for enhanced content with curriculum context"""
if
not
self
.
pgvector
:
return
[]
try
:
query_embedding
=
self
.
openai_service
.
generate_embedding
(
query
)
search_results
=
self
.
pgvector
.
search_with_curriculum_context
(
query_embedding
=
query_embedding
,
grade
=
student_info
[
'grade'
],
subject
=
subject
,
is_arabic
=
student_info
[
'is_arabic'
],
limit
=
top_k
)
relevant_results
=
[
r
for
r
in
search_results
if
r
[
'distance'
]
<
1.3
]
if
search_results
else
[]
return
relevant_results
except
Exception
as
e
:
logger
.
warning
(
f
"Error in enhanced content search: {e}"
)
return
[]
\ No newline at end of file
self_hosted_env/voice_agent/services/agent_helpers/query_handlers.py
0 → 100644
View file @
92dffa87
import
os
import
sys
sys
.
path
.
append
(
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'../../'
)))
from
typing
import
Dict
,
Any
from
core
import
StudentNationality
,
StudyLanguage
import
logging
logger
=
logging
.
getLogger
(
__name__
)
GENERAL_CHAT_CONTEXTS
:
Dict
[
StudentNationality
,
str
]
=
{
StudentNationality
.
EGYPTIAN
:
"""
مَعلومات الطِّفل:
- الاِسم: {student_name}
- السَّنة: {grade}
- الجِنسيّة: مَصري
- لُغة الدِّراسة: {study_lang}
السُّؤال: "{query}"
خَليك بتِرُد بالعاميّة المَصري، وبطريقة بسيطة وودودة.
لو الطِّفل سأل عن هُويِّتك، استخدِم الرَّد المَحدَّد في التَّعليمات.
لو سأل عن مَعلوماته الشَّخصيّة، استَخدِم البيانات اللي فوق.
مَتستَخدِمش أي مَعلومات من المَنهج أو مَحتوى تعليمي هنا.
"""
,
StudentNationality
.
SAUDI
:
"""
معلومات الطالب:
- الاسم: {student_name}
- الصف: {grade}
- الجنسية: سعودي
- لغة الدراسة: {study_lang}
السؤال: "{query}"
رد باللهجة السعوديّة الطبيعية، خلّ الرد بسيط وودود.
إذا سأل عن هويتك، استخدم الرد المحدد في التعليمات.
إذا سأل عن معلوماته الشخصية، استخدم البيانات المتوفرة أعلاه.
لا تستخدم أي محتوى من المنهج أو دروس تعليمية هنا.
"""
}
UNSAFE_CONTEXTS
:
Dict
[
StudentNationality
,
Dict
[
str
,
str
]]
=
{
StudentNationality
.
EGYPTIAN
:
{
"unsafe_religion"
:
"""
الموضوع ده مش مناسب نتكلم فيه هنا. أي حاجة دينيّة الأحسن تسأل فيها بابا أو ماما أو حد كبير بتثق فيه.
تعالى نرجع للعلوم—تحب نحكي عن الفضاء، الحيوانات، أو جسم الإنسان؟
"""
,
"unsafe_personal"
:
"""
مينفعش تقول أسرارك أو تفاصيلك الشخصية هنا، دي حاجات تحتفظ بيها لنفسك أو تقولها لحد كبير زي بابا أو ماما.
طب إيه رأيك نكمّل في العلوم ونتكلم عن الطاقة أو الكواكب؟
"""
,
"unsafe_harmful"
:
"""
الكلام ده خطر ومش مناسب. لو حسيت بحاجة زي دي، لازم تكلم بابا أو ماما أو حد كبير تثق فيه.
تحب نغيّر الموضوع ونشوف إزاي النباتات بتعيش أو إزاي جسمنا بيتحرك؟
"""
,
"unsafe_sensitive_emotion"
:
"""
أنا حاسس إنك زعلان، وده طبيعي. الأحسن تحكي لماما أو بابا أو حد كبير قريب منك.
ممكن كمان نلهي نفسنا بحاجة من العلوم—زي الفضاء أو الحيوانات.
"""
,
},
StudentNationality
.
SAUDI
:
{
"unsafe_religion"
:
"""
هذا موضوع ما نقدر نتكلم فيه هنا. أي شي يخص الدين الأفضل تسأل فيه أبوك أو أمك أو شخص كبير تثق فيه.
خلنا نرجع للعلوم—تبغى نتكلم عن الفضاء، جسم الإنسان، أو النباتات؟
"""
,
"unsafe_personal"
:
"""
ما ينفع تشارك أسرارك أو معلوماتك الشخصية هنا، هذي لازم تقولها بس لأهلك أو شخص كبير تثق فيه.
وش رايك نكمل في العلوم ونتكلم عن الطاقة أو الكواكب؟
"""
,
"unsafe_harmful"
:
"""
الكلام ذا خطر وما يصلح. إذا حسيت بشي زي كذا لازم تحكي مع أبوك أو أمك أو شخص كبير تثق فيه.
خلنا نغيّر الموضوع—تحب نتكلم عن النباتات أو عن كيف يتحرك جسم الإنسان؟
"""
,
"unsafe_sensitive_emotion"
:
"""
واضح إنك حزين، وهذا شعور طبيعي. الأفضل تحكي مع أهلك أو شخص كبير قريب منك.
وممكن نلهي نفسنا بشي من العلوم—زي الفضاء أو الحيوانات.
"""
,
}
}
class
QueryHandler
:
"""Handles different types of queries and their classification"""
def
__init__
(
self
,
openai_service
,
pgvector_service
,
db_service
):
self
.
openai_service
=
openai_service
self
.
pgvector
=
pgvector_service
self
.
db_service
=
db_service
def
classify_query_type
(
self
,
query
:
str
,
student_info
:
Dict
[
str
,
Any
])
->
str
:
"""Enhanced query classification using LLM with contextual keyword hints"""
if
not
self
.
openai_service
.
is_available
():
return
"specific_content"
is_arabic
:
bool
=
student_info
.
get
(
'is_arabic'
,
True
)
grade
:
int
=
student_info
.
get
(
'grade'
,
4
)
# Keywords grouped by category
unsafe_keywords
=
{
"unsafe_religion"
:
[
"دين"
,
"ديانة"
,
"دينى"
,
"دينك"
,
"الله"
,
"إله"
,
"عبادة"
,
"صلوات"
,
"قرآن"
,
"إنجيل"
,
"تورا"
],
"unsafe_personal"
:
[
"عنوان"
,
"ساكن"
,
"موبايل"
,
"رقم"
,
"تليفون"
,
"شارع"
,
"عمارة"
,
"شقة"
],
"unsafe_harmful"
:
[
"مخدرات"
,
"سجاير"
,
"انتحار"
,
"اضرب"
,
"أقتل"
,
"أذى"
,
"دم"
,
"خطر"
],
"unsafe_sensitive_emotion"
:
[
"زعلان"
,
"حزين"
,
"موت"
,
"اتوفى"
,
"وَفاة"
,
"بعيط"
,
"عايط"
,
"اكتئاب"
]
}
q_lower
=
query
.
lower
()
keyword_flags
=
[
cat
for
cat
,
kws
in
unsafe_keywords
.
items
()
if
any
(
kw
in
q_lower
for
kw
in
kws
)]
classification_prompt
=
f
"""
صنف السؤال التالي إلى إحدى الفئات التالية:
1. "general_chat" - أسئلة دردشة عامة وشخصية عن الطالب أو المدرس
2. "overview" - أسئلة عن نظرة عامة على المنهج أو المحتوى الكامل
3. "navigation" - أسئلة عن وحدة أو مفهوم معين
4. "specific_content" - أسئلة محددة عن موضوع علمي معين
5. "unsafe_religion" - أي كلام عن الدين أو اختراع ديانة أو عبادة
6. "unsafe_personal" - مشاركة أسرار أو تفاصيل شخصية حساسة
7. "unsafe_harmful" - كلام عن العنف، الأذى للنفس، المخدرات، الشتائم
8. "unsafe_sensitive_emotion" - مواقف حزينة جداً مثل فقدان شخص أو بكاء شديد
السؤال: "{query}"
الطالب يدرس باللغة: {"العربية" if is_arabic else "الإنجليزية"}
الصف: {grade}
"""
if
keyword_flags
:
classification_prompt
+=
f
"
\n
ملاحظة: السؤال يحتوي على كلمات قد تكون مرتبطة بالفئات: {', '.join(keyword_flags)}.
\
تأكد من السياق جيداً قبل التصنيف."
classification_prompt
+=
"
\n\n
رد فقط بكلمة واحدة من الفئات المحددة أعلاه"
try
:
response
=
self
.
openai_service
.
client
.
chat
.
completions
.
create
(
model
=
"gpt-4o-mini"
,
messages
=
[{
"role"
:
"user"
,
"content"
:
classification_prompt
}],
temperature
=
0
,
max_tokens
=
20
)
classification
:
str
=
response
.
choices
[
0
]
.
message
.
content
.
strip
()
.
lower
()
valid_classes
=
{
"general_chat"
,
"overview"
,
"navigation"
,
"specific_content"
,
"unsafe_religion"
,
"unsafe_personal"
,
"unsafe_harmful"
,
"unsafe_sensitive_emotion"
}
if
classification
in
valid_classes
:
logger
.
info
(
f
"Query classified as: {classification} for query: '{query}' (flags={keyword_flags})"
)
return
classification
else
:
logger
.
warning
(
f
"Unexpected classification '{classification}' for query '{query}', "
"defaulting to 'specific_content'"
)
return
"specific_content"
except
Exception
as
e
:
logger
.
warning
(
f
"Error in query classification: {e}, defaulting to 'specific_content'"
)
return
"specific_content"
def
handle_general_chat_query
(
self
,
query
:
str
,
student_info
:
Dict
[
str
,
Any
])
->
str
:
"""Handle general chat queries using only student information"""
student_name
:
str
=
student_info
.
get
(
'student_name'
,
'الطالب'
)
grade
:
int
=
student_info
.
get
(
'grade'
,
4
)
nationality_str
:
str
=
student_info
.
get
(
'nationality'
,
'egyptian'
)
is_arabic
:
bool
=
student_info
.
get
(
'is_arabic'
,
True
)
study_lang
=
"العربية"
if
is_arabic
else
"الإنجليزية"
# Map nationality string to enum
nationality_mapping
=
{
'egyptian'
:
StudentNationality
.
EGYPTIAN
,
'saudi'
:
StudentNationality
.
SAUDI
}
nationality_enum
=
nationality_mapping
.
get
(
nationality_str
.
lower
()
.
strip
(),
StudentNationality
.
EGYPTIAN
)
# Get template with fallback
template
=
GENERAL_CHAT_CONTEXTS
.
get
(
nationality_enum
)
if
not
template
:
logger
.
warning
(
f
"No template found for nationality: {nationality_enum}, using Egyptian fallback"
)
template
=
GENERAL_CHAT_CONTEXTS
.
get
(
StudentNationality
.
EGYPTIAN
)
if
not
template
:
# Ultimate fallback if even Egyptian template is missing
logger
.
error
(
"No templates available in GENERAL_CHAT_CONTEXTS"
)
template
=
"""
معلومات الطالب:
- الاسم: {student_name}
- الصف: {grade}
- الجنسية: {nationality}
- لغة الدراسة: {study_lang}
السؤال: "{query}"
رد بطريقة بسيطة وودودة باستخدام معلومات الطالب المتوفرة أعلاه.
"""
try
:
context
=
template
.
format
(
student_name
=
student_name
,
grade
=
grade
,
nationality
=
nationality_str
,
study_lang
=
study_lang
,
query
=
query
)
return
context
except
Exception
as
e
:
logger
.
error
(
f
"Error formatting template: {e}"
)
# Return a simple fallback context
return
f
"""
معلومات الطالب: {student_name}, الصف {grade}
السؤال: "{query}"
رد بطريقة ودودة وبسيطة.
"""
def
handle_unsafe_religion_query
(
self
,
student_info
:
Dict
[
str
,
Any
])
->
str
:
"""Handle queries about religion or religious topics"""
nationality_str
:
str
=
student_info
.
get
(
'nationality'
,
'egyptian'
)
nationality_mapping
=
{
'egyptian'
:
StudentNationality
.
EGYPTIAN
,
'saudi'
:
StudentNationality
.
SAUDI
}
nationality_enum
=
nationality_mapping
.
get
(
nationality_str
.
lower
()
.
strip
(),
StudentNationality
.
EGYPTIAN
)
# Get the appropriate unsafe response template
unsafe_responses
=
UNSAFE_CONTEXTS
.
get
(
nationality_enum
,
{})
response_template
=
unsafe_responses
.
get
(
"unsafe_religion"
)
if
not
response_template
:
# Fallback to Egyptian template if not found
unsafe_responses
=
UNSAFE_CONTEXTS
.
get
(
StudentNationality
.
EGYPTIAN
,
{})
response_template
=
unsafe_responses
.
get
(
"unsafe_religion"
,
"هذا الموضوع غير مناسب للمناقشة هنا. يرجى التحدث مع الوالدين أو شخص بالغ موثوق."
)
logger
.
info
(
f
"Handled unsafe_religion query for nationality: {nationality_enum}"
)
return
response_template
.
strip
()
def
handle_unsafe_personal_query
(
self
,
student_info
:
Dict
[
str
,
Any
])
->
str
:
"""Handle queries involving sharing personal secrets or sensitive information"""
nationality_str
:
str
=
student_info
.
get
(
'nationality'
,
'egyptian'
)
nationality_mapping
=
{
'egyptian'
:
StudentNationality
.
EGYPTIAN
,
'saudi'
:
StudentNationality
.
SAUDI
}
nationality_enum
=
nationality_mapping
.
get
(
nationality_str
.
lower
()
.
strip
(),
StudentNationality
.
EGYPTIAN
)
# Get the appropriate unsafe response template
unsafe_responses
=
UNSAFE_CONTEXTS
.
get
(
nationality_enum
,
{})
response_template
=
unsafe_responses
.
get
(
"unsafe_personal"
)
if
not
response_template
:
# Fallback to Egyptian template if not found
unsafe_responses
=
UNSAFE_CONTEXTS
.
get
(
StudentNationality
.
EGYPTIAN
,
{})
response_template
=
unsafe_responses
.
get
(
"unsafe_personal"
,
"لا يجب مشاركة المعلومات الشخصية هنا. تحدث مع الوالدين فقط حول هذه الأمور."
)
logger
.
info
(
f
"Handled unsafe_personal query for nationality: {nationality_enum}"
)
return
response_template
.
strip
()
def
handle_unsafe_harmful_query
(
self
,
student_info
:
Dict
[
str
,
Any
])
->
str
:
"""Handle queries about violence, self-harm, drugs, or inappropriate language"""
nationality_str
:
str
=
student_info
.
get
(
'nationality'
,
'egyptian'
)
nationality_mapping
=
{
'egyptian'
:
StudentNationality
.
EGYPTIAN
,
'saudi'
:
StudentNationality
.
SAUDI
}
nationality_enum
=
nationality_mapping
.
get
(
nationality_str
.
lower
()
.
strip
(),
StudentNationality
.
EGYPTIAN
)
# Get the appropriate unsafe response template
unsafe_responses
=
UNSAFE_CONTEXTS
.
get
(
nationality_enum
,
{})
response_template
=
unsafe_responses
.
get
(
"unsafe_harmful"
)
if
not
response_template
:
# Fallback to Egyptian template if not found
unsafe_responses
=
UNSAFE_CONTEXTS
.
get
(
StudentNationality
.
EGYPTIAN
,
{})
response_template
=
unsafe_responses
.
get
(
"unsafe_harmful"
,
"هذا الموضوع غير آمن وغير مناسب. يرجى التحدث مع شخص بالغ موثوق إذا كان لديك مشاعر صعبة."
)
logger
.
warning
(
f
"Handled unsafe_harmful query for nationality: {nationality_enum}"
)
return
response_template
.
strip
()
def
handle_unsafe_sensitive_emotion_query
(
self
,
student_info
:
Dict
[
str
,
Any
])
->
str
:
"""Handle queries involving very sad situations like loss or extreme sadness"""
nationality_str
:
str
=
student_info
.
get
(
'nationality'
,
'egyptian'
)
nationality_mapping
=
{
'egyptian'
:
StudentNationality
.
EGYPTIAN
,
'saudi'
:
StudentNationality
.
SAUDI
}
nationality_enum
=
nationality_mapping
.
get
(
nationality_str
.
lower
()
.
strip
(),
StudentNationality
.
EGYPTIAN
)
# Get the appropriate unsafe response template
unsafe_responses
=
UNSAFE_CONTEXTS
.
get
(
nationality_enum
,
{})
response_template
=
unsafe_responses
.
get
(
"unsafe_sensitive_emotion"
)
if
not
response_template
:
# Fallback to Egyptian template if not found
unsafe_responses
=
UNSAFE_CONTEXTS
.
get
(
StudentNationality
.
EGYPTIAN
,
{})
response_template
=
unsafe_responses
.
get
(
"unsafe_sensitive_emotion"
,
"أشعر أنك حزين. هذه مشاعر طبيعية، ولكن من المهم التحدث مع الوالدين أو شخص بالغ موثوق."
)
logger
.
info
(
f
"Handled unsafe_sensitive_emotion query for nationality: {nationality_enum}"
)
return
response_template
.
strip
()
def
handle_overview_query
(
self
,
student_info
:
Dict
[
str
,
Any
],
subject
:
str
=
"Science"
)
->
str
:
"""Handle curriculum overview queries using JSON-based data"""
if
not
self
.
pgvector
:
if
student_info
[
'study_language'
]
==
StudyLanguage
.
ARABIC
:
return
f
"عذراً، لا يمكنني عرض المنهج حالياً للصف {student_info['grade']}"
else
:
return
f
"Sorry, I cannot show the curriculum for Grade {student_info['grade']} right now"
try
:
return
self
.
pgvector
.
get_overview_response
(
student_info
[
'grade'
],
student_info
[
'is_arabic'
],
subject
)
except
Exception
as
e
:
logger
.
error
(
f
"Error getting overview response: {e}"
)
if
student_info
[
'study_language'
]
==
StudyLanguage
.
ARABIC
:
return
f
"عذراً، حدث خطأ في عرض المنهج للصف {student_info['grade']}"
else
:
return
f
"Sorry, there was an error showing the curriculum for Grade {student_info['grade']}"
def
handle_navigation_query
(
self
,
query
:
str
,
student_info
:
Dict
[
str
,
Any
],
subject
:
str
=
"Science"
)
->
str
:
"""Handle unit/concept navigation queries using JSON structure"""
if
not
self
.
pgvector
:
return
self
.
handle_overview_query
(
student_info
,
subject
)
try
:
return
self
.
pgvector
.
get_unit_navigation_response
(
query
,
student_info
[
'grade'
],
student_info
[
'is_arabic'
],
subject
)
except
Exception
as
e
:
logger
.
error
(
f
"Error getting navigation response: {e}"
)
# Fallback to overview if navigation fails
return
self
.
handle_overview_query
(
student_info
,
subject
)
\ No newline at end of file
self_hosted_env/voice_agent/services/agent_helpers/response_generator.py
0 → 100644
View file @
92dffa87
import
os
import
sys
from
typing
import
Dict
from
fastapi
import
HTTPException
from
services.agent_helpers.agent_prompts
import
SYSTEM_PROMPTS
import
logging
sys
.
path
.
append
(
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'../../'
)))
from
core
import
StudentNationality
,
StudyLanguage
,
Models
logger
=
logging
.
getLogger
(
__name__
)
class
ResponseGenerator
:
"""Handles AI response generation and conversation management"""
def
__init__
(
self
,
openai_service
,
db_service
,
pedagogy_service
,
query_handler
,
context_generator
):
self
.
openai_service
=
openai_service
self
.
db_service
=
db_service
self
.
pedagogy_service
=
pedagogy_service
self
.
query_handler
=
query_handler
self
.
context_generator
=
context_generator
def
get_conversation_history
(
self
,
student_id
:
str
)
->
list
[
Dict
[
str
,
str
]]:
"""Get conversation history from database"""
try
:
return
self
.
db_service
.
get_chat_history
(
student_id
)
except
Exception
as
e
:
logger
.
error
(
f
"Error getting conversation history for {student_id}: {e}"
)
return
[]
def
add_message_to_history
(
self
,
student_id
:
str
,
message
:
str
,
role
:
str
=
"user"
):
"""Add message to database"""
try
:
self
.
db_service
.
add_message
(
student_id
,
role
,
message
)
# Limit history to prevent growth
self
.
db_service
.
limit_history
(
student_id
,
max_messages
=
38
)
except
Exception
as
e
:
logger
.
error
(
f
"Error adding message to history for {student_id}: {e}"
)
def
prepare_system_prompt
(
self
,
student_info
:
Dict
)
->
str
:
"""Prepare system prompt based on student information"""
student_name
=
student_info
.
get
(
'student_name'
,
'الطالب'
)
.
split
()[
0
]
study_language
=
student_info
[
'study_language'
]
# Map nationality
nationality_lower
=
student_info
[
'nationality'
]
.
lower
()
.
strip
()
nationality_mapping
=
{
'egyptian'
:
StudentNationality
.
EGYPTIAN
,
'saudi'
:
StudentNationality
.
SAUDI
}
nationality
=
nationality_mapping
.
get
(
nationality_lower
,
StudentNationality
.
EGYPTIAN
)
# Get appropriate system prompt
prompt_key
=
(
nationality
,
study_language
)
base_system_prompt
=
SYSTEM_PROMPTS
.
get
(
prompt_key
,
SYSTEM_PROMPTS
.
get
((
StudentNationality
.
EGYPTIAN
,
StudyLanguage
.
ARABIC
),
""
))
formatted_base_prompt
=
base_system_prompt
.
format
(
student_name
=
student_name
,
grade
=
student_info
[
'grade'
]
)
# Add Socratic instructions if any
socratic_instructions
=
self
.
pedagogy_service
.
get_socratic_instructions
(
student_info
[
'grade'
],
student_info
[
'nationality'
]
)
if
socratic_instructions
:
formatted_base_prompt
+=
f
"
\n\n
{socratic_instructions}"
return
formatted_base_prompt
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
:
"""Enhanced AI response generation with JSON-based curriculum structure awareness"""
if
not
self
.
openai_service
.
is_available
():
raise
HTTPException
(
status_code
=
500
,
detail
=
"Agent service not available"
)
try
:
# Get student info
student_info
=
self
.
db_service
.
get_student_info
(
student_id
)
if
not
student_info
:
raise
HTTPException
(
status_code
=
404
,
detail
=
f
"Student with ID {student_id} not found"
)
student_name
=
student_info
.
get
(
'student_name'
,
'الطالب'
)
.
split
()[
0
]
study_language
=
student_info
[
'study_language'
]
# Add user message to DB
self
.
add_message_to_history
(
student_id
,
user_message
,
"user"
)
conversation_history
=
self
.
get_conversation_history
(
student_id
)
# Classify query type
query_type
=
self
.
query_handler
.
classify_query_type
(
user_message
,
student_info
)
logger
.
info
(
f
"Query type: {query_type} for student {student_name} ({study_language.value})"
)
# *** HANDLE UNSAFE QUERIES IMMEDIATELY - NO SYSTEM PROMPT ***
if
query_type
.
startswith
(
"unsafe_"
):
if
query_type
==
"unsafe_religion"
:
unsafe_response
=
self
.
query_handler
.
handle_unsafe_religion_query
(
student_info
)
elif
query_type
==
"unsafe_personal"
:
unsafe_response
=
self
.
query_handler
.
handle_unsafe_personal_query
(
student_info
)
elif
query_type
==
"unsafe_harmful"
:
unsafe_response
=
self
.
query_handler
.
handle_unsafe_harmful_query
(
student_info
)
elif
query_type
==
"unsafe_sensitive_emotion"
:
unsafe_response
=
self
.
query_handler
.
handle_unsafe_sensitive_emotion_query
(
student_info
)
else
:
unsafe_response
=
"هذا الموضوع غير مناسب للمناقشة هنا."
# Save response directly and return - NO AI MODEL CALL
self
.
add_message_to_history
(
student_id
,
unsafe_response
,
"assistant"
)
logger
.
info
(
f
"Returned direct {query_type} response for {student_name}"
)
return
unsafe_response
# *** FOR SAFE QUERIES - PROCEED WITH NORMAL AI PROCESSING ***
# Prepare system prompt
formatted_base_prompt
=
self
.
prepare_system_prompt
(
student_info
)
# Prepare messages
messages
=
[]
messages
.
append
({
"role"
:
"system"
,
"content"
:
formatted_base_prompt
})
messages
.
extend
(
conversation_history
)
messages
.
append
({
"role"
:
"user"
,
"content"
:
user_message
})
# Handle different safe query types
if
query_type
==
"general_chat"
:
chat_context
=
self
.
query_handler
.
handle_general_chat_query
(
user_message
,
student_info
)
messages
.
append
({
"role"
:
"system"
,
"content"
:
f
"سياق المحادثة العامة:
\n
{chat_context}"
})
elif
query_type
==
"overview"
:
overview_response
=
self
.
query_handler
.
handle_overview_query
(
student_info
,
subject
)
messages
.
append
({
"role"
:
"system"
,
"content"
:
f
"المنهج الكامل من ملف JSON:
\n
{overview_response}"
})
elif
query_type
==
"navigation"
:
navigation_response
=
self
.
query_handler
.
handle_navigation_query
(
user_message
,
student_info
,
subject
)
messages
.
append
({
"role"
:
"system"
,
"content"
:
f
"تفاصيل الوحدة/المفهوم من JSON:
\n
{navigation_response}"
})
elif
query_type
==
"specific_content"
:
# Enhanced content search
relevant_results
=
self
.
context_generator
.
search_enhanced_content
(
user_message
,
student_info
,
subject
,
top_k
)
if
relevant_results
:
enhanced_context
=
self
.
context_generator
.
generate_enhanced_context
(
relevant_results
,
student_info
,
query_type
)
messages
.
append
({
"role"
:
"system"
,
"content"
:
enhanced_context
})
logger
.
info
(
f
"Added enhanced context with {len(relevant_results)} chunks"
)
# Generate response using AI model
response
=
self
.
openai_service
.
client
.
chat
.
completions
.
create
(
model
=
model
,
messages
=
messages
,
temperature
=
temperature
)
ai_response
=
response
.
choices
[
0
]
.
message
.
content
.
strip
()
if
not
ai_response
:
raise
ValueError
(
"Empty response from AI model"
)
# Save AI response
self
.
add_message_to_history
(
student_id
,
ai_response
,
"assistant"
)
logger
.
info
(
f
"Generated {query_type} response for {student_name} ({study_language.value}): {len(ai_response)} characters"
)
return
ai_response
except
HTTPException
:
raise
except
Exception
as
e
:
logger
.
error
(
f
"Error generating AI response: {e}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"AI response generation failed: {str(e)}"
)
\ No newline at end of file
self_hosted_env/voice_agent/services/agent_service.py
View file @
92dffa87
...
@@ -11,177 +11,22 @@ from services.openai_service import OpenAIService
...
@@ -11,177 +11,22 @@ from services.openai_service import OpenAIService
from
services.chat_database_service
import
ChatDatabaseService
,
StudyLanguage
from
services.chat_database_service
import
ChatDatabaseService
,
StudyLanguage
from
services.pedagogy_service
import
PedagogyService
from
services.pedagogy_service
import
PedagogyService
from
services.connection_pool
import
ConnectionPool
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
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
# Enhanced system prompts (keeping existing ones but adding curriculum awareness instructions)
ENHANCED_SYSTEM_PROMPTS
:
Dict
[
tuple
,
str
]
=
{
# ---------- Egyptian + Arabic ----------
(
StudentNationality
.
EGYPTIAN
,
StudyLanguage
.
ARABIC
):
"""
إنك مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}.
فقط لو الطفّل سأل عن هويتك بصراحة ووضح (مثل "إنت مين؟"، "عرّفني بنفسك"، "إنت بتعمل إيه هنا؟")،
رُد بالنصّ الثابت ده:
"أنا عَنان مؤسِّس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
لو سأل أسئلة عامة زي "نت عارف انا مين؟" أو "إزيك؟" أو "شكرا"، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج المصري للصف {grade} من ملف JSON
- لو السؤال عن نظرة عامة على المنهج ("ماذا ندرس؟"، "أظهر المنهج"، "ما المواضيع؟")، اعرض هيكل المنهج بوضوح
- لو السؤال عن محتوى معيّن، استخدم السياق من المنهج وارבط بالوحدات والمفاهيم ذات الصلة
- اذكر دائماً موقع الموضوع في المنهج: "هذا من الوحدة الأولى، المفهوم الثاني"
- وضِّح الروابط: "هذا يرتبط بما تعلمناه عن..." أو "هذا يؤدي إلى ما سنتعلمه عن..."
ملاحظة مُلزمة: كلمة "منصّة" لازم تكتبها دايماً كده بالضبط: **مَنَصّة** (بالفتحة على الميم والنون)،
عشان الـTTS ينطقها صح.
في باقي الردود، رَد باللهجة المصريّة الطبيعيّة كأنّك بتكَلّم {student_name} قصادك.
خَلّي الكلام بسيط، واضح، وقَريب من وجدنه.
الجُملَ قُصيرَة ومُترابطة، مُش مَقطَّعة.
اشرح كأنّك بتحكي له حكاية أو بتوريّه حاجَة من الحَياة حوالينا، مُش بتقرا من كتاب.
مُمكن تُذكر اسم {student_name} مَرّة واحدة في أوّل الرّد فَقَط.
بعد كدا مَمنوع تكرار الاسم في نَفس الرّد، حَتّى في الأسئلة الختاميّة.
مَمنوع تستَعملُ أي ألقاب زي "يا بَطَل" أو "يا شاطر"، الاسم الأوَّل بَس.
ولو الرّد قُصيرَ جدّاً (جملة أو اتنين)، مُمكن تستَغنَى عن ذكر الاسم خالص.
لو فيه مُصطَلَح صَعب، فَسّره بكلمة أسهَل.
لو فيه رَمز كيمياوي زي H2O أو CO2، اكتبه زي ما هو.
الأرقام العاديّة اكتبها بالحروف العربي زي "اتنين" أو "تَلاتة".
استخدمُ التشكيل الكامل على كُلّ الكلام عَشان يطّلع بالصّوت زي نُطق اللّهجة المصريّة الطَبيعيّ.
لو {student_name} مكتوب بالإنجليزي، اكتبه دايماً بالعَربي في ردودك.
لَمّا تُذكر الصف {grade}، قُله بالطريقة الطبيعيّة زي ما الأطفال بيقولوها: الصف 4 = سنة رابعة ابتدائي، الصف 5 = سنة خامسة ابتدائي، وهكذا.
الهَدف: رَد قُصيرَ يُعلِّم ويُوصَّل المَعلومة، ويُبان إن "عَنان" بيشرَح للطفل جوّه مَنَصّة "شارِع العلوم"، مُش كتاب بيتقري.
"""
,
# ---------- Saudi + Arabic ----------
(
StudentNationality
.
SAUDI
,
StudyLanguage
.
ARABIC
):
"""
إنت مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}.
فقط لو الطفل سأل عن هويتك بصراحة ووضح (مثل "إنت مين؟"، "عرِّفني بنفسك"، "إنت وش تسوي هنا؟")،
رُد بالنص الثابت هذا:
"أنا عَنان مؤسِّس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
لو سأل أسئلة عامة مثل "نت عارف انا مين؟" أو "كيفك؟" أو "شكرا"، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج السعودي للصف {grade} من ملف JSON
- لو السؤال عن نظرة عامة على المنهج ("ماذا ندرس؟"، "أظهر المنهج"، "ما المواضيع؟")، اعرض هيكل المنهج بوضوح
- لو السؤال عن محتوى معيّن، استخدم السياق من المنهج وارבط بالوحدات والمفاهيم ذات الصلة
- اذكر دائماً موقع الموضوع في المنهج: "هذا من الوحدة الأولى، المفهوم الثاني"
- وضِّح الروابط: "هذا يرتبط بما تعلمناه عن..." أو "هذا يؤدي إلى ما سنتعلمه عن..."
ملاحظة مُلزمة: كلمة "منصّة" لازم تكتبها دايماً كده بالضبط: **مَنَصّة** (بالفتحة على الميم والنون)،
عشان الـTTS ينطقها صح.
في باقي الردود، رَد باللهجة السعوديّة الطبيعيّة، كأنك تشرح له قدّامك.
خل الشرح واضح وسهل، لكن لا يكون ناشف.
اشرح كأنك تسولف معه وتشبّه بأشياء من حياته اليوميّة.
اذكر اسم {student_name} مرّة وحدة فقط في بداية الرد.
بعد كذا لا تكرره في النص ولا في الأسئلة الختاميّة.
ممنوع تستخدم أي ألقاب مثل "يا بطل" أو "يا شاطر"، الاسم الأول يكفي.
ولو الرد قصير جداً (جملة أو جملتين), تقدر ما تذكر الاسم أبداً.
لو فيه مصطلح صعب، فسِّره بكلمة أبسط.
الرموز الكيمياوية مثل H2O أو CO2 تكتب مثل ما هي.
الأرقام في الكلام العادي تكتبها بالحروف العربي زي "اثنين" أو "ثلاثة".
استخدم التشكيل بس على الكلمات اللي ممكن الـTTS يخبّص فيها أو يقرأها خطأ، واترك الباقي بدون تشكيل عشان يطلع طبيعي.
لو {student_name} مكتوب بالإنجليزي، اكتبه دايماً بالعربي في ردودك.
لما تذكر الصف {grade}، قولها بالطريقة اللي الطفل متعود يسمعها: الصف 4 = رابع ابتدائي، الصف 5 = خامس ابتدائي، وهكذا.
الهدف: رد مبسِّط، قريب، ويبيِّن إن "عَنان" يشرح للطفل جوّه مَنَصّة "شارع العلوم"، مو يقرأ من كتاب.
"""
,
# -------- Egyptian English --------
(
StudentNationality
.
EGYPTIAN
,
StudyLanguage
.
ENGLISH
):
"""
إنت مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}. لو الطفّل سأل عن هويتك بصراحة (زي "إنت مين؟"، "عرِّفني بنفسك")،
رُد بالنصّ الثابت ده:
"أنا عَنان مؤسس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
لو سأل أسئلة عامة (زي "إزيك؟"، "شكراً")، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج الإنجليزي المصري للصف {grade} من ملف JSON
- للأسئلة العامة عن المنهج، اعرض الهيكل بوضوح
- للمحتوى المحدد، اربط بالسياق والوحدات ذات الصلة
بالنسبة لأسئلة العلوم أو المنهج:
- Always answer **in English first**.
- After answering, ask: *"اشرحهالك بالعربي اوّ بشكل ابسط؟"*
- If the child says yes (or asks in Arabic), then give a **mixed explanation**
(**English for terminologies + simple Arabic for explanation**).
احرص إن الشرح يكون بسيط، قصير، واضح، وكأنك بتحكي له من الحياة اليومية.
اذكر اسم {student_name} مرة واحدة بس في بداية الرد. متكررهوش تاني.
ممنوع تستخدم ألقاب زي "يا بطل" أو "يا شاطر".
لو الرد قصير جداً (جملة أو اتنين) ممكن تستغنى عن الاسم.
لما تذكر الصف {grade}، قولها بالطريقة اللي الأطفال المصريين بيقولوها:
الصف 4 = سنة رابعة ابتدائي، الصف 5 = سنة خامسة ابتدائي، وهكذا.
المصطلحات العلميّة: سيبها بالإنجليزي (**roots**, **photosynthesis**, **glucose**) مع شرح بسيط.
الصيغ الكيمياويّة زي H2O أو CO2 لازم تكتب زي ما هي.
الأرقام في الجُملَ العاديّة بالإنجليزي بالحروف (two, three).
الهَدف: إجابة بالإنجليزي واضحة ومبسّطة، وبعدها عرض مساعدة إضافية بالعربي لو الطفّل حب،
بحيث يبان إن "عَنان" بيشرح جوّه مَنَصّة "شارِع العُلوم".
"""
,
# -------- Saudi English --------
(
StudentNationality
.
SAUDI
,
StudyLanguage
.
ENGLISH
):
"""
إنت مُدرِّس لطفل في ابتدائي اسمه {student_name} في الصف {grade}.
لو الطفل سأل عن هويتك بصراحة (زي "إنت مين؟"، "عرِّفني بنفسك"، "إنت وش تسوي هنا؟")،
رُد بالنصّ الثابت هذا:
"أنا عَنان مؤسس شارع العلوم، وإنت هنا على مَنَصّة Science Street Lab،
وأنا هنا عشان أساعدك تتعلَّم أي حاجة عايز تتعلَّمها في العلوم."
لو سأل أسئلة عامة (زي "كيفك؟"، "شكراً")، رد بطريقة طبيعية ودودة باستخدام اسمه {student_name}.
**للمنهج والتوجه التعليمي:**
- عندك وعي كامل بالمنهج الإنجليزي السعودي للصف {grade} من ملف JSON
- للأسئلة العامة عن المنهج، اعرض الهيكل بوضوح
- للمحتوى المحدد، اربط بالسياق والوحدات ذات الصلة
بالنسبة لأسئلة العلوم أو المنهج:
- Always answer **in English first**.
- After answering, ask: *"اشرحهالك بالعربي اوّ بشكل ابسط؟"*
- If the child says yes (or asks in Arabic), then give a **mixed explanation**
(**English for terminologies + simple Arabic for explanation**).
خل الشرح واضح وسهل وبأمثلة من حياة الطفل اليوميّة.
اذكر اسم {student_name} مرّة وحدة فقط في بداية الرد. لا تكرره في نفس الرد.
ممنوع تستخدم ألقاب زي "يا بطل" أو "يا شاطر". الاسم الأول يكفي.
ولو الرد قصير جداً (جملة أو جملتين)، ممكن ما تذكر الاسم أبداً.
لما تذكر الصف {grade}، قولها بالطريقة اللي الأطفال السعوديين متعودين عليها:
الصف 4 = رابع ابتدائي، الصف 5 = خامس ابتدائي، وهكذا.
المصطلحات العلميّة: خليها بالإنجليزي (**roots**, **photosynthesis**, **glucose**) مع شرح مبسّط.
الصيغ الكيمياويّة مثل H2O أو CO2 لازم تكتب مثل ما هي.
الأرقام في النصوص العاديّة بالإنجليزي بالحروف (two, three).
الهدف: إجابة بالإنجليزي مبسّطة، وبعدها عرض مساعدة بالعربي لو الطفل حب،
عشان يبان إن "عَنان" يشرح داخل مَنَصّة "شارع العلوم".
"""
}
class
AgentService
:
class
AgentService
:
"""
Enhanced service class for handling AI agent conversations with JSON-based curriculum stru
cture"""
"""
Main service class for handling AI agent conversations with modular archite
cture"""
def
__init__
(
self
,
use_pgvector
:
bool
=
True
,
pool_handler
:
Optional
[
ConnectionPool
]
=
None
):
def
__init__
(
self
,
use_pgvector
:
bool
=
True
,
pool_handler
:
Optional
[
ConnectionPool
]
=
None
):
# Initialize core services
self
.
openai_service
=
OpenAIService
()
self
.
openai_service
=
OpenAIService
()
if
not
self
.
openai_service
.
is_available
():
if
not
self
.
openai_service
.
is_available
():
logger
.
warning
(
"Warning: OPENAI_API_KEY not found. Agent service will be disabled."
)
logger
.
warning
(
"Warning: OPENAI_API_KEY not found. Agent service will be disabled."
)
self
.
client
=
None
else
:
self
.
client
=
self
.
openai_service
.
client
# Database setup
self
.
pool_handler
=
pool_handler
self
.
pool_handler
=
pool_handler
if
self
.
pool_handler
is
None
:
if
self
.
pool_handler
is
None
:
self
.
pool_handler
=
ConnectionPool
(
self
.
pool_handler
=
ConnectionPool
(
...
@@ -194,339 +39,39 @@ class AgentService:
...
@@ -194,339 +39,39 @@ class AgentService:
port
=
os
.
getenv
(
"DB_PORT"
)
port
=
os
.
getenv
(
"DB_PORT"
)
)
)
self
.
use_pgvector
=
use_pgvector
# Pass the same pool handler to both services
self
.
db_service
=
ChatDatabaseService
(
self
.
pool_handler
)
self
.
db_service
=
ChatDatabaseService
(
self
.
pool_handler
)
# PGVector setup
self
.
use_pgvector
=
use_pgvector
if
self
.
use_pgvector
:
if
self
.
use_pgvector
:
self
.
pgvector
=
PGVectorService
(
self
.
pool_handler
)
self
.
pgvector
=
PGVectorService
(
self
.
pool_handler
)
# Setup curriculum table if needed
self
.
pgvector
.
setup_curriculum_table
()
self
.
pgvector
.
setup_curriculum_table
()
else
:
else
:
self
.
pgvector
=
None
self
.
pgvector
=
None
self
.
pedagogy_service
=
PedagogyService
()
self
.
pedagogy_service
=
PedagogyService
()
self
.
student_info
=
{}
def
is_available
(
self
)
->
bool
:
return
self
.
client
is
not
None
def
get_conversation_history
(
self
,
student_id
:
str
)
->
List
[
Dict
[
str
,
str
]]:
"""Get conversation history from database"""
try
:
return
self
.
db_service
.
get_chat_history
(
student_id
)
except
Exception
as
e
:
logger
.
error
(
f
"Error getting conversation history for {student_id}: {e}"
)
return
[]
def
add_message_to_history
(
self
,
student_id
:
str
,
message
:
str
,
role
:
str
=
"user"
):
"""Add message to database"""
try
:
self
.
db_service
.
add_message
(
student_id
,
role
,
message
)
# Limit history to prevent growth
self
.
db_service
.
limit_history
(
student_id
,
max_messages
=
38
)
except
Exception
as
e
:
logger
.
error
(
f
"Error adding message to history for {student_id}: {e}"
)
def
classify_query_type
(
self
,
query
:
str
,
student_info
:
Dict
)
->
str
:
"""Enhanced query classification using LLM based on JSON structure"""
if
not
self
.
is_available
():
return
"specific_content"
is_arabic
=
student_info
.
get
(
'is_arabic'
,
True
)
grade
=
student_info
.
get
(
'grade'
,
4
)
classification_prompt
=
f
"""
صنف السؤال التالي إلى إحدى الفئات الأربع:
1. "general_chat" - أسئلة دردشة عامة وشخصية عن الطالب أو المدرس
أمثلة: "إنت مين؟", "إزيك؟", "نت عارف انا مين؟", "انت عارف انا في سنة كام؟", "شكرا ليك", "who are you?", "how are you?", "do you know me?", "thank you"
2. "overview" - أسئلة عن نظرة عامة على المنهج أو المحتوى الكامل
أمثلة: "ماذا ندرس؟", "أظهر المنهج", "what do we study?", "show curriculum"
3. "navigation" - أسئلة عن وحدة أو مفهوم معين
أمثلة: "ما في الوحدة الأولى؟", "what's in unit 1?", "أخبرني عن مفهوم الطاقة"
4. "specific_content" - أسئلة محددة عن موضوع علمي معين
أمثلة: "ما هو التمثيل الضوئي؟", "what is photosynthesis?", "كيف تعمل الخلية؟"
السؤال: "{query}"
الطالب يدرس باللغة: {"العربية" if is_arabic else "الإنجليزية"}
الصف: {grade}
رد فقط بكلمة واحدة: general_chat أو overview أو navigation أو specific_content
"""
try
:
response
=
self
.
client
.
chat
.
completions
.
create
(
model
=
"gpt-4o-mini"
,
messages
=
[{
"role"
:
"user"
,
"content"
:
classification_prompt
}],
temperature
=
0
,
max_tokens
=
10
)
classification
=
response
.
choices
[
0
]
.
message
.
content
.
strip
()
.
lower
()
if
classification
in
[
"general_chat"
,
"overview"
,
"navigation"
,
"specific_content"
]:
logger
.
info
(
f
"Query classified as: {classification} for query: '{query}'"
)
return
classification
else
:
logger
.
warning
(
f
"Unknown classification: {classification}, defaulting to specific_content"
)
return
"specific_content"
except
Exception
as
e
:
logger
.
warning
(
f
"Error in query classification: {e}, defaulting to specific_content"
)
return
"specific_content"
def
handle_overview_query
(
self
,
student_info
:
Dict
,
subject
:
str
=
"Science"
)
->
str
:
"""Handle curriculum overview queries using JSON-based data"""
if
not
self
.
pgvector
:
if
student_info
[
'study_language'
]
==
StudyLanguage
.
ARABIC
:
return
f
"عذراً، لا يمكنني عرض المنهج حالياً للصف {student_info['grade']}"
else
:
return
f
"Sorry, I cannot show the curriculum for Grade {student_info['grade']} right now"
return
self
.
pgvector
.
get_overview_response
(
student_info
[
'grade'
],
student_info
[
'is_arabic'
],
subject
)
def
handle_general_chat_query
(
self
,
query
:
str
,
student_info
:
Dict
)
->
str
:
"""Handle general chat queries using only student information"""
student_name
=
student_info
.
get
(
'student_name'
,
'الطالب'
)
grade
=
student_info
.
get
(
'grade'
,
4
)
nationality
=
student_info
.
get
(
'nationality'
,
'مصري'
)
is_arabic
=
student_info
.
get
(
'is_arabic'
,
True
)
study_language
=
student_info
[
'study_language'
]
# Create a simple context with student info only
if
is_arabic
:
context
=
f
"""
معلومات الطالب:
- الاسم: {student_name}
- الصف: {grade}
- الجنسية: {nationality}
- لغة الدراسة: {"العربية" if is_arabic else "الإنجليزية"}
السؤال: "{query}"
أجب بناء على معلومات الطالب فقط. لا تستخدم أي معلومات من المنهج أو محتوى تعليمي.
إذا سأل الطالب عن هويتك، استخدم الرد المحدد في التعليمات.
إذا سأل عن معلوماته الشخصية، استخدم البيانات المتاحة أعلاه.
كن ودوداً وبسيطاً في الرد.
"""
else
:
context
=
f
"""
Student Information:
- Name: {student_name}
- Grade: {grade}
- Nationality: {nationality}
- Study Language: {"Arabic" if is_arabic else "English"}
Question: "{query}"
Answer based only on the student's information above. Do not use any curriculum or educational content.
If the student asks about your identity, use the specified response in the instructions.
If they ask about their personal information, use the data available above.
Be friendly and simple in your response.
"""
return
context
def
handle_navigation_query
(
self
,
query
:
str
,
student_info
:
Dict
,
subject
:
str
=
"Science"
)
->
str
:
"""Handle unit/concept navigation queries using JSON structure"""
if
not
self
.
pgvector
:
return
self
.
handle_overview_query
(
student_info
,
subject
)
return
self
.
pgvector
.
get_unit_navigation_response
(
query
,
student_info
[
'grade'
],
student_info
[
'is_arabic'
],
subject
)
def
generate_enhanced_context
(
self
,
search_results
:
List
[
Dict
],
student_info
:
Dict
,
query_type
:
str
)
->
str
:
"""Generate enhanced context with JSON-based curriculum structure awareness"""
if
not
search_results
:
return
""
is_arabic
=
student_info
[
'is_arabic'
]
study_language
=
student_info
[
'study_language'
]
grade
=
student_info
[
'grade'
]
if
study_language
==
StudyLanguage
.
ENGLISH
:
context_message
=
f
"📚 من المنهج الإنجليزي لمادة العلوم للصف {grade}:
\n\n
"
else
:
context_message
=
f
"📚 من المنهج العربي لمادة العلوم للصف {grade}:
\n\n
"
for
result
in
search_results
:
# Basic information
unit_info
=
f
"الوحدة: {result['unit']}"
if
result
.
get
(
'unit'
)
else
""
concept_info
=
f
"المفهوم: {result['concept']}"
if
result
.
get
(
'concept'
)
else
""
lesson_info
=
f
"الدرس: {result['lesson']}"
if
result
.
get
(
'lesson'
)
else
""
# Build header
context_parts
=
[
info
for
info
in
[
unit_info
,
concept_info
,
lesson_info
]
if
info
]
if
context_parts
:
context_message
+=
f
"**{' → '.join(context_parts)}**
\n
"
# Add content
context_message
+=
f
"{result['chunk_text']}
\n
"
# Add curriculum context if available
if
'curriculum_context'
in
result
:
ctx
=
result
[
'curriculum_context'
]
if
ctx
.
get
(
'navigation_hint'
):
context_message
+=
f
"
\n
💡 {ctx['navigation_hint']}
\n
"
if
ctx
.
get
(
'related_concepts'
)
and
query_type
==
"specific_content"
:
related
=
', '
.
join
(
ctx
[
'related_concepts'
][:
3
])
if
is_arabic
:
context_message
+=
f
"🔗 مفاهيم ذات صلة: {related}
\n
"
else
:
context_message
+=
f
"🔗 Related concepts: {related}
\n
"
context_message
+=
"
\n
---
\n\n
"
# Initialize modular components
self
.
query_handler
=
QueryHandler
(
self
.
openai_service
,
self
.
pgvector
,
self
.
db_service
)
# Add instruction for using the context
self
.
context_generator
=
ContextGenerator
(
self
.
openai_service
,
self
.
pgvector
)
if
study_language
==
StudyLanguage
.
ENGLISH
:
self
.
response_generator
=
ResponseGenerator
(
context_message
+=
f
"استخدم هذه المعلومات لتقديم شرح دقيق للطفل. المنهج إنجليزي فاذكر المصطلحات الإنجليزية مع الشرح بالعربي."
self
.
openai_service
,
self
.
db_service
,
self
.
pedagogy_service
,
else
:
self
.
query_handler
,
self
.
context_generator
context_message
+=
f
"استخدم هذه المعلومات لتقديم شرح دقيق ومناسب للطفل باستخدام المصطلحات العربية."
return
context_message
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
:
"""Enhanced AI response generation with JSON-based curriculum structure awareness"""
if
not
self
.
is_available
():
raise
HTTPException
(
status_code
=
500
,
detail
=
"Agent service not available"
)
try
:
# Get student info with explicit language
student_info
=
self
.
db_service
.
get_student_info
(
student_id
)
if
not
student_info
:
raise
HTTPException
(
status_code
=
404
,
detail
=
f
"Student with ID {student_id} not found"
)
# Extract information
full_name
=
student_info
.
get
(
'student_name'
,
'الطالب'
)
student_name
=
full_name
.
split
()[
0
]
if
full_name
else
"الطالب"
study_language
=
student_info
[
'study_language'
]
# Map nationality
nationality_lower
=
student_info
[
'nationality'
]
.
lower
()
.
strip
()
nationality_mapping
=
{
'egyptian'
:
StudentNationality
.
EGYPTIAN
,
'saudi'
:
StudentNationality
.
SAUDI
}
nationality
=
nationality_mapping
.
get
(
nationality_lower
,
StudentNationality
.
EGYPTIAN
)
# Add user message to DB
self
.
add_message_to_history
(
student_id
,
user_message
,
"user"
)
conversation_history
=
self
.
get_conversation_history
(
student_id
)
# Classify query type based on JSON structure
query_type
=
self
.
classify_query_type
(
user_message
,
student_info
)
logger
.
info
(
f
"Query type: {query_type} for student {student_name} ({study_language.value})"
)
# Get appropriate system prompt
prompt_key
=
(
nationality
,
study_language
)
base_system_prompt
=
ENHANCED_SYSTEM_PROMPTS
.
get
(
prompt_key
,
ENHANCED_SYSTEM_PROMPTS
.
get
((
StudentNationality
.
EGYPTIAN
,
StudyLanguage
.
ARABIC
),
""
))
formatted_base_prompt
=
base_system_prompt
.
format
(
student_name
=
student_name
,
grade
=
student_info
[
'grade'
]
)
# Add Socratic instructions if any
socratic_instructions
=
self
.
pedagogy_service
.
get_socratic_instructions
(
student_info
[
'grade'
],
student_info
[
'nationality'
]
)
if
socratic_instructions
:
formatted_base_prompt
+=
f
"
\n\n
{socratic_instructions}"
# Prepare messages
messages
=
[]
has_system_message
=
conversation_history
and
conversation_history
[
0
]
.
get
(
"role"
)
==
"system"
if
not
has_system_message
:
messages
.
append
({
"role"
:
"system"
,
"content"
:
formatted_base_prompt
})
self
.
add_message_to_history
(
student_id
,
formatted_base_prompt
,
"system"
)
messages
.
extend
(
conversation_history
)
# Handle different query types with JSON-based curriculum awareness
if
query_type
==
"general_chat"
:
# Handle general chat with student info only
chat_context
=
self
.
handle_general_chat_query
(
user_message
,
student_info
)
messages
.
append
({
"role"
:
"system"
,
"content"
:
f
"سياق المحادثة العامة:
\n
{chat_context}"
})
elif
query_type
==
"overview"
:
# Direct curriculum overview from JSON
overview_response
=
self
.
handle_overview_query
(
student_info
,
subject
)
messages
.
append
({
"role"
:
"system"
,
"content"
:
f
"المنهج الكامل من ملف JSON:
\n
{overview_response}"
})
elif
query_type
==
"navigation"
:
# Unit/concept navigation from JSON structure
navigation_response
=
self
.
handle_navigation_query
(
user_message
,
student_info
,
subject
)
messages
.
append
({
"role"
:
"system"
,
"content"
:
f
"تفاصيل الوحدة/المفهوم من JSON:
\n
{navigation_response}"
})
elif
query_type
==
"specific_content"
and
self
.
pgvector
:
# Enhanced content search with JSON-based curriculum context
try
:
query_embedding
=
self
.
openai_service
.
generate_embedding
(
user_message
)
search_results
=
self
.
pgvector
.
search_with_curriculum_context
(
query_embedding
=
query_embedding
,
grade
=
student_info
[
'grade'
],
subject
=
subject
,
is_arabic
=
student_info
[
'is_arabic'
],
limit
=
top_k
)
relevant_results
=
[
r
for
r
in
search_results
if
r
[
'distance'
]
<
1.3
]
if
search_results
else
[]
if
relevant_results
:
enhanced_context
=
self
.
generate_enhanced_context
(
relevant_results
,
student_info
,
query_type
)
)
messages
.
append
({
"role"
:
"system"
,
"content"
:
enhanced_context
})
logger
.
info
(
f
"Added enhanced JSON-based context with {len(relevant_results)} chunks"
)
except
Exception
as
e
:
def
is_available
(
self
)
->
bool
:
logger
.
warning
(
f
"Error in enhanced content search: {e}"
)
return
self
.
openai_service
.
is_available
(
)
# Generate response
def
generate_response
(
self
,
user_message
:
str
,
student_id
:
str
,
subject
:
str
=
"Science"
,
response
=
self
.
client
.
chat
.
completions
.
create
(
model
:
str
=
Models
.
chat
,
temperature
:
float
=
0.3
,
top_k
:
int
=
3
)
->
str
:
model
=
model
,
"""Main response generation method"""
messages
=
messages
,
return
self
.
response_generator
.
generate_response
(
temperature
=
temperature
user_message
,
student_id
,
subject
,
model
,
temperature
,
top_k
)
)
ai_response
=
response
.
choices
[
0
]
.
message
.
content
.
strip
()
if
not
ai_response
:
raise
ValueError
(
"Empty response from AI model"
)
# Save AI response
self
.
add_message_to_history
(
student_id
,
ai_response
,
"assistant"
)
logger
.
info
(
f
"Generated {query_type} response for {student_name} ({study_language.value}): {len(ai_response)} characters"
)
return
ai_response
except
HTTPException
:
raise
except
Exception
as
e
:
logger
.
error
(
f
"Error generating AI response: {e}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"AI response generation failed: {str(e)}"
)
def
search_similar
(
self
,
query_embedding
:
List
[
float
],
student_id
:
str
,
def
search_similar
(
self
,
query_embedding
:
List
[
float
],
student_id
:
str
,
subject
:
str
=
"chemistry"
,
top_k
:
int
=
3
):
subject
:
str
=
"chemistry"
,
top_k
:
int
=
3
):
"""Search similar content with student-specific filtering
and JSON-based curriculum awareness
"""
"""Search similar content with student-specific filtering"""
if
not
self
.
pgvector
:
if
not
self
.
pgvector
:
raise
HTTPException
(
status_code
=
400
,
detail
=
"PGVector service not enabled"
)
raise
HTTPException
(
status_code
=
400
,
detail
=
"PGVector service not enabled"
)
...
@@ -535,8 +80,6 @@ Be friendly and simple in your response.
...
@@ -535,8 +80,6 @@ Be friendly and simple in your response.
if
not
student_info
:
if
not
student_info
:
raise
HTTPException
(
status_code
=
404
,
detail
=
f
"Student with ID {student_id} not found"
)
raise
HTTPException
(
status_code
=
404
,
detail
=
f
"Student with ID {student_id} not found"
)
logger
.
info
(
f
"Enhanced search for student {student_info['student_name']} who studies in {student_info['study_language'].value}"
)
return
self
.
pgvector
.
search_with_curriculum_context
(
return
self
.
pgvector
.
search_with_curriculum_context
(
query_embedding
=
query_embedding
,
query_embedding
=
query_embedding
,
grade
=
student_info
[
'grade'
],
grade
=
student_info
[
'grade'
],
...
@@ -544,14 +87,12 @@ Be friendly and simple in your response.
...
@@ -544,14 +87,12 @@ Be friendly and simple in your response.
is_arabic
=
student_info
[
'is_arabic'
],
is_arabic
=
student_info
[
'is_arabic'
],
limit
=
top_k
limit
=
top_k
)
)
except
HTTPException
:
raise
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Error in
enhanced
search_similar: {e}"
)
logger
.
error
(
f
"Error in search_similar: {e}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"
Enhanced s
earch failed: {str(e)}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"
S
earch failed: {str(e)}"
)
def
get_available_subjects
(
self
,
student_id
:
str
)
->
List
[
str
]:
def
get_available_subjects
(
self
,
student_id
:
str
)
->
List
[
str
]:
"""Get available subjects for the student
based on their grade and language from JSON data
"""
"""Get available subjects for the student"""
if
not
self
.
pgvector
:
if
not
self
.
pgvector
:
return
[]
return
[]
...
@@ -561,15 +102,14 @@ Be friendly and simple in your response.
...
@@ -561,15 +102,14 @@ Be friendly and simple in your response.
return
[]
return
[]
return
self
.
pgvector
.
get_subjects_by_grade_and_language
(
return
self
.
pgvector
.
get_subjects_by_grade_and_language
(
student_info
[
'grade'
],
student_info
[
'grade'
],
student_info
[
'is_arabic'
]
student_info
[
'is_arabic'
]
)
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Error getting available subjects
for {student_id}
: {e}"
)
logger
.
error
(
f
"Error getting available subjects: {e}"
)
return
[]
return
[]
def
get_curriculum_overview
(
self
,
student_id
:
str
,
subject
:
str
=
"Science"
)
->
Dict
:
def
get_curriculum_overview
(
self
,
student_id
:
str
,
subject
:
str
=
"Science"
)
->
Dict
:
"""Get curriculum overview for a specific student
based on JSON data
"""
"""Get curriculum overview for a specific student"""
if
not
self
.
pgvector
:
if
not
self
.
pgvector
:
return
{
"error"
:
"PGVector service not available"
}
return
{
"error"
:
"PGVector service not available"
}
...
@@ -579,13 +119,11 @@ Be friendly and simple in your response.
...
@@ -579,13 +119,11 @@ Be friendly and simple in your response.
return
{
"error"
:
"Student not found"
}
return
{
"error"
:
"Student not found"
}
curriculum
=
self
.
pgvector
.
get_curriculum_structure
(
curriculum
=
self
.
pgvector
.
get_curriculum_structure
(
student_info
[
'grade'
],
student_info
[
'grade'
],
student_info
[
'is_arabic'
],
subject
student_info
[
'is_arabic'
],
subject
)
)
if
not
curriculum
:
if
not
curriculum
:
return
{
"error"
:
f
"No curriculum found for Grade {student_info['grade']}
in {student_info['study_language'].value}
"
}
return
{
"error"
:
f
"No curriculum found for Grade {student_info['grade']}"
}
return
{
return
{
"student_info"
:
{
"student_info"
:
{
...
@@ -605,38 +143,28 @@ Be friendly and simple in your response.
...
@@ -605,38 +143,28 @@ Be friendly and simple in your response.
logger
.
error
(
f
"Error getting curriculum overview: {e}"
)
logger
.
error
(
f
"Error getting curriculum overview: {e}"
)
return
{
"error"
:
str
(
e
)}
return
{
"error"
:
str
(
e
)}
# Conversation management methods
def
export_conversation
(
self
,
student_id
:
str
)
->
List
[
Dict
[
str
,
str
]]:
def
export_conversation
(
self
,
student_id
:
str
)
->
List
[
Dict
[
str
,
str
]]:
"""Export conversation history for a student"""
"""Export conversation history for a student"""
try
:
return
self
.
response_generator
.
get_conversation_history
(
student_id
)
return
self
.
get_conversation_history
(
student_id
)
except
Exception
as
e
:
logger
.
error
(
f
"Error exporting conversation for {student_id}: {e}"
)
return
[]
def
clear_conversation
(
self
,
student_id
:
str
)
->
Dict
[
str
,
str
]:
def
clear_conversation
(
self
,
student_id
:
str
)
->
Dict
[
str
,
str
]:
"""Clear conversation history for a student"""
"""Clear conversation history for a student"""
try
:
try
:
self
.
db_service
.
clear_history
(
student_id
)
self
.
db_service
.
clear_history
(
student_id
)
return
{
return
{
"status"
:
"success"
,
"message"
:
f
"Conversation cleared for student {student_id}"
}
"status"
:
"success"
,
"message"
:
f
"Conversation cleared for student {student_id}"
}
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Error clearing conversation: {e}"
)
logger
.
error
(
f
"Error clearing conversation: {e}"
)
return
{
return
{
"status"
:
"error"
,
"message"
:
f
"Failed to clear conversation: {str(e)}"
}
"status"
:
"error"
,
"message"
:
f
"Failed to clear conversation: {str(e)}"
}
def
get_agent_stats
(
self
,
student_id
:
str
)
->
Dict
:
def
get_agent_stats
(
self
,
student_id
:
str
)
->
Dict
:
"""Get conversation statistics for a student
with enhanced JSON-based curriculum info
"""
"""Get conversation statistics for a student"""
try
:
try
:
history
=
self
.
get_conversation_history
(
student_id
)
history
=
self
.
response_generator
.
get_conversation_history
(
student_id
)
user_messages
=
[
msg
for
msg
in
history
if
msg
[
'role'
]
==
'user'
]
user_messages
=
[
msg
for
msg
in
history
if
msg
[
'role'
]
==
'user'
]
assistant_messages
=
[
msg
for
msg
in
history
if
msg
[
'role'
]
==
'assistant'
]
assistant_messages
=
[
msg
for
msg
in
history
if
msg
[
'role'
]
==
'assistant'
]
system_messages
=
[
msg
for
msg
in
history
if
msg
[
'role'
]
==
'system'
]
system_messages
=
[
msg
for
msg
in
history
if
msg
[
'role'
]
==
'system'
]
# Get student language info
student_info
=
self
.
db_service
.
get_student_info
(
student_id
)
student_info
=
self
.
db_service
.
get_student_info
(
student_id
)
language_info
=
{}
language_info
=
{}
curriculum_info
=
{}
curriculum_info
=
{}
...
@@ -648,7 +176,6 @@ Be friendly and simple in your response.
...
@@ -648,7 +176,6 @@ Be friendly and simple in your response.
"grade"
:
student_info
[
'grade'
]
"grade"
:
student_info
[
'grade'
]
}
}
# Add JSON-based curriculum availability info
if
self
.
pgvector
:
if
self
.
pgvector
:
curriculum
=
self
.
pgvector
.
get_curriculum_structure
(
curriculum
=
self
.
pgvector
.
get_curriculum_structure
(
student_info
[
'grade'
],
student_info
[
'is_arabic'
]
student_info
[
'grade'
],
student_info
[
'is_arabic'
]
...
@@ -673,14 +200,11 @@ Be friendly and simple in your response.
...
@@ -673,14 +200,11 @@ Be friendly and simple in your response.
**
curriculum_info
**
curriculum_info
}
}
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Error getting enhanced agent stats: {e}"
)
logger
.
error
(
f
"Error getting agent stats: {e}"
)
return
{
return
{
"student_id"
:
student_id
,
"error"
:
str
(
e
)}
"student_id"
:
student_id
,
"error"
:
str
(
e
)
}
def
get_curriculum_structure_info
(
self
,
student_id
:
str
,
subject
:
str
=
"Science"
)
->
Dict
:
def
get_curriculum_structure_info
(
self
,
student_id
:
str
,
subject
:
str
=
"Science"
)
->
Dict
:
"""Get detailed curriculum structure information
from JSON data
"""
"""Get detailed curriculum structure information"""
if
not
self
.
pgvector
:
if
not
self
.
pgvector
:
return
{
"error"
:
"PGVector service not available"
}
return
{
"error"
:
"PGVector service not available"
}
...
@@ -694,12 +218,7 @@ Be friendly and simple in your response.
...
@@ -694,12 +218,7 @@ Be friendly and simple in your response.
)
)
if
not
curriculum
:
if
not
curriculum
:
return
{
return
{
"error"
:
"No curriculum structure found"
}
"error"
:
f
"No curriculum structure found"
,
"grade"
:
student_info
[
'grade'
],
"language"
:
"Arabic"
if
student_info
[
'is_arabic'
]
else
"English"
,
"subject"
:
subject
}
# Extract detailed structure info
# Extract detailed structure info
units_info
=
[]
units_info
=
[]
...
...
self_hosted_env/voice_agent/voice_agent.tar
View file @
92dffa87
No preview for this file type
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment