import streamlit as st import torch from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM import random import time # Configure page st.set_page_config( page_title="Text-to-Quiz Generator", page_icon="🧠", layout="wide" ) # Load the model with caching @st.cache_resource def load_model(): try: # Check if PyTorch is available print(f"PyTorch version: {torch.__version__}") print(f"CUDA available: {torch.cuda.is_available()}") # Using a smaller, more efficient model that works well for question generation model_name = "valhalla/t5-small-e2e-qg" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSeq2SeqLM.from_pretrained(model_name) # Set device device = "cuda" if torch.cuda.is_available() else "cpu" print(f"Using device: {device}") # Move model to device model = model.to(device) return model, tokenizer, device except Exception as e: st.error(f"Error loading model: {str(e)}") print(f"Error details: {str(e)}") return None, None, None # Custom CSS def load_css(): st.markdown(""" """, unsafe_allow_html=True) # Function to generate questions from a passage def generate_questions(model, tokenizer, device, text, num_questions=5): try: # Process text in chunks if it's too long max_length = 512 chunks = [] if len(text) > max_length: # Simple chunking based on sentences sentences = text.split('. ') current_chunk = "" for sentence in sentences: if len(current_chunk) + len(sentence) < max_length: current_chunk += sentence + ". " else: chunks.append(current_chunk) current_chunk = sentence + ". " if current_chunk: chunks.append(current_chunk) else: chunks = [text] all_generated_texts = [] # Process each chunk for chunk in chunks: inputs = tokenizer(chunk, return_tensors="pt", max_length=512, truncation=True) inputs = {k: v.to(device) for k, v in inputs.items()} # Generate with beam search for multiple diverse outputs with torch.no_grad(): outputs = model.generate( inputs["input_ids"], max_length=64, num_beams=5, num_return_sequences=min(3, num_questions), # Generate up to 3 questions per chunk temperature=1.0, diversity_penalty=1.0, num_beam_groups=5, early_stopping=True ) decoded_outputs = tokenizer.batch_decode(outputs, skip_special_tokens=True) all_generated_texts.extend(decoded_outputs) # If we have enough questions, stop if len(all_generated_texts) >= num_questions: break # Ensure we don't return more than num_questions all_generated_texts = all_generated_texts[:num_questions] # Process and extract questions and answers questions_answers = [] for generated_text in all_generated_texts: # Try to find question and answer if "?" in generated_text: parts = generated_text.split("?", 1) if len(parts) > 1: question = parts[0].strip() + "?" answer = parts[1].strip() # Clean up answer if it starts with common patterns for prefix in ["answer:", "a:", " - "]: if answer.lower().startswith(prefix): answer = answer[len(prefix):].strip() if question and answer and len(question) > 10: questions_answers.append({ "question": question, "answer": answer }) return questions_answers except Exception as e: st.error(f"Error generating questions: {str(e)}") print(f"Detailed error: {str(e)}") return [] # Function to create quiz from generated Q&A pairs def create_quiz(questions_answers, num_options=4): quiz_items = [] # First filter out very short answers and duplicates filtered_qa = [] seen_questions = set() for qa in questions_answers: q = qa["question"].strip() a = qa["answer"].strip() # Skip very short answers if len(a) < 2 or len(q) < 10: continue # Skip duplicate questions q_lower = q.lower() if q_lower in seen_questions: continue seen_questions.add(q_lower) filtered_qa.append({"question": q, "answer": a}) # Use the filtered Q&A pairs all_answers = [qa["answer"] for qa in filtered_qa] for i, qa in enumerate(filtered_qa): correct_answer = qa["answer"] # Create distractors by selecting random answers from other questions other_answers = [a for a in all_answers if a != correct_answer] if other_answers: # Select random distractors num_distractors = min(num_options - 1, len(other_answers)) distractors = random.sample(other_answers, num_distractors) # Combine correct answer and distractors options = [correct_answer] + distractors random.shuffle(options) quiz_items.append({ "id": i, "question": qa["question"], "correct_answer": correct_answer, "options": options }) return quiz_items # Alternative question generation using simpler approach def generate_questions_simple(text, num_questions=5): try: # Simple question generation for demonstration # In a real app, you'd use a proper NLP model # Extract sentences sentences = text.split('.') sentences = [s.strip() for s in sentences if len(s.strip()) > 20] # Select random sentences to turn into questions if len(sentences) < num_questions: selected_sentences = sentences else: selected_sentences = random.sample(sentences, num_questions) questions_answers = [] # Simple transformation of sentences into questions for sentence in selected_sentences: # Very simple question generation (not ideal but works as fallback) words = sentence.split() if len(words) < 5: continue # Extract key entities for answer potential_answer = " ".join(words[-3:]) # Create question from beginning of sentence question_words = words[:len(words)-3] question = " ".join(question_words) + "?" questions_answers.append({ "question": question, "answer": potential_answer }) return questions_answers except Exception as e: print(f"Error in simple question generation: {str(e)}") return [] # Main app def main(): load_css() # App title st.markdown('
{item["question"]}
Your answer: {user_answer}
Your answer: {user_answer}
Correct answer: {item["correct_answer"]}