from altair import Bin from numpy import tri import streamlit as st import pickle import json import os import random import time import re import hashlib from typing import List, Dict, Any from streamlit_chat import message from dotenv import load_dotenv from datetime import datetime from supabase_utils import init_supabase_client, update_user_profile, get_user_profile from supabase import Client from sympy import Rem from PIL import Image import io from faster_whisper import WhisperModel import tempfile load_dotenv() # Haystack imports from haystack import Pipeline, Document from haystack.document_stores.in_memory import InMemoryDocumentStore from haystack.components.retrievers.in_memory import InMemoryBM25Retriever, InMemoryEmbeddingRetriever from haystack.components.builders import PromptBuilder from haystack.components.embedders import SentenceTransformersDocumentEmbedder, SentenceTransformersTextEmbedder # Google AI integration - Custom Component import google.generativeai as genai from haystack import component, default_from_dict, default_to_dict @component class CustomGoogleAIGenerator: """ Một component Haystack tùy chỉnh để gọi trực tiếp API Gemini của Google. """ def __init__(self, api_key: str, model_name: str = "gemini-1.5-pro"): self.api_key = api_key self.model_name = model_name genai.configure(api_key=self.api_key) self.safety_settings = [ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"}, {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"}, {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"}, {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"}, ] self.generation_config = genai.types.GenerationConfig( temperature=0.2, max_output_tokens=1024 ) self.model = genai.GenerativeModel( self.model_name, generation_config=self.generation_config, safety_settings=self.safety_settings ) def to_dict(self): return default_to_dict(self, api_key=self.api_key, model_name=self.model_name) @classmethod def from_dict(cls, data): return default_from_dict(cls, data) @component.output_types(replies=List[str]) def run(self, prompt_parts: List[Any]): """ Gửi một prompt đa phương thức (văn bản và hình ảnh) đến API Gemini. """ try: processed_parts = [] for part in prompt_parts: if isinstance(part, bytes): try: img = Image.open(io.BytesIO(part)) processed_parts.append(img) except Exception as e: print(f"Lỗi khi xử lý ảnh: {e}") else: processed_parts.append(part) response = self.model.generate_content(processed_parts) return {"replies": [response.text]} except Exception as e: return {"replies": [f"Xin lỗi, đã có lỗi xảy ra khi kết nối với mô hình AI."]} st.set_page_config( page_title="AI Math Tutor", page_icon="🤖", layout="wide", initial_sidebar_state="expanded", menu_items={ 'About': "Gia sư Toán AI thông minh cho học sinh lớp 9" } ) st.markdown(""" """, unsafe_allow_html=True) # Kiểm tra API key if "GOOGLE_API_KEY" not in os.environ: st.error("⚠️ Không tìm thấy API key. Vui lòng cấu hình biến môi trường.") st.stop() @st.cache_resource def load_resources(): """Load và khởi tạo tất cả tài nguyên của hệ thống""" # Load documents try: with open("embedded_documents.pkl", "rb") as f: documents = pickle.load(f) except FileNotFoundError: st.error("❌ Không tìm thấy dữ liệu học liệu") st.stop() # Load videos try: with open("videos.json", "r", encoding="utf-8") as f: videos_data = json.load(f) except FileNotFoundError: st.error("❌ Không tìm thấy dữ liệu video") st.stop() # Initialize document store document_store = InMemoryDocumentStore() document_store.write_documents(documents) # Initialize components retriever = InMemoryEmbeddingRetriever(document_store=document_store) text_embedder = SentenceTransformersTextEmbedder( model="bkai-foundation-models/vietnamese-bi-encoder" ) print("DEBUG: Loading Faster Whisper model...") model_size = "small" # Chạy trên CPU với INT8 để tối ưu whisper_model = WhisperModel(model_size, device="cpu", compute_type="int8") print(f"DEBUG: Faster Whisper model '{model_size}' loaded successfully.") # Templates informer_template = """ Bạn là một Gia sư Toán AI chuyên nghiệp. Vai trò của bạn là cung cấp một lời giải hoặc một lời giải thích chi tiết, chính xác và dễ hiểu cho học sinh lớp 9. **QUY TRÌNH CỦA BẠN:** 1. **Đọc Lịch sử Trò chuyện:** Hiểu rõ bối cảnh và câu hỏi trước đó của học sinh. 2. **Nghiên cứu Tài liệu:** Tham khảo kỹ các thông tin từ sách giáo khoa được cung cấp. 3. **Trả lời câu hỏi cuối cùng:** Dựa vào cả lịch sử và tài liệu, hãy trả lời câu hỏi cuối cùng của học sinh. **YÊU CẦU TRÌNH BÀY:** - Sử dụng ngôn ngữ sư phạm, rõ ràng, từng bước một. - Sử dụng Markdown để định dạng các công thức toán học, các đề mục và nhấn mạnh các điểm quan trọng. - Luôn trả lời bằng tiếng Việt. --- **LỊCH SỬ TRÒ CHUYỆN GẦN ĐÂY:** {{ conversation_history }} --- **THÔNG TIN SÁCH GIÁO KHOA (TỪ RAG):** {% for doc in documents %} {{ doc.content }} {% endfor %} --- **Câu hỏi cuối cùng của học sinh:** {{ query }} **Lời giải chi tiết của bạn:** """ practice_template = """ Bạn là một chuyên gia ra đề thi và tư vấn học liệu môn Toán. **NHIỆM VỤ:** Dựa trên **chủ đề yếu** của học sinh và **danh sách video** được cung cấp, hãy thực hiện 2 việc: 1. **Tạo 2 Bài tập Mới:** - Các bài tập phải liên quan trực tiếp đến chủ đề yếu. - Độ khó tương đương chương trình lớp 9. - Bài tập phải hoàn toàn mới, không được trùng lặp với các ví dụ phổ biến. 2. **Đề xuất 1 Video Phù hợp nhất:** - Chọn ra MỘT video từ danh sách có nội dung liên quan chặt chẽ nhất đến chủ đề yếu. **THÔNG TIN ĐẦU VÀO:** - **Chủ đề yếu của học sinh:** '{{ student_weakness }}' - **Danh sách video có sẵn (JSON):** {{ video_cheatsheet_json }} **YÊU CẦU OUTPUT:** Trả lời theo định dạng sau (không thêm lời dẫn): 🎯 **BÀI TẬP CỦNG CỐ** **Bài 1:** [Nội dung câu hỏi bài tập 1] **Bài 2:** [Nội dung câu hỏi bài tập 2] 📹 **VIDEO ĐỀ XUẤT** **[Tên video]** 🎬 Link: https://www.youtube.com/playlist?list=PL5q2T2FxzK7XY4s9FqDi6KCFEpGr2LX2D """ insight_template = """ Bạn là một chuyên gia phân tích giáo dục. Nhiệm vụ của bạn là đọc kỹ đoạn hội thoại và xác định chính xác những khái niệm toán học mà học sinh đang hiểu sai. **HƯỚNG DẪN:** - Đọc kỹ toàn bộ hội thoại. - Tập trung vào những câu hỏi hoặc nhận định của 'User' thể hiện sự nhầm lẫn hoặc thiếu kiến thức. - Dựa trên sự nhầm lẫn đó, xác định khái niệm toán học cốt lõi bị hiểu sai. - Chỉ trả lời bằng một đối tượng JSON duy nhất theo định dạng sau. Không thêm bất kỳ giải thích hay văn bản nào khác. **VÍ DỤ:** --- Hội thoại: User: hệ thức Vi-ét dùng để làm gì? Assistant: ... User: vậy nếu phương trình vô nghiệm thì vẫn tính tổng và tích các nghiệm được đúng không? --- JSON Output: {"misunderstood_concepts": ["điều kiện áp dụng hệ thức Vi-ét"], "sentiment": "confused"} --- **BÂY GIỜ, HÃY PHÂN TÍCH HỘI THOẠI SAU:** **Hội thoại:** {{ conversation_history }} **JSON Output:** """ verifier_template = """Bin là một người kiểm định chất lượng toán học cực kỳ khó tính và chính xác. Nhiệm vụ của bạn là kiểm tra xem lời giải được đề xuất có hoàn toàn đúng về mặt toán học và logic hay không. **Câu hỏi của học sinh:** {{ query }} **Lời giải được đề xuất:** {{ informer_answer }} **YÊU CẦU:** Hãy kiểm tra từng bước, từng công thức và kết quả cuối cùng. Sau đó, chỉ trả lời bằng một đối tượng JSON duy nhất theo định dạng sau. **JSON Output:** {"is_correct": [true hoặc false], "correction_suggestion": "[Nếu sai, hãy giải thích ngắn gọn và chính xác lỗi sai nằm ở đâu. Nếu đúng, để trống chuỗi này.]"} """ intent_template = """ Bạn là một hệ thống phân loại ý định cực kỳ chính xác. Dựa vào câu hỏi cuối cùng của người dùng, hãy phân loại nó vào MỘT trong các loại sau. **ĐỊNH NGHĨA CÁC LOẠI:** - 'greeting_social': Chào hỏi, xã giao, cảm ơn, tạm biệt. - 'math_question': Bất kỳ câu hỏi nào liên quan trực tiếp đến kiến thức toán học, bao gồm giải bài tập, tính toán, hỏi định nghĩa, hỏi công thức, hỏi tính chất. - 'request_for_practice': Yêu cầu bài tập luyện tập, muốn thực hành. - 'expression_of_stress': Biểu hiện căng thẳng, mệt mỏi, nản lòng. - 'study_support': Hỏi về phương pháp học chung, cách để tiến bộ, tìm kiếm động lực. - 'off_topic': Chủ đề hoàn toàn không liên quan đến học tập. **VÍ DỤ:** --- User: Chào bạn Phân loại: greeting_social --- User: Giải giúp mình phương trình x^2 + 5x - 6 = 0 Phân loại: math_question --- User: hệ thức Vi-ét dùng để làm gì? <-- VÍ DỤ MỚI QUAN TRỌNG Phân loại: math_question --- User: Bài này khó quá, mình nản thật Phân loại: expression_of_stress --- User: Có bài nào tương tự để mình luyện tập thêm không? Phân loại: request_for_practice --- User: Làm sao để học tốt môn hình học không gian? Phân loại: study_support --- User: Giá vàng hôm nay bao nhiêu? Phân loại: off_topic --- **Bây giờ, hãy phân loại lịch sử chat sau. Chỉ trả về MỘT từ duy nhất.** **Lịch sử chat:** {{ conversation_history }} **Phân loại:** """ # --- TEMPLATES CHO TUTOR AGENT --- # Prompt tổng quát định hình vai trò và tính cách tutor_master_prompt = """ Bạn là một Gia sư Toán AI, một người bạn đồng hành học tập thông minh, thấu cảm và chuyên nghiệp. Vai trò của bạn là phản hồi lại học sinh một cách phù hợp nhất dựa trên ý định của họ. Luôn sử dụng ngôn ngữ tích cực, khuyến khích và thân thiện. Luôn trả lời bằng tiếng Việt. """ # Prompt cho intent 'greeting_social' greeting_template = """ {{ master_prompt }} **Bối cảnh:** Học sinh đang bắt đầu cuộc trò chuyện hoặc nói những câu xã giao (chào hỏi, cảm ơn). **Nhiệm vụ:** Hãy phản hồi lại một cách thân thiện, tự nhiên và mời gọi họ bắt đầu buổi học. **Lịch sử chat gần đây:** {{ conversation_history }} **Lời chào thân thiện của bạn:** """ # Prompt cho intent 'expression_of_stress' stress_template = """ {{ master_prompt }} **Bối cảnh:** Học sinh đang thể hiện sự căng thẳng, mệt mỏi hoặc nản lòng về việc học. **NHIỆM VỤ CỰC KỲ QUAN TRỌNG:** 1. **Đồng cảm:** Thể hiện rằng bạn hiểu cảm giác của họ. 2. **Bình thường hóa:** Cho họ biết rằng cảm giác này là bình thường. 3. **Gợi ý giải pháp AN TOÀN:** Đề xuất những hành động đơn giản như nghỉ ngơi, hít thở sâu. 4. **TUYỆT ĐỐI KHÔNG:** Đóng vai chuyên gia tâm lý, không đưa ra lời khuyên phức tạp. **Lịch sử chat gần đây:** {{ conversation_history }} **Lời động viên an toàn và thấu cảm của bạn:** """ # Prompt cho intent 'study_support' support_template = """ {{ master_prompt }} **Bối cảnh:** Học sinh đang hỏi về phương pháp học tập, cách để tiến bộ hoặc tìm kiếm động lực. **Nhiệm vụ:** Hãy đưa ra những lời khuyên chung, hữu ích và mang tính động viên về việc học Toán. Bạn có thể gợi ý về các chức năng của mình (giải bài tập, tạo luyện tập,...). **Lịch sử chat gần đây:** {{ conversation_history }} **Lời khuyên và hỗ trợ của bạn:** """ # Prompt cho intent 'off_topic' off_topic_template = """ {{ master_prompt }} **Bối cảnh:** Học sinh đang hỏi một câu hoàn toàn không liên quan đến toán học hoặc học tập. **Nhiệm vụ:** Hãy lịch sự từ chối trả lời và nhẹ nhàng hướng cuộc trò chuyện quay trở lại chủ đề chính là học Toán. **Lịch sử chat gần đây:** {{ conversation_history }} **Lời từ chối khéo léo của bạn:** """ # Create prompt builders informer_prompt_builder = PromptBuilder(template=informer_template, required_variables=["documents", "query", "conversation_history"]) practice_prompt_builder = PromptBuilder(template=practice_template, required_variables=["student_weakness", "video_cheatsheet_json"]) insight_prompt_builder = PromptBuilder(template=insight_template, required_variables=["conversation_history"]) verifier_prompt_builder = PromptBuilder(template=verifier_template, required_variables=["query", "informer_answer"]) intent_prompt_builder = PromptBuilder(template=intent_template, required_variables=["conversation_history"]) greeting_prompt_builder = PromptBuilder(template=greeting_template, required_variables=["master_prompt", "conversation_history"]) stress_prompt_builder = PromptBuilder(template=stress_template, required_variables=["master_prompt", "conversation_history"]) support_prompt_builder = PromptBuilder(template=support_template, required_variables=["master_prompt", "conversation_history"]) off_topic_prompt_builder = PromptBuilder(template=off_topic_template, required_variables=["master_prompt", "conversation_history"]) # Create generator generator = CustomGoogleAIGenerator(api_key=os.getenv("GOOGLE_API_KEY")) return { "informer_prompt_builder": informer_prompt_builder, "generator": generator, "practice_prompt_builder": practice_prompt_builder, "insight_prompt_builder": insight_prompt_builder, "verifier_prompt_builder": verifier_prompt_builder, "intent_prompt_builder": intent_prompt_builder, "videos_data": videos_data, "document_store": document_store, "tutor_master_prompt": tutor_master_prompt, "greeting_prompt_builder": greeting_prompt_builder, "stress_prompt_builder": stress_prompt_builder, "support_prompt_builder": support_prompt_builder, "off_topic_prompt_builder": off_topic_prompt_builder, "retriever": retriever, "text_embedder": text_embedder, "whisper_model": whisper_model } def transcribe_audio(audio_file, whisper_model: WhisperModel) -> str: """ Nhận audio file từ st.audio_input và chuyển đổi thành văn bản bằng Faster Whisper. Phiên bản được cập nhật cho môi trường deployment. """ if not audio_file: return "" tmp_file_path = "" try: # Đọc audio file từ st.audio_input (UploadedFile object) audio_bytes = audio_file.read() with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmpfile: tmpfile.write(audio_bytes) tmp_file_path = tmpfile.name print(f"DEBUG: [Whisper] Audio saved to temp file: {tmp_file_path}") print(f"DEBUG: [Whisper] Transcribing audio from: {tmp_file_path}") segments, info = whisper_model.transcribe(tmp_file_path, beam_size=5, language="vi") print(f"DEBUG: [Whisper] Detected language: {info.language} with probability {info.language_probability}") transcribed_text = " ".join(segment.text for segment in segments) print(f"DEBUG: [Whisper] Transcribed text: '{transcribed_text}'") return transcribed_text.strip() except Exception as e: st.error(f"Lỗi khi xử lý giọng nói: {e}") return "" finally: if tmp_file_path and os.path.exists(tmp_file_path): os.remove(tmp_file_path) print(f"DEBUG: [Whisper] Cleaned up temp file: {tmp_file_path}") def classify_intent(conversation_history: str, resources: Dict) -> str: """Phân loại ý định người dùng""" valid_intents = ['greeting_social', 'math_question', 'request_for_practice', 'expression_of_stress', 'study_support', 'off_topic'] try: prompt_builder = resources["intent_prompt_builder"] prompt_text = prompt_builder.run(conversation_history=conversation_history)["prompt"] result = resources["generator"].run(prompt_parts=[prompt_text]) intent = result["replies"][0].strip().lower() user_input_debug = "N/A" if 'User: ' in conversation_history: lines = conversation_history.split('\n') for line in reversed(lines): if line.strip().startswith('User: '): user_input_debug = line.replace('User: ', '').strip() break print(f"DEBUG - User input: {user_input_debug}") print(f"DEBUG - Classified intent: {intent}") if intent not in valid_intents: math_keywords = ['giải', 'tính', 'phương trình', 'bài tập', 'toán', 'xác suất', 'thống kê', 'hình học', 'đại số'] user_input_for_fallback = "N/A" if 'User: ' in conversation_history: lines = conversation_history.split('\n') for line in reversed(lines): if line.strip().startswith('User: '): user_input_for_fallback = line.replace('User: ', '').strip() break if any(keyword in user_input_for_fallback.lower() for keyword in math_keywords): intent = 'math_question' else: intent = 'greeting_social' return intent except Exception as e: print(f"DEBUG - Intent classification error: {e}") return 'greeting_social' def informer_agent(query: str, conversation_history_str: str, resources: Dict) -> str: """Agent giải toán dựa trên RAG""" try: result = resources["informer_pipeline"].run({ "text_embedder": {"text": query}, "prompt_builder": {"query": query, "conversation_history": conversation_history_str} }) return result["generator"]["replies"][0] except: return "Xin lỗi, tôi không thể giải bài này lúc này." def verifier_agent(query: str, informer_answer: str, resources: Dict) -> Dict: """Agent kiểm tra tính đúng đắn""" try: prompt_text = resources["verifier_prompt_builder"].run(query=query, informer_answer=informer_answer)["prompt"] result = resources["generator"].run(prompt_parts=[prompt_text]) llm_reply_string = result["replies"][0] json_match = re.search(r"\{.*\}", llm_reply_string, re.DOTALL) if json_match: return json.loads(json_match.group(0)) else: return {"is_correct": True, "correction_suggestion": "Lỗi parse verifier"} except Exception as e: print(f"ERROR: [Verifier Agent] Lỗi: {e}") return {"is_correct": True, "correction_suggestion": ""} def insight_agent(conversation_history: str, resources: Dict) -> Dict: """Agent phân tích điểm yếu, với logic trích xuất JSON thông minh.""" try: prompt_builder = resources["insight_prompt_builder"] prompt_text = prompt_builder.run(conversation_history=conversation_history)["prompt"] print("\n" + "="*50) print(prompt_text) print("="*50 + "\n") result = resources["generator"].run(prompt_parts=[prompt_text]) llm_reply = result["replies"][0] json_match = re.search(r"\{.*\}", llm_reply, re.DOTALL) if json_match: json_string = json_match.group(0) return json.loads(json_string) else: return {"misunderstood_concepts": [], "sentiment": "neutral"} except json.JSONDecodeError as e: return {"misunderstood_concepts": [], "sentiment": "neutral"} except Exception as e: return {"misunderstood_concepts": [], "sentiment": "neutral"} def practice_agent(student_weakness: str, resources: Dict) -> str: """Agent tạo bài tập""" try: video_cheatsheet = [] for video in resources["videos_data"]: video_cheatsheet.append({ "title": video["title"], "keywords": video["keywords"], "summary": video["summary_for_llm"] }) video_json = json.dumps(video_cheatsheet, ensure_ascii=False) prompt_text = resources["practice_prompt_builder"].run( student_weakness=student_weakness, video_cheatsheet_json=video_json )["prompt"] result = resources["generator"].run(prompt_parts=[prompt_text]) return result["replies"][0] except: return "Xin lỗi, tôi không thể tạo bài tập lúc này." def problem_solving_engine( query_text: str, query_image: bytes, conversation_history_str: str, resources: Dict ) -> str: """ Cỗ máy giải quyết vấn đề đa năng, TÁI SỬ DỤNG informer_prompt_builder. """ print("DEBUG: Multimodal Problem-Solving Engine activated.") try: extracted_text_from_image = "" if query_image: print("DEBUG: [Stage 1] Image detected. Calling Gemini for OCR...") try: ocr_prompt_parts = [ "Bạn là một hệ thống OCR toán học siêu chính xác. Hãy đọc và trích xuất toàn bộ văn bản từ hình ảnh sau đây. Chỉ trả về phần văn bản được trích xuất.", query_image ] ocr_result = resources["generator"].run(prompt_parts=ocr_prompt_parts) extracted_text_from_image = ocr_result["replies"][0] print(f"DEBUG: [Stage 1] Text extracted from image: '{extracted_text_from_image}'") except Exception as e: print(f"ERROR: [Stage 1] OCR failed: {e}") extracted_text_from_image = "Không thể đọc được nội dung từ hình ảnh." full_query_text = (query_text + " " + extracted_text_from_image).strip() print(f"DEBUG: [Stage 1.5] Full query text: '{full_query_text}'") context_docs = [] if full_query_text: try: print("DEBUG: [Stage 2] Starting RAG retrieval...") embedding = resources["text_embedder"].run(text=full_query_text)["embedding"] print("DEBUG: [Stage 2] Embedding created successfully") context_docs = resources["retriever"].run(query_embedding=embedding)["documents"] print(f"DEBUG: [Stage 2] Retrieved {len(context_docs)} documents") except Exception as e: print(f"ERROR: [Stage 2] RAG retrieval failed: {e}") context_docs = [] print("DEBUG: [Stage 3] Building final prompt...") try: informer_prompt_builder = resources["informer_prompt_builder"] print("DEBUG: [Stage 3a] Got informer_prompt_builder") text_prompt_result = informer_prompt_builder.run( query=query_text if query_text else "Giải bài toán trong hình.", conversation_history=conversation_history_str, documents=context_docs ) print("DEBUG: [Stage 3a] Prompt builder ran successfully") text_part = text_prompt_result["prompt"] print(f"DEBUG: [Stage 3a] Generated text prompt length: {len(text_part)} chars") except Exception as e: print(f"ERROR: [Stage 3a] Prompt building failed: {e}") # Fallback to simple prompt text_part = f"""Bạn là gia sư toán AI. Hãy giải bài toán sau: Câu hỏi: {query_text if query_text else "Giải bài toán trong hình"} Nội dung từ hình: {extracted_text_from_image} Lịch sử: {conversation_history_str} Hãy trả lời chi tiết bằng tiếng Việt:""" final_prompt_parts = [text_part] if query_image: final_prompt_parts.append("\n**Hình ảnh đính kèm:**") final_prompt_parts.append(query_image) print(f"DEBUG: [Stage 3b] Final prompt parts count: {len(final_prompt_parts)}") print("DEBUG: [Stage 4] Calling Gemini for final answer...") try: final_result = resources["generator"].run(prompt_parts=final_prompt_parts) informer_answer = final_result["replies"][0] print(f"DEBUG: [Stage 4] Got answer, length: {len(informer_answer)} chars") except Exception as e: print(f"ERROR: [Stage 4] Gemini call failed: {e}") return f"Xin lỗi, tôi không thể xử lý câu hỏi này lúc này. Lỗi: {str(e)}" try: print("DEBUG: [Stage 5] Starting verification...") verification_query = full_query_text if full_query_text else "Phân tích bài toán trong hình ảnh" verification = verifier_agent(verification_query, informer_answer, resources) print(f"DEBUG: [Stage 5] Verification result: {verification}") if verification.get("is_correct", True): return informer_answer else: correction = verification.get("correction_suggestion", "") return f"🔍 Tôi đã xem xét lại và thấy có một chút chưa chính xác. {correction}" except Exception as e: print(f"ERROR: [Stage 5] Verification failed: {e}") return informer_answer except Exception as e: print(f"ERROR: [Problem-Solving Engine] Critical error: {str(e)}") import traceback print(f"ERROR: [Problem-Solving Engine] Traceback: {traceback.format_exc()}") return f"Xin lỗi, đã có lỗi nghiêm trọng khi xử lý yêu cầu: {str(e)}" def tutor_agent_response(user_input: str, intent: str, conversation_history_str: str, resources: Dict, supabase: Client, user_id: str, display_name: str) -> str: """ Agent chính, bây giờ CHỈ xử lý các intent giao tiếp. Các câu hỏi toán học đã được xử lý bởi problem_solving_engine. """ print(f"DEBUG: Tutor Agent is handling a communication intent: '{intent}'") if intent == "greeting_social": prompt_builder = resources["greeting_prompt_builder"] elif intent == "expression_of_stress": prompt_builder = resources["stress_prompt_builder"] elif intent == "study_support": prompt_builder = resources["support_prompt_builder"] elif intent == "request_for_practice": print("DEBUG: Tutor Agent is triggering the Practice Flow.") insights = insight_agent(conversation_history_str, resources) if insights and insights.get("misunderstood_concepts"): weakness = insights["misunderstood_concepts"][0] return practice_agent(weakness, resources) else: return practice_agent("các chủ đề toán lớp 9 tổng quát", resources) else: prompt_builder = resources["off_topic_prompt_builder"] try: prompt_text = prompt_builder.run( master_prompt=resources["tutor_master_prompt"], conversation_history=conversation_history_str )["prompt"] result = resources["generator"].run(prompt_parts=[prompt_text]) return result["replies"][0] except Exception as e: print(f"ERROR: Could not generate response for intent '{intent}': {e}") return "Rất xin lỗi, tôi đang gặp một chút sự cố." def render_chat_message(content: str, is_user: bool, key: str, image: bytes = None): """Render tin nhắn chat, có thể kèm ảnh.""" css_class = "user-message" if is_user else "bot-message" if image: st.image(image, width=250) if content: # Xử lý format text để tránh hiển thị rời rạc cleaned_content = content.strip() # Tách thành các paragraph dựa trên line breaks kép paragraphs = cleaned_content.split('\n\n') formatted_paragraphs = [] for paragraph in paragraphs: if paragraph.strip(): # Xử lý từng paragraph lines = paragraph.split('\n') # Ghép các dòng trong cùng paragraph lại với nhau # Chỉ thêm space nếu dòng không kết thúc bằng dấu câu formatted_lines = [] for line in lines: line = line.strip() if line: # Nếu dòng kết thúc bằng dấu câu, không thêm space if line.endswith(('.', ',', ':', ';', '!', '?')): formatted_lines.append(line) else: # Nếu không kết thúc bằng dấu câu, thêm space để ghép với dòng tiếp theo formatted_lines.append(line + ' ') # Ghép các dòng trong paragraph paragraph_text = ''.join(formatted_lines) # Xử lý khoảng trắng thừa paragraph_text = ' '.join(paragraph_text.split()) formatted_paragraphs.append(paragraph_text) # Ghép các paragraph lại với line break final_content = '\n\n'.join(formatted_paragraphs) # Sử dụng markdown để render với format đúng st.markdown(f'
Hệ thống đa tác nhân chuyên nghiệp cho trải nghiệm học tập tối ưu
Nội dung chuẩn theo chương trình Toán lớp 9
Phân tích điểm yếu và đề xuất bài tập phù hợp
Kho video phong phú với lời giải chi tiết
● Online - Sẵn sàng hỗ trợ {display_name}