capradeepgujaran commited on
Commit
60b2489
1 Parent(s): 82ee3e0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -105
app.py CHANGED
@@ -5,33 +5,17 @@ from PIL import Image, ImageDraw, ImageFont
5
  from datetime import datetime
6
  import json
7
  import tempfile
8
- from typing import List, Dict, Tuple, Optional, Any
9
  from dataclasses import dataclass
10
  import subprocess
11
- from pathlib import Path
12
- import logging
13
- from functools import lru_cache
14
 
15
- # Configure logging
16
- logging.basicConfig(
17
- level=logging.INFO,
18
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
19
- )
20
- logger = logging.getLogger(__name__)
21
-
22
- @dataclass(frozen=True)
23
  class Question:
24
  question: str
25
  options: List[str]
26
  correct_answer: int
27
 
28
- def __post_init__(self):
29
- if not (0 <= self.correct_answer < len(self.options)):
30
- raise ValueError(f"Correct answer index {self.correct_answer} out of range")
31
- if len(self.options) != 4:
32
- raise ValueError(f"Must have exactly 4 options, got {len(self.options)}")
33
-
34
- @dataclass(frozen=True)
35
  class QuizFeedback:
36
  is_correct: bool
37
  selected: Optional[str]
@@ -40,50 +24,61 @@ class QuizFeedback:
40
  class QuizGenerator:
41
  def __init__(self, api_key: str):
42
  self.client = Groq(api_key=api_key)
43
- self._question_cache = lru_cache(maxsize=100)(self._generate_questions_internal)
44
 
45
  def generate_questions(self, text: str, num_questions: int) -> List[Question]:
 
 
46
  try:
47
- # Use text hash as cache key
48
- text_hash = hash(text)
49
- return self._question_cache(text_hash, num_questions)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  except Exception as e:
51
- logger.error(f"Error generating questions: {e}")
52
- raise QuizGenerationError(f"Failed to generate questions: {e}")
 
 
 
 
53
 
54
- def _generate_questions_internal(self, text_hash: str, num_questions: int) -> List[Question]:
55
- """Internal question generation with retry logic"""
56
- max_retries = 3
57
- last_error = None
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- for attempt in range(max_retries):
60
- try:
61
- response = self.client.chat.completions.create(
62
- messages=[
63
- {
64
- "role": "system",
65
- "content": """You are a quiz generator. Create clear questions with concise answer options.
66
- Always return valid JSON array of questions. Format answers as short, clear phrases."""
67
- },
68
- {
69
- "role": "user",
70
- "content": self._create_prompt(text_hash, num_questions)
71
- }
72
- ],
73
- model="llama-3.2-3b-preview",
74
- temperature=0,
75
- max_tokens=2048
76
- )
77
-
78
- questions = self._parse_response(response.choices[0].message.content)
79
- return self._validate_questions(questions, num_questions)
80
-
81
- except Exception as e:
82
- last_error = e
83
- logger.warning(f"Attempt {attempt + 1}/{max_retries} failed: {e}")
84
- if attempt == max_retries - 1:
85
- raise QuizGenerationError(f"Failed after {max_retries} attempts: {last_error}")
86
- continue
87
 
88
  def _parse_response(self, response_text: str) -> List[Dict]:
89
  """Parse response with improved error handling"""
@@ -101,20 +96,18 @@ class QuizGenerator:
101
 
102
  json_text = cleaned_text[start_idx:end_idx + 1]
103
 
 
104
  try:
105
- parsed = json.loads(json_text)
106
- if not isinstance(parsed, list):
107
- raise ValueError("Response is not a JSON array")
108
- return parsed
109
  except json.JSONDecodeError as e:
110
- logger.error(f"JSON Parse Error: {e}")
111
- logger.debug(f"Attempted to parse: {json_text}")
112
  raise
113
 
114
  except Exception as e:
115
- logger.error(f"Error parsing response: {e}")
116
- logger.debug(f"Original response: {response_text}")
117
- raise ValueError(f"Failed to parse response: {e}")
118
 
119
  def _validate_questions(self, questions: List[Dict], num_questions: int) -> List[Question]:
120
  """Validate questions with improved error checking"""
@@ -123,7 +116,7 @@ class QuizGenerator:
123
  for q in questions:
124
  try:
125
  if not self._is_valid_question(q):
126
- logger.warning(f"Invalid question format: {q}")
127
  continue
128
 
129
  validated.append(Question(
@@ -132,7 +125,7 @@ class QuizGenerator:
132
  correct_answer=int(q["correct_answer"]) % 4
133
  ))
134
  except Exception as e:
135
- logger.error(f"Error validating question: {e}")
136
  continue
