language-learn / backend /tutor_engine.py
swapmyface's picture
Initial deploy: Language Tutor with Flask API
07bc9aa verified
"""Tutor engine: builds system prompts with STRICT language isolation."""
from backend.language_config import LANGUAGES, LEVELS, CURRICULUM, get_vocabulary
from backend.teacher_profiles import TEACHERS
def build_system_prompt(target_lang, instruction_lang, level, topic_id, teacher_id):
"""Build the system prompt with strict language isolation rules."""
target = LANGUAGES.get(target_lang, {})
instruction = LANGUAGES.get(instruction_lang, {})
level_cfg = LEVELS.get(level, {})
teacher = TEACHERS.get(teacher_id, {})
target_name = target.get("name", target_lang)
instruction_name = instruction.get("name", instruction_lang)
target_script = target.get("script", "")
target_native = target.get("native_name", "")
topics = CURRICULUM.get(level, [])
topic_info = next((t for t in topics if t["id"] == topic_id), {})
topic_title = topic_info.get("title", topic_id)
vocab = get_vocabulary(target_lang, topic_id)
vocab_block = ""
if vocab:
vocab_lines = [f" - {v['word']} ({v['transliteration']}) = {v['meaning']}"
for v in vocab[:10]]
vocab_block = "Key vocabulary for this lesson:\n" + "\n".join(vocab_lines)
is_immersion = target_lang == instruction_lang
# --- STRICT LANGUAGE ISOLATION (MOST CRITICAL RULES) ---
if is_immersion:
lang_rules = f"""
=== ABSOLUTE LANGUAGE RULES (HIGHEST PRIORITY — NEVER VIOLATE) ===
You are in IMMERSION MODE. The student is learning {target_name} using {target_name} itself.
1. EVERY SINGLE WORD you write MUST be in {target_name} ({target_script} script).
2. DO NOT use ANY other language. Not a single word of English or any other language.
3. Explanations, grammar notes, encouragement — ALL in {target_name}.
4. If the student writes in another language, respond ONLY in {target_name} and gently guide them back.
=== END LANGUAGE RULES ===
"""
else:
lang_rules = f"""
=== ABSOLUTE LANGUAGE RULES (HIGHEST PRIORITY — NEVER VIOLATE) ===
The student is learning {target_name} and already knows {instruction_name}.
1. ALL your explanations, instructions, grammar notes, encouragement, and conversation MUST be in {instruction_name}.
2. Teach {target_name} words/phrases using {target_script} script.
3. NEVER use English unless the instruction language IS English.
4. NEVER use any language other than {target_name} (for teaching) and {instruction_name} (for explaining).
5. When presenting vocabulary, use this format:
[{target_name} word in {target_script}] ([transliteration]) — [{instruction_name} meaning]
6. Even small words like "means", "is called", "for example" MUST be in {instruction_name}.
7. If the student writes in {instruction_name}, respond in {instruction_name} while teaching {target_name} content.
8. If the student attempts {target_name}, praise them in {instruction_name} and provide corrections in {instruction_name}.
=== END LANGUAGE RULES ===
"""
# Teacher personality
teacher_traits = teacher.get("system_prompt_traits", "You are a helpful language teacher.")
greeting_style = teacher.get("greeting_style", "friendly")
# Honorifics
honorifics_note = ""
if target.get("has_honorifics"):
honorifics_note = f"\n{target_name} has formal/informal address. At {level} level, teach the polite/formal forms primarily."
prompt = f"""{lang_rules}
{teacher_traits}
You are teaching {target_name} ({target_native}) to a {level_cfg.get('name', level)} student.
Topic: {topic_title}
{honorifics_note}
Teaching guidelines for {level} level:
- Vocabulary limit: ~{level_cfg.get('vocab_limit', 500)} words
- Grammar: {level_cfg.get('grammar_complexity', 'simple')}
- Corrections: {level_cfg.get('correction_style', 'gentle')}
- Response length: {level_cfg.get('response_length', '3-6 sentences')}
{vocab_block}
Format rules:
- When introducing a new {target_name} word, always write it in {target_script} script first, then transliteration in parentheses.
- Use short, clear sentences.
- After teaching 2-3 new words, give a quick practice exercise.
- When sharing cultural context, wrap it in [CULTURAL NOTE: your note here] format.
- For mini-dialogues, use [DIALOGUE] and [/DIALOGUE] markers.
- For sentence builder exercises, use [SENTENCE_BUILDER: word1 | word2 | word3 | ...] format with the correct order.
Remember: The language rules above are ABSOLUTE. Never break them under any circumstances.
"""
return prompt.strip()
def build_greeting_prompt(target_lang, instruction_lang, level, topic_id, teacher_id):
"""Build the first greeting message prompt."""
target = LANGUAGES.get(target_lang, {})
instruction = LANGUAGES.get(instruction_lang, {})
teacher = TEACHERS.get(teacher_id, {})
target_name = target.get("name", target_lang)
instruction_name = instruction.get("name", instruction_lang)
greeting_style = teacher.get("greeting_style", "friendly")
topics = CURRICULUM.get(level, [])
topic_info = next((t for t in topics if t["id"] == topic_id), {})
topic_title = topic_info.get("title", topic_id)
is_immersion = target_lang == instruction_lang
if is_immersion:
return (
f"Greet the student in {target_name} only. Be {greeting_style}. "
f"Introduce yourself as {teacher.get('name', 'Teacher')} and say you'll be teaching "
f"'{topic_title}' today. Start with a warm greeting in {target_name} and "
f"introduce 1-2 basic words from the topic. Keep it short and encouraging. "
f"Remember: EVERYTHING must be in {target_name}."
)
else:
return (
f"Greet the student in {instruction_name}. Be {greeting_style}. "
f"Introduce yourself as {teacher.get('name', 'Teacher')} and say you'll be teaching "
f"{target_name} today, specifically '{topic_title}'. "
f"Say a common greeting in {target_name} with its {instruction_name} meaning. "
f"Keep it short and welcoming. Remember: explain everything in {instruction_name}, "
f"teach words in {target_name}."
)
def evaluate_pronunciation(user_text, expected_text):
"""Simple pronunciation scoring by comparing transcribed text to expected."""
if not user_text or not expected_text:
return {"score": 0, "feedback": "No text to compare"}
user_words = user_text.lower().strip().split()
expected_words = expected_text.lower().strip().split()
if not expected_words:
return {"score": 0, "feedback": "No expected text"}
matches = 0
for uw in user_words:
if uw in expected_words:
matches += 1
score = min(100, int((matches / len(expected_words)) * 100))
if score >= 90:
feedback = "Excellent pronunciation!"
elif score >= 70:
feedback = "Good attempt! A few sounds need practice."
elif score >= 50:
feedback = "Decent try! Keep practicing the tricky sounds."
else:
feedback = "Let's practice this phrase more. Listen carefully and try again."
return {"score": score, "feedback": feedback, "expected": expected_text, "heard": user_text}