"""AI Coaching service — conversational assistant, tips, study suggestions.""" import json import logging _logger = logging.getLogger(__name__) class CoachService: """High-level AI coaching: chat, tips, explanations, writing help, study plans.""" def __init__(self, env): from .openai_service import OpenAIService self.env = env self.ai = OpenAIService(env) def _log(self, action, latency_ms=0, status="success", error=None, inp=None, out=None): try: self.env["encoach.ai.log"].sudo().create({ "service": "coach", "action": action, "latency_ms": latency_ms, "status": status, "error_message": error, "input_preview": (inp or "")[:500], "output_preview": (out or "")[:500], }) except Exception: _logger.warning("Failed to log coach call", exc_info=True) def chat(self, message, *, history=None, student_context=None): """Multi-turn coaching conversation with RAG context.""" import time t0 = time.time() messages = [ {"role": "system", "content": ( "You are EnCoach AI — a friendly, expert IELTS and English learning coach. " "You help students with study strategies, explain concepts, motivate them, " "and answer questions about their learning journey. " "Be encouraging but honest. Keep responses concise (under 150 words). " "If asked about scores or progress, reference the student context provided." )}, ] if student_context: messages.append({"role": "system", "content": f"Student context: {json.dumps(student_context)}"}) for h in (history or []): messages.append({"role": h.get("role", "user"), "content": h["content"]}) messages.append({"role": "user", "content": message}) reply = self.ai.chat_with_context( messages, message, content_types=["course", "resource", "module", "feedback"], model=self.ai.fast_model, action="coach_chat", max_tokens=512, ) self._log("coach_chat", int((time.time() - t0) * 1000), inp=message[:500], out=reply[:500]) return {"reply": reply} def get_tip(self, context="general"): """Get a contextual learning tip, enriched with knowledge base content.""" import time t0 = time.time() vector_context = self.ai._get_vector_context(context, content_types=["resource", "feedback"], limit=3) kb_text = self.ai._format_context(vector_context) if vector_context else "" system_prompt = ( "Generate a single, practical English learning or IELTS preparation tip. " "Make it specific and actionable. Return JSON: {\"tip\": string, \"category\": string}" ) if kb_text: system_prompt += f"\n\nRelevant knowledge base content:\n{kb_text}" messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Context: {context}"}, ] result = self.ai.chat_json(messages, model=self.ai.fast_model, action="coach_tip", max_tokens=256) self._log("coach_tip", int((time.time() - t0) * 1000), inp=context, out=json.dumps(result)[:500]) return result def explain(self, score_data, student_context=""): """Explain a grade or assessment result.""" import time t0 = time.time() explanation = self.ai.explain_grade(score_data, student_context) self._log("coach_explain", int((time.time() - t0) * 1000), out=explanation[:500]) return {"explanation": explanation} def suggest(self, student_profile): """Suggest next study actions.""" import time t0 = time.time() result = self.ai.suggest_study_plan(student_profile) self._log("coach_suggest", int((time.time() - t0) * 1000), out=json.dumps(result)[:500]) return result def writing_help(self, task, draft, help_type="improve"): """Help with writing tasks.""" import time t0 = time.time() result = self.ai.writing_help(task, draft, help_type) self._log("coach_writing", int((time.time() - t0) * 1000), inp=draft[:200], out=json.dumps(result)[:500]) return result def get_hint(self, question_context): """Give a hint for a question without revealing the answer.""" import time t0 = time.time() messages = [ {"role": "system", "content": ( "Give a helpful hint for this question WITHOUT revealing the answer. " "Guide the student's thinking. Return JSON: {\"hint\": string, \"strategy\": string}" )}, {"role": "user", "content": json.dumps(question_context)}, ] result = self.ai.chat_json(messages, model=self.ai.fast_model, action="coach_hint", max_tokens=256) self._log("coach_hint", int((time.time() - t0) * 1000), out=json.dumps(result)[:500]) return result