137
 
138
  if not validated:
@@ -427,54 +420,52 @@ class QuizApp:
427
  def __init__(self, api_key: str):
428
  self.quiz_generator = QuizGenerator(api_key)
429
  self.certificate_generator = CertificateGenerator()
430
- self._current_questions: List[Question] = []
431
- self._user_answers: List[Optional[str]] = []
432
 
433
  def generate_questions(self, text: str, num_questions: int) -> Tuple[bool, List[Question]]:
434
- """Generate quiz questions with improved error handling"""
 
 
 
435
  try:
436
  questions = self.quiz_generator.generate_questions(text, num_questions)
437
- self._current_questions = questions
438
- self._user_answers = [None] * len(questions)
439
  return True, questions
440
  except Exception as e:
441
- logger.error(f"Error generating questions: {e}")
442
  return False, []
443
-
444
  def calculate_score(self, answers: List[Optional[str]]) -> Tuple[float, bool, List[QuizFeedback]]:
445
- """Calculate quiz score with improved validation"""
446
- if not answers or not self._current_questions:
 
 
 
447
  return 0, False, []
448
 
449
- if len(answers) != len(self._current_questions):
450
- logger.warning("Answer count mismatch with questions")
451
- return 0, False, []
452
-
453
  feedback = []
454
  correct = 0
455
 
456
- for question, answer in zip(self._current_questions, answers):
457
- is_correct = False
458
- correct_answer = question.options[question.correct_answer]
459
-
460
- if answer is not None:
461
- try:
462
- selected_index = question.options.index(answer)
463
- is_correct = selected_index == question.correct_answer
464
- if is_correct:
465
- correct += 1
466
- except ValueError:
467
- logger.warning(f"Invalid answer selected: {answer}")
468
-
469
- feedback.append(QuizFeedback(
470
- is_correct=is_correct,
471
- selected=answer,
472
- correct_answer=correct_answer
473
- ))
474
 
475
- score = (correct / len(self._current_questions)) * 100
476
  return score, score >= 80, feedback
477
-
478
 
479
  def update_questions(self, text: str, num_questions: int) -> Tuple[gr.update, gr.update, List[gr.update], List[Question], gr.update]:
480
  """
@@ -527,8 +518,6 @@ class QuizApp:
527
  gr.update(selected=1)
528
  )
529
 
530
-
531
-
532
  def submit_quiz(self, q1: Optional[str], q2: Optional[str], q3: Optional[str],
533
  q4: Optional[str], q5: Optional[str], questions: List[Question]
534
  ) -> Tuple[gr.update, List[gr.update], float, str, gr.update]:
@@ -953,5 +942,4 @@ def create_quiz_interface():
953
 
954
  if __name__ == "__main__":
955
  demo = create_quiz_interface()
956
- demo.launch()
957
-
 
5
  from datetime import datetime
6
  import json
7
  import tempfile
8
+ from typing import List, Dict, Tuple, Optional
9
  from dataclasses import dataclass
10
  import subprocess
 
 
 
11
 
12
+ @dataclass
 
 
 
 
 
 
 
13
  class Question:
14
  question: str
15
  options: List[str]
16
  correct_answer: int
17
 
18
+ @dataclass
 
 
 
 
 
 
19
  class QuizFeedback:
20
  is_correct: bool
21
  selected: Optional[str]
 
24
  class QuizGenerator:
25
  def __init__(self, api_key: str):
26
  self.client = Groq(api_key=api_key)
 
27
 
28
  def generate_questions(self, text: str, num_questions: int) -> List[Question]:
29
+ prompt = self._create_prompt(text, num_questions)
30
+
31
  try:
32
+ response = self.client.chat.completions.create(
33
+ messages=[
34
+ {
35
+ "role": "system",
36
+ "content": """You are a quiz generator. Create clear questions with concise answer options.
37
+ Always return valid JSON array of questions. Format answers as short, clear phrases."""
38
+ },
39
+ {
40
+ "role": "user",
41
+ "content": prompt
42
+ }
43
+ ],
44
+ model="llama-3.2-3b-preview",
45
+ temperature=0,
46
+ max_tokens=2048
47
+ )
48
+
49
+ questions = self._parse_response(response.choices[0].message.content)
50
+ return self._validate_questions(questions, num_questions)
51
+
52
  except Exception as e:
53
+ print(f"Error in generate_questions: {str(e)}")
54
+ print(f"Response content: {response.choices[0].message.content if response else 'No response'}")
55
+ raise QuizGenerationError(f"Failed to generate questions: {str(e)}")
56
+
57
+ def _create_prompt(self, text: str, num_questions: int) -> str:
58
+ return f"""Generate exactly {num_questions} multiple choice questions based on the following text. Follow these rules strictly:
59
 
