# agents/teacher.py import os import json import time from dotenv import load_dotenv import openai from .base_agent import BaseAgent load_dotenv() class TeacherAgent(BaseAgent): def __init__(self, level): super().__init__(f"{level.capitalize()}TeacherAgent") api_key = os.getenv("OPENAI_API_KEY") self.client = openai.OpenAI(api_key=api_key) self.level = level.lower() def teach_module(self, module): """ Returns a rich, k-shot based explanation string for the given module. Will attempt a small re-prompt if the output is too short. """ system_prompt = ( "You are an engaging and adaptive AI Tutor. Produce a thorough, structured explanation " "of the requested module tuned to the user's learning level. Use the structure below." "\n\nTeaching style differences by level:\n" "- Novice: Simple language, relatable analogies, many concrete examples.\n" "- Intermediate: Explain 'how' and 'why', connect to related concepts, include practical examples.\n" "- Advanced: Discuss nuances, edge-cases, performance, comparative methods and deeper insights.\n\n" "Explanation structure (MANDATORY):\n" "1) Core Concept — clear, precise description (several sentences)\n" "2) Worked Example or Analogy — at least one detailed example that illustrates application\n" "4) Key Takeaway — one concise sentence\n\n" "### Few-shot examples (follow style):\n\n" "Novice Example:\n" "Module: Variables\n" "Core Concept: Variables are containers that store values like numbers or text. Example: x = 5.\n" "Analogy/Example: Think of a labeled jar.\n" "Intermediate Example:\n" "Module: For loops\n" "Core Concept: For loops iterate over a collection. Example: for item in collection: process(item)\n" "Application: Useful for batch-processing and iteration in algorithms.\n\n" "Advanced Example:\n" "Module: Tail recursion\n" "Core Concept: Tail recursion preserves state for compiler optimizations; consider stack usage.\n\n" "Produce detailed output (minimum ~130 words). Do NOT output JSON — just plain text explanation." ) user_prompt = ( f"Module: {module.name}\n" f"Learning objective: {getattr(module, 'learning_objective', '')}\n\n" "Please produce the explanation now." ) resp = self.client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], # you can tune temperature if needed ) explanation = resp.choices[0].message.content.strip() # If too short, ask for expansion once if len(explanation.split()) < 120: followup = ( "The previous explanation was too short. Please expand the explanation, add another worked example " "and a short code or pseudo-code snippet where appropriate. Keep same style & level." ) resp2 = self.client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, {"role": "user", "content": followup} ], ) extra = resp2.choices[0].message.content.strip() # concatenate but keep readable explanation = explanation + "\n\n" + extra # return explanation string (do not print in agent; app will show it) return explanation def evaluate_example(self, module, student_example): """ Evaluate the student's example for the given module. Returns a dict: { "is_correct": bool, "feedback": str, "confidence": float (0-1) } The model MUST output JSON only; we robustly parse it and fallback if needed. """ system_prompt = ( "You are an expert educational assessor. Evaluate the student's example strictly with respect " "to the module's learning objective. Respond with JSON ONLY (no extra text). The JSON object MUST contain:\n" " - is_correct: true/false\n" " - feedback: short one-sentence constructive feedback\n" " - confidence: numeric between 0 and 1\n\n" f"Evaluation sensitivity is based on learner level: {self.level}." ) user_prompt = ( f"Module: {module.name}\n" f"Learning objective: {getattr(module, 'learning_objective', '')}\n\n" f"Student example: {student_example}\n\n" "Evaluate and return JSON only." ) resp = self.client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], # lower temperature can reduce hallucinations ) raw = resp.choices[0].message.content.strip() # Try to parse JSON; be forgiving (extract first {...}) try: parsed = json.loads(raw) except Exception: import re m = re.search(r'(\{.*\})', raw, re.DOTALL) if m: try: parsed = json.loads(m.group(1)) except Exception: parsed = None else: parsed = None if not parsed: # fallback: simple heuristics (very conservative) is_correct = len(student_example.strip()) > 20 feedback = "Could not parse evaluator output; using conservative heuristic. Provide a more concrete example." \ if not is_correct else "Example seems plausible but automatic evaluation failed to parse." confidence = 0.45 if not is_correct else 0.6 return {"is_correct": bool(is_correct), "feedback": feedback, "confidence": confidence, "raw": raw} # normalize fields is_correct = bool(parsed.get("is_correct", parsed.get("correct", False))) feedback = str(parsed.get("feedback", parsed.get("explanation", ""))) confidence = float(parsed.get("confidence", parsed.get("score", 0))) if parsed.get("confidence") is not None else 0.9 return {"is_correct": is_correct, "feedback": feedback, "confidence": confidence, "raw": raw} def check_example(self, module): """ Interactive method to check if student understands the module by asking for an example. Returns True if the student provides a satisfactory example, False otherwise. """ print(f"\nšŸŽÆ Let's test your understanding of '{module.name}'!") print("Please provide a specific example or application that demonstrates this concept.") learning_objective = getattr(module, 'learning_objective', '') if learning_objective: print(f"Learning Objective: {learning_objective}") print("\nYour example should be:") print("• Specific and concrete (not just a definition)") print("• Relevant to the module content") print("• Show your understanding of how to apply the concept") print("• At least 2-3 sentences with clear reasoning") while True: # Allow multiple attempts student_example = input("\nYour example: ").strip() if not student_example: print("āŒ Please provide an example to demonstrate your understanding.") continue # Check for minimum length and specificity if len(student_example.split()) < 10: print("āŒ Your example is too brief. Please provide a more detailed example with specific details.") continue # Evaluate the example evaluation = self.evaluate_example(module, student_example) if evaluation['is_correct']: print("āœ… Correct! Your example demonstrates good understanding.") return True else: print("āŒ Incorrect. Your example doesn't demonstrate sufficient understanding.") # Provide a simple hint hint = self.get_simple_hint(module) if hint: print(f"šŸ’” Hint: {hint}") retry = input("\nWould you like to try again with a different example? (y/n): ").lower().strip() if retry == 'y': continue else: print("šŸ“š Let's review the concept again and then try the example.") return False def get_simple_hint(self, module): """Generate a simple hint to guide the student toward a better example.""" system_prompt = ( "You are an expert tutor providing a simple hint. Give a brief, helpful suggestion (1-2 sentences) " "to guide the student toward providing a better example. Don't give away the answer, just nudge them in the right direction." ) user_prompt = ( f"Module: {module.name}\n" f"Learning Objective: {getattr(module, 'learning_objective', '')}\n\n" "Provide a simple hint to help the student think of a better example." ) try: response = self.client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ] ) return response.choices[0].message.content.strip() except Exception: return "Think about how this concept applies in real-world situations or practical scenarios." # convenience subclasses (optional) class NoviceTeacherAgent(TeacherAgent): def __init__(self): super().__init__("novice") class IntermediateTeacherAgent(TeacherAgent): def __init__(self): super().__init__("intermediate") class AdvancedTeacherAgent(TeacherAgent): def __init__(self): super().__init__("advanced")