File size: 22,676 Bytes
6947350 593daef 1a524f7 bdf1a51 593daef bb9f8aa bd3ead1 bdf1a51 bd3ead1 bdf1a51 bb9f8aa bdf1a51 593daef bdf1a51 6947350 bd3ead1 593daef bdf1a51 5e9ce61 e0365b0 bd3ead1 593daef bdf1a51 bd3ead1 690a5d7 bb9f8aa bdf1a51 63bc563 e42f9b2 bdf1a51 bd3ead1 bdf1a51 bb9f8aa 63bc563 bdf1a51 e208dea 104b202 593daef bdf1a51 593daef c6693f8 01c57fe bdf1a51 593daef d1848e7 593daef e208dea bd3ead1 593daef 7958064 593daef e208dea bd3ead1 593daef 79c0109 593daef e208dea bd3ead1 e208dea bd3ead1 e208dea c1325fe e3e145d c1325fe bdf1a51 593daef af23cee bb9f8aa 593daef 896265e bb9f8aa 593daef c1325fe 593daef 0d5d2d1 964471b 0d5d2d1 593daef bdf1a51 63bc563 0bafada bd3ead1 bdf1a51 bd3ead1 bdf1a51 bd3ead1 bdf1a51 bd3ead1 bdf1a51 a227606 bd3ead1 a227606 bd3ead1 e208dea af23cee bd3ead1 bdf1a51 bd3ead1 bdf1a51 d5a7f13 bd3ead1 bdf1a51 bd3ead1 bdf1a51 e208dea bdf1a51 bb9f8aa bdf1a51 bd3ead1 63bc563 bdf1a51 bd3ead1 896265e c1325fe bdf1a51 bd3ead1 c1325fe 593daef 896265e bdf1a51 593daef c1325fe bdf1a51 c1325fe 593daef bdf1a51 52b5a69 bdf1a51 bd3ead1 bdf1a51 bd3ead1 bdf1a51 bd3ead1 bdf1a51 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 | import gradio as gr
from groq import Groq
import os
import json
import logging
import re
import sys
import traceback
from typing import Dict, Any, Tuple
from dotenv import load_dotenv
import markdown2
# Set up detailed logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler("tutor_app.log")
]
)
# Load environment variables
load_dotenv()
logging.debug("Environment variables loaded")
# Print environment check
logging.debug(f"GROQ_API_KEY present: {bool(os.getenv('GROQ_API_KEY'))}")
# Initialize Groq client with error handling
try:
api_key = os.getenv("GROQ_API_KEY")
if not api_key:
logging.critical("GROQ_API_KEY is missing or empty")
raise EnvironmentError("GROQ_API_KEY environment variable is required but not set")
client = Groq(api_key=api_key)
logging.debug("Groq client initialized successfully")
# Test the client with a simple API call
try:
models = client.models.list()
logging.debug(f"API connection test successful. Available models: {[m.id for m in models.data][:3]}...")
except Exception as e:
logging.warning(f"API connection test failed: {e}")
except Exception as e:
logging.critical(f"Failed to initialize Groq client: {str(e)}")
logging.critical(traceback.format_exc())
raise EnvironmentError(f"Error initializing Groq API client: {str(e)}")
def transcribe_audio(audio):
"""Transcribe audio file to text using Groq's API."""
try:
if audio is None:
logging.debug("No audio input provided")
return ""
logging.debug(f"Audio input received: {type(audio)} - {audio}")
# Fixed: Properly handle audio path from Gradio
if isinstance(audio, dict) and 'path' in audio:
audio_path = audio['path']
else:
audio_path = audio
logging.debug(f"Audio path: {audio_path}")
if audio_path and os.path.exists(audio_path):
with open(audio_path, "rb") as audio_file:
audio_data = audio_file.read()
logging.debug(f"Audio file opened, size: {len(audio_data)} bytes")
try:
logging.debug("Sending audio to Groq API for transcription")
transcription = client.audio.transcriptions.create(
file=("audio.wav", audio_data),
model="distil-whisper-large-v3-en",
)
logging.debug(f"Transcription response received: {transcription}")
if hasattr(transcription, 'text'):
result = transcription.text
logging.debug(f"Transcription text: {result}")
return result
else:
result = transcription.get('text', "Transcription succeeded but returned no text")
logging.debug(f"Transcription text (alt format): {result}")
return result
except Exception as e:
logging.error(f"Audio transcription API error: {str(e)}")
return f"Audio transcription failed: {str(e)}"
else:
logging.warning(f"Audio file not found at path: {audio_path}")
return "Audio file not found. Please try recording again."
except Exception as e:
logging.error(f"Error in transcription: {str(e)}")
return f"Error in transcription: {str(e)}"
def generate_tutor_output(subject: str, difficulty: str, student_input: str) -> Dict[str, str]:
"""Generate educational content based on student input."""
try:
logging.debug(f"Generating tutor output for subject: {subject}, difficulty: {difficulty}")
logging.debug(f"Student input: {student_input}")
# Define enhanced topics for all subjects
topics = {
"math": {
"quadratic equation": "solving quadratic equations, including methods like factoring, using the quadratic formula, and completing the square",
"pythagorean theorem": "the Pythagorean theorem, its proof, and applications in geometry and trigonometry",
"calculus": "fundamental concepts of calculus, including limits, derivatives, and integrals",
"linear algebra": "basics of linear algebra, including vectors, matrices, and linear transformations",
"statistics": "key concepts in statistics, such as probability distributions, hypothesis testing, and regression analysis"
},
"science": {
"photosynthesis": "the process of photosynthesis in plants, including light-dependent and light-independent reactions",
"newton's laws": "Newton's laws of motion and their applications in classical mechanics",
"periodic table": "the structure and organization of the periodic table of elements",
"dna replication": "the process of DNA replication and its importance in cell division",
"climate change": "the causes and effects of climate change, including global warming and its impact on ecosystems"
},
}
subject_lower = subject.lower() if subject else "general"
enhanced_topic = None
for subj, subject_topics in topics.items():
for topic, description in subject_topics.items():
if topic.lower() in student_input.lower():
enhanced_topic = description
logging.debug(f"Enhanced topic detected: {topic}")
break
if enhanced_topic:
break
# Strengthened prompt with strict quiz formatting
if enhanced_topic:
logging.debug(f"Using enhanced topic: {enhanced_topic}")
prompt = f"""
You are an expert tutor specializing in {enhanced_topic} at the {difficulty} level. The student has asked: "{student_input}"
Generate a detailed response as a valid JSON object with exactly these keys and content:
- "lesson": A comprehensive lesson (3-4 paragraphs, at least 200 words) based on national educational standards, including historical context.
- "example": A detailed step-by-step example problem with a full solution, formatted as: "Example Problem: [question]\nStep 1: [step]\nStep 2: [step]\nAnswer: [solution]".
- "real_world_problem": A challenging real-world application of this concept (at least 100 words).
- "quiz": A single string containing exactly 3 multiple-choice questions, each formatted as "1. [Question]\n a) [option]\n b) [option]\n c) [option]\n Correct answer: [letter]", separated by newlines.
Ensure all sections are fully populated with relevant content. Return only the JSON object, enclosed in ```json``` markers, with no additional text outside the markers.
"""
else:
logging.debug(f"Using general subject: {subject_lower}")
prompt = f"""
You are an expert tutor in {subject_lower} at the {difficulty} level. The student has asked: "{student_input}"
Generate a detailed response as a valid JSON object with exactly these keys and content:
- "lesson": A descriptive, engaging lesson (3-4 paragraphs, at least 200 words) on the topic.
- "example": An example problem with a full solution, formatted as: "Example Problem: [question]\nStep 1: [step]\nStep 2: [step]\nAnswer: [solution]".
- "real_world_problem": A real-world problem solvable using the lesson concepts (at least 100 words).
- "quiz": A single string containing exactly 3 multiple-choice questions, each formatted as "1. [Question]\n a) [option]\n b) [option]\n c) [option]\n Correct answer: [letter]", separated by newlines.
Ensure all sections are fully populated with relevant content. Return only the JSON object, enclosed in ```json``` markers, with no additional text outside the markers.
"""
# Model selection with fallback
try:
models = client.models.list()
available_models = [m.id for m in models.data]
logging.debug(f"Available models: {available_models}")
target_model = "llama-3.3-70b-versatile" if "llama-3.3-70b-versatile" in available_models else available_models[0]
except Exception:
logging.warning("Could not fetch model list, using default model")
target_model = "llama-3.3-70b-versatile"
logging.debug(f"Sending prompt to model: {target_model}")
completion = client.chat.completions.create(
messages=[
{
"role": "system",
"content": f"You are the world's best AI tutor, renowned for your ability to explain complex concepts clearly and engagingly. Your expertise in {subject_lower} is unparalleled, and you tailor your teaching to {difficulty} level students. Always return a complete response with all requested sections as a valid JSON object enclosed in ```json``` markers.",
},
{
"role": "user",
"content": prompt,
}
],
model=target_model,
max_tokens=4000,
)
response_content = completion.choices[0].message.content
logging.debug(f"Raw API response: {response_content}")
# Extract JSON from response
json_match = re.search(r'```json\s*([\s\S]*?)\s*```', response_content)
if json_match:
json_str = json_match.group(1)
try:
result = json.loads(json_str)
logging.debug("Successfully parsed JSON from response")
except json.JSONDecodeError as e:
logging.warning(f"JSON parsing failed: {e}")
result = None
else:
logging.warning("No JSON markers found in response")
result = None
# Improved fallback parsing with better section detection
if not result:
logging.debug("Falling back to text parsing")
sections = {
"lesson": "",
"example": "",
"real_world_problem": "",
"quiz": ""
}
# Fix: Improved section detection
current_section = None
section_lines = {
"lesson": [],
"example": [],
"real_world_problem": [],
"quiz": []
}
# First, identify section headers
lines = response_content.split('\n')
for i, line in enumerate(lines):
line = line.strip()
if not line or line.startswith('```'):
continue
lower_line = line.lower()
if "lesson" in lower_line or "introduction" in lower_line:
current_section = "lesson"
continue
elif "example" in lower_line or "problem" in lower_line and "real" not in lower_line:
current_section = "example"
continue
elif any(kw in lower_line for kw in ["real-world", "real world", "application"]):
current_section = "real_world_problem"
continue
elif "quiz" in lower_line or "questions" in lower_line:
current_section = "quiz"
continue
if current_section:
section_lines[current_section].append(line)
# Join lines for each section
for section, lines in section_lines.items():
sections[section] = "\n".join(lines)
logging.debug(f"Parsed sections: {sections}")
result = sections
# Ensure all keys are present and non-empty
for key in ["lesson", "example", "real_world_problem", "quiz"]:
if key not in result or not result[key].strip():
result[key] = f"No {key.replace('_', ' ')} provided - generation incomplete"
return result
except Exception as e:
logging.error(f"Error in generate_tutor_output: {str(e)}")
logging.error(traceback.format_exc())
return {
"lesson": f"Error: {str(e)}",
"example": "",
"real_world_problem": "",
"quiz": ""
}
def process_output(output: Dict[str, Any]) -> Tuple[str, str, str, str]:
"""Process the output from generate_tutor_output into HTML format."""
try:
logging.debug(f"Processing output: {str(output)}")
lesson = markdown2.markdown(output.get("lesson", "No lesson available"))
example = markdown2.markdown(output.get("example", "No example available"))
real_world = markdown2.markdown(output.get("real_world_problem", "No real-world application available"))
quiz = markdown2.markdown(output.get("quiz", "No quiz available"))
logging.debug("Output processed successfully")
return lesson, example, real_world, quiz
except Exception as e:
logging.error(f"Error processing output: {str(e)}")
logging.error(traceback.format_exc())
return f"Error processing output: {str(e)}", "", "", ""
def create_interface() -> gr.Blocks:
"""Create the Gradio interface."""
logging.debug("Creating Gradio interface")
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🎓 Vers3Dynamics Tutor: Your Personal Learning Companion")
state = gr.State({"is_submitting": False})
with gr.Row():
with gr.Column(scale=2):
subject = gr.Dropdown(
["Art History", "Computer Science", "Literature", "Math", "Music", "Science", "Social Science"],
label="Subject",
info="Choose the subject of your lesson",
value="Math"
)
difficulty = gr.Radio(
["Primary", "Secondary", "Higher Education"],
label="Difficulty Level",
info="Select your proficiency level",
value="Secondary"
)
student_input = gr.Textbox(
placeholder="Type your topic or question here...",
label="Type Your Question",
info="Enter the topic you want to explore"
)
audio_input = gr.Audio(
type="filepath",
label="Speak Your Question",
sources=["microphone"],
format="wav"
)
with gr.Row():
submit_button = gr.Button("📚 Teach Me", variant="primary")
clear_button = gr.Button("🧹 Clear", variant="secondary")
status_indicator = gr.Textbox(
label="Status",
value="Ready",
interactive=False
)
with gr.Column(scale=3):
transcription_output = gr.Textbox(label="Transcribed Audio (if provided)")
lesson_output = gr.HTML(label="Lesson")
example_output = gr.HTML(label="Example")
real_world_output = gr.HTML(label="Real-World Application")
quiz_output = gr.HTML(label="Quiz")
gr.Markdown("""
### How to Use
1. Select a subject from the dropdown.
2. Choose your difficulty level.
3. Enter the topic or question you'd like to explore, or use the microphone to speak your question.
4. Click 'Teach Me' to receive a personalized lesson, example, real-world application, and quiz.
5. Review the AI-generated content to enhance your learning.
6. Use the 'Clear' button to reset all fields and start a new query.
7. Feel free to ask follow-up questions or explore new topics!
Remember: This is an AI-powered educational tool. Always verify important information with authoritative sources.
### How to Record Your Voice
1. Look for the microphone icon in the "Or speak your question" section.
2. Click on the microphone icon to start recording.
3. Speak clearly and at a normal pace into your device's microphone.
4. Click the stop button (square icon) when you're done speaking.
5. You can play back your recording using the play button to check if it's clear.
6. If you're satisfied with the recording, click 'Teach Me' to process your spoken question.
7. If you're not happy with the recording, you can click the microphone icon again to start over.
Note: Make sure your browser has permission to access your microphone. If you encounter any issues, try using a different browser or check your device's audio settings.
""")
def process_input(subject, difficulty, text_input, audio_input, state):
"""Process input from text or audio."""
try:
logging.info(f"Received inputs - subject: {subject}, difficulty: {difficulty}")
logging.info(f"Text input: '{text_input}', Audio input: {audio_input}")
subject = subject or "Math"
difficulty = difficulty or "Secondary"
if not text_input.strip() and not audio_input:
return (
{"is_submitting": False},
"Ready",
"No input provided",
"Please provide a question to begin",
"",
"",
""
)
if text_input.strip():
student_input = text_input.strip()
transcribed_text = "Using text input"
elif audio_input:
transcribed_text = transcribe_audio(audio_input)
student_input = transcribed_text
if "error" in transcribed_text.lower():
return (
{"is_submitting": False},
"Ready",
transcribed_text,
"Transcription error. Please try typing your question.",
"",
"",
""
)
else:
return (
{"is_submitting": False},
"Ready",
"No valid input",
"Please provide a question",
"",
"",
""
)
tutor_output = generate_tutor_output(subject, difficulty, student_input)
lesson, example, real_world, quiz = process_output(tutor_output)
return (
{"is_submitting": False},
"Ready",
transcribed_text,
lesson,
example,
real_world,
quiz
)
except Exception as e:
logging.error(f"Error in process_input: {str(e)}")
logging.error(traceback.format_exc())
return (
{"is_submitting": False},
"Error",
f"Error: {str(e)}",
f"Error processing request: {str(e)}",
"",
"",
""
)
def clear_outputs():
"""Clear all inputs and outputs."""
logging.debug("Clearing all outputs")
return {"is_submitting": False}, "Ready", "", "", "", "", "", ""
submit_button.click(
fn=process_input,
inputs=[subject, difficulty, student_input, audio_input, state],
outputs=[state, status_indicator, transcription_output, lesson_output, example_output, real_world_output, quiz_output]
)
clear_button.click(
fn=clear_outputs,
inputs=[],
outputs=[state, status_indicator, student_input, transcription_output, lesson_output, example_output, real_world_output, quiz_output]
)
return demo
def check_health():
"""Perform a health check of required components."""
problems = []
if not os.getenv("GROQ_API_KEY"):
problems.append("GROQ_API_KEY environment variable is not set")
try:
import markdown2
except ImportError:
problems.append("markdown2 package is not installed")
try:
client = Groq(api_key=os.getenv("GROQ_API_KEY") or "dummy_key_for_test")
except Exception as e:
problems.append(f"Groq client initialization failed: {str(e)}")
return problems
if __name__ == "__main__":
logging.info("Starting Vers3Dynamics Tutor application")
health_issues = check_health()
if health_issues:
for issue in health_issues:
logging.critical(f"Health check failed: {issue}")
logging.critical("Application may not function properly due to the above issues")
try:
demo = create_interface()
logging.info("Gradio interface created successfully")
try:
logging.info("Launching Gradio server")
demo.queue()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
debug=True
)
except Exception as e:
logging.critical(f"Failed to launch Gradio server: {str(e)}")
logging.critical(traceback.format_exc())
except Exception as e:
logging.critical(f"Failed to create Gradio interface: {str(e)}")
logging.critical(traceback.format_exc()) |