60
+ 1. Each question must be clear and focused
61
+ 2. Provide exactly 4 options for each question
62
+ 3. Mark the correct answer with index (0-3)
63
+ 4. Keep all options concise (under 10 words)
64
+ 5. Return ONLY a JSON array with this exact format:
65
+ [
66
+ {{
67
+ "question": "Clear question text here?",
68
+ "options": [
69
+ "Brief option 1",
70
+ "Brief option 2",
71
+ "Brief option 3",
72
+ "Brief option 4"
73
+ ],
74
+ "correct_answer": 0
75
+ }}
76
+ ]
77
 
78
+ Text to generate questions from:
79
+ {text}
80
+
81
+ Important: Return only the JSON array, no other text or formatting."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  def _parse_response(self, response_text: str) -> List[Dict]:
84
  """Parse response with improved error handling"""
 
96
 
97
  json_text = cleaned_text[start_idx:end_idx + 1]
98
 
99
+ # Attempt to parse the JSON
100
  try:
101
+ return json.loads(json_text)
 
 
 
102
  except json.JSONDecodeError as e:
103
+ print(f"JSON Parse Error: {str(e)}")
104
+ print(f"Attempted to parse: {json_text}")
105
  raise
106
 
107
  except Exception as e:
108
+ print(f"Error parsing response: {str(e)}")
109
+ print(f"Original response: {response_text}")
110
+ raise ValueError(f"Failed to parse response: {str(e)}")
111
 
112
  def _validate_questions(self, questions: List[Dict], num_questions: int) -> List[Question]:
113
  """Validate questions with improved error checking"""
 
116
  for q in questions:
117
  try:
118
  if not self._is_valid_question(q):
119
+ print(f"Invalid question format: {q}")
120
  continue
121
 
122
  validated.append(Question(
 
125
  correct_answer=int(q["correct_answer"]) % 4
126
  ))
127
  except Exception as e:
128
+ print(f"Error validating question: {str(e)}")
129
  continue
130
 
131
  if not validated:
 
420
  def __init__(self, api_key: str):
421
  self.quiz_generator = QuizGenerator(api_key)
422
  self.certificate_generator = CertificateGenerator()
423
+ self.current_questions: List[Question] = []
 
424
 
425
  def generate_questions(self, text: str, num_questions: int) -> Tuple[bool, List[Question]]:
426
+ """
427
+ Generate quiz questions using the QuizGenerator
428
+ Returns (success, questions) tuple
429
+ """
430
  try:
431
  questions = self.quiz_generator.generate_questions(text, num_questions)
432
+ self.current_questions = questions
 
433
  return True, questions
434
  except Exception as e:
435
+ print(f"Error generating questions: {e}")
436
  return False, []
437
+
438
  def calculate_score(self, answers: List[Optional[str]]) -> Tuple[float, bool, List[QuizFeedback]]:
439
+ """
440
+ Calculate the quiz score and generate feedback
441
+ Returns (score, passed, feedback) tuple
442
+ """
443
+ if not answers or not self.current_questions:
444
  return 0, False, []
445
 
 
 
 
 
446
  feedback = []
447
  correct = 0
448
 
449
+ for question, answer in zip(self.current_questions, answers):
450
+ if answer is None:
451
+ feedback.append(QuizFeedback(False, None, question.options[question.correct_answer]))
452
+ continue
453
+
454
+ try:
455
+ selected_index = question.options.index(answer)
456
+ is_correct = selected_index == question.correct_answer
457
+ if is_correct:
458
+ correct += 1
459
+ feedback.append(QuizFeedback(
460
+ is_correct,
461
+ answer,
462
+ question.options[question.correct_answer]
463
+ ))
464
+ except ValueError:
465
+ feedback.append(QuizFeedback(False, answer, question.options[question.correct_answer]))
 
466
 
467
+ score = (correct / len(self.current_questions)) * 100
468
  return score, score >= 80, feedback
 
469
 
470
  def update_questions(self, text: str, num_questions: int) -> Tuple[gr.update, gr.update, List[gr.update], List[Question], gr.update]:
471
  """
 
518
  gr.update(selected=1)
519
  )
520
 
 
 
521
  def submit_quiz(self, q1: Optional[str], q2: Optional[str], q3: Optional[str],
522
  q4: Optional[str], q5: Optional[str], questions: List[Question]
523
  ) -> Tuple[gr.update, List[gr.update], float, str, gr.update]:
 
942
 
943
  if __name__ == "__main__":
944
  demo = create_quiz_interface()
945
+ demo.launch()